Preauthentication Plugin Framework
authorSam Hartman <hartmans@mit.edu>
Tue, 3 Oct 2006 19:07:17 +0000 (19:07 +0000)
committerSam Hartman <hartmans@mit.edu>
Tue, 3 Oct 2006 19:07:17 +0000 (19:07 +0000)
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

30 files changed:
README
doc/copyright.texinfo
src/Makefile.in
src/config-files/krb5.conf.M
src/config/pre.in
src/configure.in
src/include/k5-int.h
src/include/kdb.h
src/include/krb5/krb5.hin
src/include/krb5/preauth_plugin.h [new file with mode: 0644]
src/kdc/Makefile.in
src/kdc/dispatch.c
src/kdc/do_as_req.c
src/kdc/kdc_preauth.c
src/kdc/kdc_util.h
src/kdc/main.c
src/lib/krb5/error_tables/krb5_err.et
src/lib/krb5/krb/Makefile.in
src/lib/krb5/krb/get_in_tkt.c
src/lib/krb5/krb/init_ctx.c
src/lib/krb5/krb/preauth2.c
src/lib/krb5/os/init_os_ctx.c
src/plugins/preauth/cksum_body/Makefile.in [new file with mode: 0644]
src/plugins/preauth/cksum_body/cksum_body.exports [new file with mode: 0644]
src/plugins/preauth/cksum_body/configure.in [new file with mode: 0644]
src/plugins/preauth/cksum_body/src/cksum_body.c [new file with mode: 0644]
src/plugins/preauth/wpse/Makefile.in [new file with mode: 0644]
src/plugins/preauth/wpse/configure.in [new file with mode: 0644]
src/plugins/preauth/wpse/src/wpse.c [new file with mode: 0644]
src/plugins/preauth/wpse/wpse.exports [new file with mode: 0644]

diff --git a/README b/README
index 243af7d7e092f3ac6ffaf7f4c3dd27a64660d191..13802a8f9e9b6e60b5772c619d8ac935ea334545 100644 (file)
--- a/README
+++ b/README
@@ -112,7 +112,7 @@ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
 Individual source code files are copyright MIT, Cygnus Support,
-OpenVision, Oracle, Sun Soft, FundsXpress, and others.
+Novell, OpenVision, Oracle, Redhat, Sun Soft, FundsXpress, and others.
 
 Project Athena, Athena, Athena MUSE, Discuss, Hesiod, Kerberos, Moira,
 and Zephyr are trademarks of the Massachusetts Institute of Technology
@@ -222,6 +222,37 @@ src/lib/crypto/aes has the following copyright:
  in respect of any properties, including, but not limited to, correctness 
  and fitness for purpose.
 
+---Code, including the preauthentication plugins contains the following copyright:
+     Copyright (c) 2006 Red Hat, Inc.
+    Portions copyright (c) 2006 Massachusetts Institute of Technology
+      All Rights Reserved.
+
+     Redistribution and use in source and binary forms, with or without
+     modification, are permitted provided that the following conditions are met:
+
+     Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in
+       the documentation and/or other materials provided with the
+       distribution.
+     Neither the name of Red Hat, Inc., nor the names of its
+       contributors may be used to endorse or promote products derived
+       from this software without specific prior written permission.
+
+
+     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+     IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+     TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+     PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+     OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+     EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+     PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 --- The implementations of GSSAPI mechglue in GSSAPI-SPNEGO in
     src/lib/gssapi, including the following files:
 
index cfb9e2ec3f0ec6b7910d8a91f7fb0b6e9317ccc2..cbe9c9751b4b1ed18b229226abb4380fbd63eac5 100644 (file)
@@ -143,6 +143,41 @@ and fitness for purpose.
 @vskip 12pt
 @end iftex
 
+Portions contributed by Redhat contain the following copyright:
+
+@quotation
+ Copyright (c) 2006 Red Hat, Inc.
+Portions copyright (c) 2006 Massachusetts Institute of Technology
+  All Rights Reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+ Neither the name of Red Hat, Inc., nor the names of its
+    contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+@end quotation
+
 @value{PRODUCT} includes documentation and software developed at the
 University of California at Berkeley, which includes this copyright
 notice:
index 423cca4c5f5d0ed108cf983ae1f29241ebc41a5a..94829d061cf46b5cd25cacaa86625d473323bcac 100644 (file)
@@ -3,7 +3,7 @@ datadir=@datadir@
 thisconfigdir=.
 myfulldir=.
 mydir=.
-# Don't build sample by default: plugins/locate/python 
+# Don't build sample by default: plugins/locate/python plugins/preauth/wpse plugins/preauth/cksum_body
 SUBDIRS=util include lib @krb524@ kdc kadmin @ldap_plugin_dir@ slave clients \
        plugins/kdb/db2 \
        appl tests \
index 0791c5fa8bbfe3c4aadc30fdf50ff54d4664f407..92460a79b952b0ad0ddc0f5393b1432e698ad3be 100644 (file)
@@ -162,6 +162,12 @@ libraries, use a value of 3 to use the CKSUMTYPE_RSA_MD4_DES
 instead.  This field is ignored when its value is incompatible with
 the session key type.
 
+.IP preferred_preauth_types
+This allows you to set the preferred preauthentication types which the
+client will attempt before others which may be advertised by a KDC.  The
+default value for this setting is "17, 16, 15, 14", which forces libkrb5
+to attempt to use PKINIT if it is supported.
+
 .IP ccache_type
 User this parameter on systems which are DCE clients, to specify the
 type of cache to be created by kinit, or when forwarded tickets are
@@ -175,7 +181,7 @@ Specifies the location of the Kerberos V4 srvtab file.  Default is
 "/etc/srvtab".
 
 .IP krb4_config
-Specifies the location of hte Kerberos V4 configuration file.  Default
+Specifies the location of the Kerberos V4 configuration file.  Default
 is "/etc/krb.conf".
 
 .IP krb4_realms
index ba452c77c49b1a8f70cae3721afcfbc17df6efc4..47f8c67f7c928df9ca1ad324bd4fc6844a2090de 100644 (file)
@@ -213,6 +213,7 @@ KRB5_SHLIBDIR = @libdir@$(SHLIB_TAIL_COMP)
 KRB5_INCDIR = @includedir@
 MODULE_DIR = @libdir@/krb5/plugins
 KRB5_DB_MODULE_DIR = $(MODULE_DIR)/kdb
+KRB5_PA_MODULE_DIR = $(MODULE_DIR)/preauth
 KRB5_LIBKRB5_MODULE_DIR = $(MODULE_DIR)/libkrb5
 KRB5_INCSUBDIRS = \
        $(KRB5_INCDIR)/krb5 \
index f7e795edef3aa034f332184e58eba17dd008ba70..f23211ea2dc8b881ee4804c74050c7d88eb45bc7 100644 (file)
@@ -916,7 +916,7 @@ if test -n "$OPENLDAP_PLUGIN"; then
 fi
 AC_SUBST(ldap_plugin_dir)
 
-AC_CONFIG_SUBDIRS(lib/apputils plugins/kdb/db2 appl tests)
+AC_CONFIG_SUBDIRS(lib/apputils plugins/kdb/db2 plugins/preauth/wpse plugins/preauth/cksum_body appl tests)
 dnl
 if false; then
   AC_CHECK_HEADERS(Python.h python2.3/Python.h)
index 46f4e03e33ff9535e0726d6c6cf32a93f0b20748..53b2acbf2b3b01e61ab8fc273b5d9a7bdc619152 100644 (file)
@@ -550,6 +550,9 @@ krb5int_locate_server (krb5_context, const krb5_data *realm,
 
 #endif /* KRB5_LIBOS_PROTO__ */
 
+#include <krb5/preauth_plugin.h>
+typedef struct _krb5_preauth_context krb5_preauth_context;
+
 /* new encryption provider api */
 
 struct krb5_enc_provider {
@@ -966,13 +969,22 @@ void krb5int_populate_gic_opt (
 
 
 krb5_error_code krb5_do_preauth
-(krb5_context, krb5_kdc_req *,
-               krb5_pa_data **, krb5_pa_data ***,
+(krb5_context, krb5_preauth_context **, krb5_kdc_req *, krb5_data *,
+               krb5_data *, krb5_pa_data **, krb5_pa_data ***,
                krb5_data *salt, krb5_data *s2kparams,
  krb5_enctype *,
                krb5_keyblock *,
                krb5_prompter_fct, void *,
                krb5_gic_get_as_key_fct, void *);
+void KRB5_CALLCONV krb5_init_preauth_context
+       (krb5_context, krb5_preauth_context **);
+void KRB5_CALLCONV krb5_clear_preauth_context_use_counts
+       (krb5_context, krb5_preauth_context *);
+void KRB5_CALLCONV krb5_preauth_prepare_request
+       (krb5_context, krb5_preauth_context **,
+        krb5_get_init_creds_opt *, krb5_kdc_req *);
+void KRB5_CALLCONV krb5_free_preauth_context
+       (krb5_context, krb5_preauth_context *);
 
 void KRB5_CALLCONV krb5_free_sam_challenge
        (krb5_context, krb5_sam_challenge * );
@@ -1063,6 +1075,9 @@ struct _krb5_context {
     struct krb5plugin_service_locate_ftable *vtbl;
     void (**locate_fptrs)(void);
 
+    /* preauth module stuff */
+    struct plugin_dir_handle preauth_plugins;
+
     /* error detail info */
     struct errinfo err;
 };
index a8381aaa62bf84850d6affa75dd0dee10f11546d..9f1fedbf293c5f9a2035d8f55bc9998957ffd3d6 100644 (file)
@@ -171,6 +171,7 @@ typedef struct __krb5_key_salt_tuple {
 #define KRB5_TL_SECURID_STATE           0x0006
 #define KRB5_TL_DB_ARGS                 0x7fff
 #endif /* SECURID */
+#define KRB5_TL_USER_CERTIFICATE        0x0007
     
 /*
  * Determines the number of failed KDC requests before DISALLOW_ALL_TIX is set
index 418a21c0a48bf0ef8e9ba67c6f028ebae33d68f0..32b714adb1b7269cf597066a154da90c51fcec86 100644 (file)
@@ -884,9 +884,14 @@ krb5_error_code krb5_decrypt_data
 #define KRB5_PADATA_ETYPE_INFO         11 /* Etype info for preauth */
 #define KRB5_PADATA_SAM_CHALLENGE      12 /* draft challenge system */
 #define KRB5_PADATA_SAM_RESPONSE       13 /* draft challenge system response */
-#define KRB5_PADATA_PK_AS_REQ          14 /* PKINIT */
-#define KRB5_PADATA_PK_AS_REP          15 /* PKINIT */
+#define KRB5_PADATA_PK_AS_REQ_OLD      14 /* PKINIT */
+#define KRB5_PADATA_PK_AS_REP_OLD      15 /* PKINIT */
+#define KRB5_PADATA_PK_AS_REQ          16 /* PKINIT */
+#define KRB5_PADATA_PK_AS_REP          17 /* PKINIT */
 #define KRB5_PADATA_ETYPE_INFO2                19
+#define KRB5_PADATA_USE_SPECIFIED_KVNO 20
+#define KRB5_PADATA_SAM_REDIRECT       21
+#define KRB5_PADATA_GET_FROM_TYPED_DATA        22
 #define KRB5_PADATA_REFERRAL           25 /* draft referral system */
 #define KRB5_PADATA_SAM_CHALLENGE_2    30 /* draft challenge system, updated */
 #define KRB5_PADATA_SAM_RESPONSE_2     31 /* draft challenge system, updated */
diff --git a/src/include/krb5/preauth_plugin.h b/src/include/krb5/preauth_plugin.h
new file mode 100644 (file)
index 0000000..7c9788d
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * <krb5/preauth_plugin.h>
+ *
+ * Copyright (c) 2006 Red Hat, Inc.
+ * Portions copyright (c) 2006 Massachusetts Institute of Technology
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Red Hat, Inc., nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Preauthentication plugin definitions for Kerberos 5.
+ */
+
+#ifndef KRB5_PREAUTH_PLUGIN_H_INCLUDED
+#define KRB5_PREAUTH_PLUGIN_H_INCLUDED
+#include <krb5/krb5.h>
+
+/*
+ * While arguments of these types are passed-in, for the most part a preauth
+ * module can treat them as opaque.  If we need keying data, we can ask for
+ * it directly.
+ */
+struct _krb5_db_entry_new;
+struct _krb5_key_data;
+
+/*
+ * Preauth mechanism property flags, unified from previous definitions in the
+ * KDC and libkrb5 sources.
+ */
+
+/* Provides a real answer which we can send back to the KDC (client-only).  The
+ * client assumes that one real answer will be enough. */
+#define PA_REAL                0x00000001
+
+/* Doesn't provide a real answer, but must be given a chance to run before any
+ * REAL mechanism callbacks (client-only). */
+#define PA_INFO                0x00000002
+
+/* Causes the KDC to include this mechanism in a list of supported preauth
+ * types if the user's DB entry flags the user as requiring hardware-based
+ * preauthentication (server-only). */
+#define PA_HARDWARE    0x00000004
+
+/* Causes the KDC to include this mechanism in a list of supported preauth
+ * types if the user's DB entry flags the user as requiring preauthentication,
+ * and to fail preauthentication if we can't verify the client data.  The
+ * flipside of PA_SUFFICIENT (server-only). */
+#define PA_REQUIRED    0x00000008
+
+/* Causes the KDC to include this mechanism in a list of supported preauth
+ * types if the user's DB entry flags the user as requiring preauthentication,
+ * and to mark preauthentication as successful if we can verify the client
+ * data.  The flipside of PA_REQUIRED (server-only). */
+#define PA_SUFFICIENT  0x00000010
+
+/* Marks this preauthentication mechanism as one which changes the key which is
+ * used for encrypting the response to the client.  Modules which have this
+ * flag have their server_return_proc called before modules which do not, and
+ * are passed over if a previously-called module has modified the encrypting
+ * key (server-only). */
+#define PA_REPLACES_KEY        0x00000020
+
+/* Causes the KDC to check with this preauthentication module even if the
+ * client has no entry in the realm database.  If the module returns a success
+ * code, continue processing and assume that its return_padata callback will
+ * supply us with a key for encrypting the AS reply (server-only). */
+/* #define PA_VIRTUAL  (0x00000040 | PA_REPLACES_KEY) */
+
+/* Not really a padata type, so don't include it in any list of preauth types
+ * which gets sent over the wire. */
+#define PA_PSEUDO      0x00000080
+
+/*
+ * A server module's callback functions are allowed to request specific types
+ * of information about the given client or server record or request, even
+ * though the database records themselves are opaque to the module.
+ */
+enum krb5plugin_preauth_entry_request_type {
+    /* The returned krb5_data item holds a DER-encoded X.509 certificate. */
+    krb5plugin_preauth_entry_request_certificate = 1,
+    /* The returned krb5_data_item holds a krb5_deltat. */
+    krb5plugin_preauth_entry_max_time_skew = 2,
+    /* The returned krb5_data_item holds an array of krb5_keyblock structures,
+     * terminated by an entry with key type = 0.
+     * Each keyblock should have its contents freed in turn, and then the data
+     * item itself should be freed. */
+    krb5plugin_preauth_keys = 3,
+    /* The returned krb5_data_item holds the request structure, re-encoded
+     * using DER.  Unless the client implementation is the same as the server
+     * implementation, there's a good chance that the result will not match
+     * what the client sent, so don't go creating any fatal errors if it
+     * doesn't match up. */
+    krb5plugin_preauth_request_body = 4,
+};
+typedef krb5_error_code
+(*preauth_get_entry_data_proc)(krb5_context,
+                              krb5_kdc_req *,
+                              struct _krb5_db_entry_new *,
+                              enum krb5plugin_preauth_entry_request_type,
+                              krb5_data **);
+
+/*
+ * A callback which will obtain the user's long-term AS key by prompting the
+ * user for the password, then salting it properly, and so on.  For the moment,
+ * it's identical to the get_as_key callback used inside of libkrb5, but we
+ * define a new typedef here instead of making the existing one public to
+ * isolate ourselves from potential future changes.
+ */
+typedef krb5_error_code
+(*preauth_get_as_key_proc)(krb5_context,
+                          krb5_principal,
+                          krb5_enctype,
+                          krb5_prompter_fct,
+                          void *prompter_data,
+                          krb5_data *salt,
+                          krb5_data *s2kparams,
+                          krb5_keyblock *as_key,
+                          void *gak_data);
+
+/*
+ * The function table / structure which a preauth module must export as
+ * "preauthentication0".  NOTE: replace "0" with "1" for the type and variable
+ * names if this gets picked up by upstream.  If the interfaces work correctly,
+ * future versions of the table will add either more callbacks or more
+ * arguments to callbacks, and in both cases we'll be able to wrap the v0
+ * functions.
+ */
+typedef struct krb5plugin_preauth_ftable_v0 {
+    /* Not-usually-visible name. */
+    char *name;
+
+    /* Pointer to zero-terminated lists of pa_types which this module can
+     * provide services for. */
+    krb5_preauthtype *client_pa_type_list;
+    krb5_preauthtype *server_pa_type_list;
+
+    /* Pointer to zero-terminated list of enc_types which this module claims
+     * to add support for. */
+    krb5_enctype *client_enctype_list;
+
+    /* Per-module initialization/cleanup.  The init function is called
+     * by libkrb5 when the module is loaded, and the fini function is
+     * called before the module is unloaded.  Both are optional and
+     * may be called multiple times in case the module is used in
+     * multiple contexts.*/
+    krb5_error_code (*client_init)(krb5_context, krb5_preauthtype, void **);
+    void (*client_fini)(krb5_context, krb5_preauthtype, void *);
+    /* A callback which returns flags indicating if the module is a "real" or
+     * an "info" mechanism, and so on.  This function is called for each entry
+     * in the client_pa_type_list. */
+    int (*client_flags)(krb5_context, krb5_preauthtype);
+    /* Clean up a client context.  Can be NULL. */
+    void (*client_cleanup)(krb5_context context, void *module_context,
+                          void **request_context);
+    /* Client function which processes server-supplied data in pa_data,
+     * returns created data in out_pa_data, storing any of its own state in
+     * client_context if data for the associated preauthentication type is
+     * needed.  It is also called after the AS-REP is received if the AS-REP
+     * includes preauthentication data of the associated type.
+     * NOTE! the encoded_previous_request will be NULL the first time this
+     * function is called, because it is expected to only ever contain the data
+     * obtained from a previous call to this function. */
+    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);
+    /* Client function which can attempt to use e-data in the error response to
+     * try to recover from the given error.  If this function is not NULL, and
+     * it stores data in out_pa_data which is different data from the contents
+     * of in_pa_data, then the client library will retransmit the request. */
+    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 *error,
+                                      krb5_pa_data *in_pa_data,
+                                      krb5_pa_data **out_pa_data);
+
+    /* Per-module initialization/cleanup.  The init function is called by the
+     * KDC when the module is loaded, and the fini function is called before
+     * the module is unloaded.  Both are optional. */
+    krb5_error_code (*server_init_proc)(krb5_context, krb5_preauthtype,
+                                       void **);
+    void (*server_fini_proc)(krb5_context, krb5_preauthtype, void *);
+    /* Return the flags which the KDC should use for this module.  This is a
+     * callback instead of a static value because the module may or may not
+     * wish to count itself as a hardware preauthentication module (in other
+     * words, the flags may be affected by the configuration, for example if a
+     * site administrator can force a particular preauthentication type to be
+     * supported using only hardware).  This function is called for each entry
+     * entry in the server_pa_type_list. */
+    int (*server_flags_proc)(krb5_context, krb5_preauthtype);
+    /* Get preauthentication data to send to the client as part of the "you
+     * need to use preauthentication" error.  The module doesn't need to
+     * actually provide data if the protocol doesn't require it, but it should
+     * return either zero or non-zero to control whether its padata type is
+     * included in the list which is sent back to the client.  Is not allowed
+     * to create a context because we have no guarantee that the client will
+     * ever call again (or that it will hit this server if it does), in which
+     * case a context might otherwise hang around forever. */
+    krb5_error_code (*server_edata_proc)(krb5_context, krb5_kdc_req *request,
+                                        struct _krb5_db_entry_new *client,
+                                        struct _krb5_db_entry_new *server,
+                                        preauth_get_entry_data_proc,
+                                        void *pa_module_context,
+                                        krb5_pa_data *data);
+    /* Verify preauthentication data sent by the client, setting the
+     * TKT_FLG_PRE_AUTH or TKT_FLG_HW_AUTH flag in the enc_tkt_reply's "flags"
+     * field as appropriate, and returning nonzero on failure.  Can create
+     * context data for consumption by the return_proc or freepa_proc below. */
+    krb5_error_code (*server_verify_proc)(krb5_context,
+                                         struct _krb5_db_entry_new *client,
+                                         krb5_data *req_pkt,
+                                         krb5_kdc_req *request,
+                                         krb5_enc_tkt_part *enc_tkt_reply,
+                                         krb5_pa_data *data,
+                                         preauth_get_entry_data_proc,
+                                         void *pa_module_context,
+                                         void **pa_request_context);
+    /* Generate preauthentication response data to send to the client as part
+     * of the AS-REP.  If it needs to override the key which is used to encrypt
+     * the response, it can do so.  The module is expected (but not required,
+     * if a freepa_proc is also provided) to free any context data it saved in
+     * "request_pa_context". */
+    krb5_error_code (*server_return_proc)(krb5_context, krb5_pa_data * padata,
+                                         struct _krb5_db_entry_new *client,
+                                         krb5_data *req_pkt,
+                                         krb5_kdc_req *request,
+                                         krb5_kdc_rep *reply,
+                                         struct _krb5_key_data *client_keys,
+                                         krb5_keyblock *encrypting_key,
+                                         krb5_pa_data **send_pa,
+                                         preauth_get_entry_data_proc,
+                                         void *pa_module_context,
+                                         void **pa_request_context);
+    /* Free up the server-side per-request context, in cases where
+     * server_return_proc() didn't or for whatever reason was not called.  Can
+     * be NULL. */
+    krb5_error_code (*server_freepa_reqcontext_proc)(krb5_context,
+                                                    void *pa_module_context,
+                                                    void **request_pa_context);
+} krb5plugin_preauth_ftable_v0;
+
+#endif /* KRB5_PREAUTH_PLUGIN_H_INCLUDED */
index 0a932518aeb90adb4dd0f689939f17d97f9ab272..cdf4870015296253baba9fce91a27607205e2655 100644 (file)
@@ -13,7 +13,7 @@ PROG_LIBPATH=-L$(TOPLIBD) $(KRB4_LIBPATH)
 KDB5_LIB_DEPS=$(DL_LIB) $(THREAD_LINKOPTS)
 PROG_RPATH=$(KRB5_LIBDIR)
 FAKEKA=@FAKEKA@
-DEFS=
+DEFS=-DLIBDIR=\"$(KRB5_LIBDIR)\"
 
 all:: krb5kdc rtest $(FAKEKA)
 
index 84cfe39ac52f554e6ded32b31196f3be8fc789e5..1d7c3bed7113e9eb504cd0fe4f5cb8d3497393f9 100644 (file)
@@ -94,7 +94,7 @@ dispatch(krb5_data *pkt, const krb5_fulladdr *from, krb5_data **response)
             * pointer.
             */
            if (!(retval = setup_server_realm(as_req->server))) {
-               retval = process_as_req(as_req, from, response);
+               retval = process_as_req(as_req, pkt, from, response);
            }
            krb5_free_kdc_req(kdc_context, as_req);
        }
index 6355e4bde41ae1c9e183521e3cdb4cfaf07cd60a..42c41f9acc7c6df6e08b2148c0ac1dab231a3120 100644 (file)
@@ -50,8 +50,8 @@ static krb5_error_code prepare_error_as (krb5_kdc_req *, int, krb5_data *,
 
 /*ARGSUSED*/
 krb5_error_code
-process_as_req(krb5_kdc_req *request, const krb5_fulladdr *from,
-              krb5_data **response)
+process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
+              const krb5_fulladdr *from, krb5_data **response)
 {
     krb5_db_entry client, server;
     krb5_kdc_rep reply;
@@ -78,6 +78,7 @@ process_as_req(krb5_kdc_req *request, const krb5_fulladdr *from,
     char ktypestr[128];
     char rep_etypestr[128];
     char fromstringbuf[70];
+    void *pa_context = NULL;
 
     ticket_reply.enc_part.ciphertext.data = 0;
     e_data.data = 0;
@@ -260,7 +261,8 @@ process_as_req(krb5_kdc_req *request, const krb5_fulladdr *from,
      * Check the preauthentication if it is there.
      */
     if (request->padata) {
-       errcode = check_padata(kdc_context, &client, request, &enc_tkt_reply);
+       errcode = check_padata(kdc_context, &client, req_pkt, request,
+                              &enc_tkt_reply, &pa_context);
        if (errcode) {
 #ifdef KRBCONF_KDC_MODIFIES_KDB
            /*
@@ -381,8 +383,8 @@ process_as_req(krb5_kdc_req *request, const krb5_fulladdr *from,
     reply_encpart.caddrs = enc_tkt_reply.caddrs;
 
     /* Fetch the padata info to be returned */
-    errcode = return_padata(kdc_context, &client, request, &reply, client_key,
-                           &encrypting_key);
+    errcode = return_padata(kdc_context, &client, req_pkt, request,
+                           &reply, client_key, &encrypting_key, &pa_context);
     if (errcode) {
        status = "KDC_RETURN_PADATA";
        goto errout;
@@ -427,6 +429,9 @@ process_as_req(krb5_kdc_req *request, const krb5_fulladdr *from,
 #endif /* KRBCONF_KDC_MODIFIES_KDB */
 
 errout:
+    if (pa_context)
+       free_padata_context(kdc_context, &pa_context);
+
     if (status) {
         char * emsg = 0;
        if (errcode) 
index cd74528adfeee058c51b071542fd49a0ec03414b..1b11de3026b24d2b98ed293623ca8cb5d5166746 100644 (file)
 #include <syslog.h>
 
 #include <assert.h>
+#include "../include/krb5/preauth_plugin.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
 
 /* XXX This is ugly and should be in a header file somewhere */
 #ifndef KRB5INT_DES_TYPES_DEFINED
@@ -72,44 +79,74 @@ extern int mit_des_is_weak_key (mit_des_cblock );
 
 typedef krb5_error_code (*verify_proc)
     (krb5_context, krb5_db_entry *client,
+                   krb5_data *req_pkt,
                    krb5_kdc_req *request,
-                   krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data);
+                   krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_module_context,
+                   void **pa_request_context);
 
 typedef krb5_error_code (*edata_proc)
     (krb5_context, krb5_kdc_req *request,
                    krb5_db_entry *client, krb5_db_entry *server,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_module_context,
                    krb5_pa_data *data);
 
 typedef krb5_error_code (*return_proc)
     (krb5_context, krb5_pa_data * padata, 
                    krb5_db_entry *client,
+                   krb5_data *req_pkt,
                    krb5_kdc_req *request, krb5_kdc_rep *reply,
                    krb5_key_data *client_key,
                    krb5_keyblock *encrypting_key,
-                   krb5_pa_data **send_pa);
+                   krb5_pa_data **send_pa,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_module_context,
+                   void **pa_request_context);
+
+typedef krb5_error_code (*freepa_proc)
+    (krb5_context, void *pa_module_context, void **pa_request_context);
+
+typedef krb5_error_code (*init_proc)
+    (krb5_context, krb5_preauthtype, void **);
+typedef void (*fini_proc)
+    (krb5_context, krb5_preauthtype, void *);
 
 typedef struct _krb5_preauth_systems {
-    char *     name;
+    const char *name;
     int                type;
     int                flags;
+    void       *pa_sys_context;
+    init_proc   init;
+    fini_proc   fini;
     edata_proc get_edata;
     verify_proc        verify_padata;
     return_proc return_padata;
+    freepa_proc free_pa_request_context;
 } krb5_preauth_systems;
 
 static krb5_error_code verify_enc_timestamp
     (krb5_context, krb5_db_entry *client,
+                   krb5_data *req_pkt,
                    krb5_kdc_req *request,
-                   krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data);
+                   krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_system_context,
+                   void **pa_request_context);
 
 static krb5_error_code get_etype_info
     (krb5_context, krb5_kdc_req *request,
                    krb5_db_entry *client, krb5_db_entry *server,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_system_context,
                    krb5_pa_data *data);
 static krb5_error_code
 get_etype_info2(krb5_context context, krb5_kdc_req *request,
-              krb5_db_entry *client, krb5_db_entry *server,
-                 krb5_pa_data *pa_data);
+               krb5_db_entry *client, krb5_db_entry *server,
+               preauth_get_entry_data_proc get_entry_data,
+               void *pa_system_context,
+               krb5_pa_data *pa_data);
 static krb5_error_code
 etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata, 
                         krb5_db_entry *client,
@@ -122,58 +159,75 @@ etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata,
 static krb5_error_code
 return_etype_info(krb5_context, krb5_pa_data * padata, 
                  krb5_db_entry *client,
+                 krb5_data *req_pkt,
                  krb5_kdc_req *request, krb5_kdc_rep *reply,
                  krb5_key_data *client_key,
                  krb5_keyblock *encrypting_key,
-                 krb5_pa_data **send_pa);
+                 krb5_pa_data **send_pa,
+                 preauth_get_entry_data_proc get_entry_data,
+                 void *pa_system_context,
+                 void **pa_request_context);
 
 static krb5_error_code
 return_etype_info2(krb5_context, krb5_pa_data * padata, 
                   krb5_db_entry *client,
+                  krb5_data *req_pkt,
                   krb5_kdc_req *request, krb5_kdc_rep *reply,
                   krb5_key_data *client_key,
                   krb5_keyblock *encrypting_key,
-                  krb5_pa_data **send_pa);
+                  krb5_pa_data **send_pa,
+                  preauth_get_entry_data_proc get_entry_data,
+                  void *pa_system_context,
+                  void **pa_request_context);
 
 static krb5_error_code return_pw_salt
     (krb5_context, krb5_pa_data * padata, 
                    krb5_db_entry *client,
+                   krb5_data *req_pkt,
                    krb5_kdc_req *request, krb5_kdc_rep *reply,
                    krb5_key_data *client_key,
                    krb5_keyblock *encrypting_key,
-                   krb5_pa_data **send_pa);
+                   krb5_pa_data **send_pa,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_system_context,
+                   void **pa_request_context);
 
 /* SAM preauth support */
 static krb5_error_code verify_sam_response
     (krb5_context, krb5_db_entry *client,
+                   krb5_data *req_pkt,
                    krb5_kdc_req *request,
-                   krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data);
+                   krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_module_context,
+                   void **pa_request_context);
 
 static krb5_error_code get_sam_edata
     (krb5_context, krb5_kdc_req *request,
                    krb5_db_entry *client, krb5_db_entry *server,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_module_context,
                    krb5_pa_data *data);
 static krb5_error_code return_sam_data
     (krb5_context, krb5_pa_data * padata, 
                    krb5_db_entry *client,
+                   krb5_data *req_pkt,
                    krb5_kdc_req *request, krb5_kdc_rep *reply,
                    krb5_key_data *client_key,
                    krb5_keyblock *encrypting_key,
-                   krb5_pa_data **send_pa);
-/*
- * Preauth property flags
- */
-#define PA_HARDWARE    0x00000001
-#define PA_REQUIRED    0x00000002
-#define PA_SUFFICIENT  0x00000004
-       /* Not really a padata, so don't include it in the etype list*/
-#define PA_PSEUDO      0x00000008 
+                   krb5_pa_data **send_pa,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_module_context,
+                   void **pa_request_context);
 
-static krb5_preauth_systems preauth_systems[] = {
+static krb5_preauth_systems static_preauth_systems[] = {
     {
        "timestamp",
         KRB5_PADATA_ENC_TIMESTAMP,
         0,
+       NULL,
+       NULL,
+       NULL,
         0,
        verify_enc_timestamp,
        0
@@ -182,6 +236,9 @@ static krb5_preauth_systems preauth_systems[] = {
        "etype-info",
        KRB5_PADATA_ETYPE_INFO,
        0,
+       NULL,
+       NULL,
+       NULL,
        get_etype_info,
        0,
        return_etype_info
@@ -190,6 +247,9 @@ static krb5_preauth_systems preauth_systems[] = {
        "etype-info2",
        KRB5_PADATA_ETYPE_INFO2,
        0,
+       NULL,
+       NULL,
+       NULL,
        get_etype_info2,
        0,
        return_etype_info2
@@ -198,6 +258,9 @@ static krb5_preauth_systems preauth_systems[] = {
        "pw-salt",
        KRB5_PADATA_PW_SALT,
        PA_PSEUDO,              /* Don't include this in the error list */
+       NULL,
+       NULL,
+       NULL,
        0, 
        0,
        return_pw_salt
@@ -206,6 +269,9 @@ static krb5_preauth_systems preauth_systems[] = {
        "sam-response",
        KRB5_PADATA_SAM_RESPONSE,
        0,
+       NULL,
+       NULL,
+       NULL,
        0,
        verify_sam_response,
        return_sam_data
@@ -214,6 +280,9 @@ static krb5_preauth_systems preauth_systems[] = {
        "sam-challenge",
        KRB5_PADATA_SAM_CHALLENGE,
        PA_HARDWARE,            /* causes get_preauth_hint_list to use this */
+       NULL,
+       NULL,
+       NULL,
        get_sam_edata,
        0,
        0
@@ -221,13 +290,361 @@ static krb5_preauth_systems preauth_systems[] = {
     { "[end]", -1,}
 };
 
-#define MAX_PREAUTH_SYSTEMS (sizeof(preauth_systems)/sizeof(preauth_systems[0]))
+static krb5_preauth_systems *preauth_systems;
+static int n_preauth_systems;
+static struct plugin_dir_handle preauth_plugins;
+
+krb5_error_code
+load_preauth_plugins(krb5_context context)
+{
+    struct errinfo err;
+    void **preauth_plugins_ftables;
+    struct krb5plugin_preauth_ftable_v0 *ftable;
+    int module_count, i, j, k;
+    krb5_preauthtype pa_type;
+    void *pa_sys_context;
+    init_proc server_init_proc;
+
+    /* Attempt to load all of the preauth plugins we can find. */
+    PLUGIN_DIR_INIT(&preauth_plugins);
+    if (PLUGIN_DIR_OPEN(&preauth_plugins) == 0) {
+       if (krb5int_open_plugin_dirs(objdirs, NULL,
+                                    &preauth_plugins, &err) != 0) {
+           return KRB5_PLUGIN_NO_HANDLE;
+       }
+    }
+
+    /* Get the method tables provided by the loaded plugins. */
+    memset(&err, 0, sizeof(err));
+    preauth_plugins_ftables = NULL;
+    if (krb5int_get_plugin_dir_data(&preauth_plugins, "preauthentication0",
+                                   &preauth_plugins_ftables, &err) != 0) {
+       return KRB5_PLUGIN_NO_HANDLE;
+    }
+
+    /* Count the valid modules. */
+    module_count = sizeof(static_preauth_systems)
+                  / sizeof(static_preauth_systems[0]);
+    if (preauth_plugins_ftables != NULL) {
+       for (i = 0; preauth_plugins_ftables[i] != NULL; i++) {
+           ftable = preauth_plugins_ftables[i];
+           if ((ftable->server_flags_proc == NULL) &&
+               (ftable->server_edata_proc == NULL) &&
+               (ftable->server_verify_proc == NULL) &&
+               (ftable->server_return_proc == NULL)) {
+               continue;
+           }
+           for (j = 0;
+                ftable->server_pa_type_list != NULL &&
+                ftable->server_pa_type_list[j] > 0;
+                j++) {
+               module_count++;
+           }
+       }
+    }
+
+    /* Build the complete list of supported preauthentication options, and
+     * leave room for a terminator entry. */
+    preauth_systems = malloc(sizeof(krb5_preauth_systems) * (module_count + 1));
+    if (preauth_systems == NULL) {
+       return ENOMEM;
+    }
+
+    /* Add the locally-supplied mechanisms to the dynamic list first. */
+    for (i = 0, k = 0;
+        i < sizeof(static_preauth_systems) / sizeof(static_preauth_systems[0]);
+        i++) {
+       if (static_preauth_systems[i].type == -1)
+           break;
+       preauth_systems[k] = static_preauth_systems[i];
+       /* Try to initialize the preauth system.  If it fails, we'll remove it
+        * from the list of systems we'll be using. */
+       pa_sys_context = NULL;
+       pa_type = static_preauth_systems[i].type;
+       server_init_proc = static_preauth_systems[i].init;
+       if ((server_init_proc != NULL) &&
+           ((*server_init_proc)(context, pa_type, &pa_sys_context) != 0)) {
+           memset(&preauth_systems[k], 0, sizeof(preauth_systems[k]));
+           continue;
+       }
+       preauth_systems[k].pa_sys_context = pa_sys_context;
+       k++;
+    }
+
+    /* Now add the dynamically-loaded mechanisms to the list. */
+    if (preauth_plugins_ftables != NULL) {
+       for (i = 0; preauth_plugins_ftables[i] != NULL; i++) {
+           ftable = preauth_plugins_ftables[i];
+           if ((ftable->server_flags_proc == NULL) &&
+               (ftable->server_edata_proc == NULL) &&
+               (ftable->server_verify_proc == NULL) &&
+               (ftable->server_return_proc == NULL)) {
+               continue;
+           }
+           for (j = 0;
+                ftable->server_pa_type_list != NULL &&
+                ftable->server_pa_type_list[j] > 0;
+                j++) {
+               /* Try to initialize the module.  If it fails, we'll remove it
+                * from the list of modules we'll be using. */
+               pa_sys_context = NULL;
+               server_init_proc = ftable->server_init_proc;
+               pa_type = ftable->server_pa_type_list[j];
+               if ((server_init_proc != NULL) &&
+                   ((*server_init_proc)(context, pa_type,
+                                        &pa_sys_context) != 0)) {
+                   memset(&preauth_systems[k], 0, sizeof(preauth_systems[k]));
+                   continue;
+               }
+               preauth_systems[k].name = ftable->name;
+               pa_type = ftable->server_pa_type_list[j];
+               preauth_systems[k].type = pa_type;
+               preauth_systems[k].flags = ftable->server_flags_proc(context,
+                                                                    pa_type);
+               preauth_systems[k].pa_sys_context = pa_sys_context;
+               preauth_systems[k].init = server_init_proc;
+               preauth_systems[k].fini = ftable->server_fini_proc;
+               preauth_systems[k].get_edata = ftable->server_edata_proc;
+               preauth_systems[k].verify_padata = ftable->server_verify_proc;
+               preauth_systems[k].return_padata = ftable->server_return_proc;
+               preauth_systems[k].free_pa_request_context =
+                   ftable->server_freepa_reqcontext_proc;
+               k++;
+           }
+       }
+    }
+    n_preauth_systems = k;
+    /* Add the end-of-list marker. */
+    preauth_systems[k].name = "[end]";
+    preauth_systems[k].type = -1;
+}
+
+krb5_error_code
+unload_preauth_plugins(krb5_context context)
+{
+    int i;
+    struct errinfo err;
+    if (preauth_systems != NULL) {
+       for (i = 0; i < n_preauth_systems; i++) {
+           if (preauth_systems[i].fini != NULL) {
+               (*preauth_systems[i].fini)(context,
+                                          preauth_systems[i].type,
+                                          preauth_systems[i].pa_sys_context);
+           }
+           memset(&preauth_systems[i], 0, sizeof(preauth_systems[i]));
+       }
+       free(preauth_systems);
+       preauth_systems = NULL;
+       n_preauth_systems = 0;
+       krb5int_close_plugin_dirs(&preauth_plugins);
+    }
+    return 0;
+}
+
+/*
+ * The make_padata_context() function creates a space for storing any context
+ * information which will be needed by return_padata() later.  Each preauth
+ * type gets a context storage location of its own.
+ */
+struct request_pa_context {
+    int n_contexts;
+    struct {
+       krb5_preauth_systems *pa_system;
+       void *pa_context;
+    } *contexts;
+};
+
+static krb5_error_code
+make_padata_context(krb5_context context, void **padata_context)
+{
+    int i;
+    struct request_pa_context *ret;
+
+    ret = malloc(sizeof(*ret));
+    if (ret == NULL) {
+       return ENOMEM;
+    }
+
+    ret->n_contexts = n_preauth_systems;
+    ret->contexts = malloc(sizeof(ret->contexts[0]) * ret->n_contexts);
+    if (ret->contexts == NULL) {
+       free(ret);
+       return ENOMEM;
+    }
+
+    memset(ret->contexts, 0, sizeof(ret->contexts[0]) * ret->n_contexts);
+
+    for (i = 0; i < ret->n_contexts; i++) {
+       ret->contexts[i].pa_system = &preauth_systems[i];
+       ret->contexts[i].pa_context = NULL;
+    }
+
+    *padata_context = ret;
+
+    return 0;
+}
+
+/*
+ * The free_padata_context function frees any context information pointers
+ * which the check_padata() function created but which weren't already cleaned
+ * up by return_padata().
+ */
+krb5_error_code
+free_padata_context(krb5_context kcontext, void **padata_context)
+{
+    struct request_pa_context *context;
+    krb5_preauth_systems *preauth_system;
+    void **pctx, *mctx;
+    int i;
+
+    if (padata_context == NULL)
+       return 0;
+
+    context = *padata_context;
+
+    for (i = 0; i < context->n_contexts; i++) {
+       if (context->contexts[i].pa_context != NULL) {
+           preauth_system = context->contexts[i].pa_system;
+           mctx = preauth_system->pa_sys_context;
+           if (preauth_system->free_pa_request_context != NULL) {
+               pctx = &context->contexts[i].pa_context;
+               (*preauth_system->free_pa_request_context)(kcontext, mctx,
+                                                          pctx);
+           }
+           context->contexts[i].pa_context = NULL;
+       }
+    }
+
+    free(context->contexts);
+    free(context);
+
+    return 0;
+}
+
+/* Retrieve a specified tl_data item from the given entry, and return its
+ * contents in a new krb5_data, which must be freed by the caller. */
+static krb5_error_code
+get_entry_tl_data(krb5_context context, krb5_db_entry *entry,
+                 krb5_int16 tl_data_type, krb5_data **result)
+{
+    krb5_tl_data *tl;
+    for (tl = entry->tl_data; tl != NULL; tl = tl->tl_data_next) {
+       if (tl->tl_data_type == tl_data_type) {
+           *result = malloc(sizeof(krb5_data));
+           if (*result == NULL) {
+               return ENOMEM;
+           }
+           (*result)->magic = KV5M_DATA;
+           (*result)->data = malloc(tl->tl_data_length);
+           if ((*result)->data == NULL) {
+               free(*result);
+               *result = NULL;
+               return ENOMEM;
+           }
+           memcpy((*result)->data, tl->tl_data_contents, tl->tl_data_length);
+           return 0;
+       }
+    }
+    return ENOENT;
+}
+
+/*
+ * Retrieve a specific piece of information pertaining to the entry or the
+ * request and return it in a new krb5_data item which the caller must free.
+ *
+ * This may require massaging data into a contrived format, but it will
+ * hopefully keep us from having to reveal library-internal functions to
+ * modules.
+ */
+static krb5_error_code
+get_entry_data(krb5_context context,
+              krb5_kdc_req *request, krb5_db_entry *entry,
+              enum krb5plugin_preauth_entry_request_type type,
+              krb5_data **result)
+{
+    int i, k;
+    krb5_tl_data *tl;
+    krb5_data *ret;
+    krb5_deltat *delta;
+    krb5_keyblock *keys;
+    krb5_key_data *entry_key;
+
+    switch (type) {
+    case krb5plugin_preauth_entry_request_certificate:
+       return get_entry_tl_data(context, entry,
+                                KRB5_TL_USER_CERTIFICATE, result);
+       break;
+    case krb5plugin_preauth_entry_max_time_skew:
+       ret = malloc(sizeof(krb5_data));
+       if (ret == NULL)
+           return ENOMEM;
+       delta = malloc(sizeof(krb5_deltat));
+       if (delta == NULL) {
+           free(ret);
+           return ENOMEM;
+       }
+       *delta = context->clockskew;
+       ret->data = (char *) delta;
+       ret->length = sizeof(*delta);
+       *result = ret;
+       return 0;
+       break;
+    case krb5plugin_preauth_keys:
+       ret = malloc(sizeof(krb5_data));
+       if (ret == NULL)
+           return ENOMEM;
+       keys = malloc(sizeof(krb5_keyblock) * (request->nktypes + 1));
+       if (keys == NULL) {
+           free(ret);
+           return ENOMEM;
+       }
+       ret->data = (char *) keys;
+       ret->length = sizeof(krb5_keyblock) * (request->nktypes + 1);
+       memset(ret->data, 0, ret->length);
+       k = 0;
+       for (i = 0; i < request->nktypes; i++) {
+           entry_key = NULL;
+           if (krb5_dbe_find_enctype(context, entry, request->ktype[i],
+                                     -1, 0, &entry_key) != 0)
+               continue;
+           if (krb5_dbekd_decrypt_key_data(context, &master_keyblock,
+                                           entry_key, &keys[k], NULL) != 0) {
+               if (keys[k].contents != NULL)
+                   krb5_free_keyblock_contents(context, &keys[k]);
+               memset(&keys[k], 0, sizeof(keys[k]));
+               continue;
+           }
+           k++;
+       }
+       if (k > 0) {
+           *result = ret;
+           return 0;
+       } else {
+           free(keys);
+           free(ret);
+       }
+       break;
+    case krb5plugin_preauth_request_body:
+       ret = NULL;
+       encode_krb5_kdc_req_body(request, &ret);
+       if (ret != NULL) {
+           *result = ret;
+           return 0;
+       }
+       return ASN1_PARSE_ERROR;
+       break;
+    default:
+       break;
+    }
+    return ENOENT;
+}
 
 static krb5_error_code
 find_pa_system(int type, krb5_preauth_systems **preauth)
 {
-    krb5_preauth_systems       *ap = preauth_systems;
-    
+    krb5_preauth_systems *ap;
+
+    ap = preauth_systems ? preauth_systems : static_preauth_systems;
     while ((ap->type != -1) && (ap->type != type))
        ap++;
     if (ap->type == -1)
@@ -236,6 +653,111 @@ find_pa_system(int type, krb5_preauth_systems **preauth)
     return 0;
 } 
 
+static krb5_error_code
+find_pa_context(krb5_preauth_systems *pa_sys,
+               struct request_pa_context *context,
+               void ***pa_context)
+{
+    krb5_preauth_systems *preauth_system;
+    int i;
+
+    *pa_context = 0;
+
+    if (context == NULL)
+       return KRB5KRB_ERR_GENERIC;
+
+    for (i = 0; i < context->n_contexts; i++) {
+       if (context->contexts[i].pa_system == pa_sys) {
+           *pa_context = &context->contexts[i].pa_context;
+           return 0;
+       }
+    }
+
+    return KRB5KRB_ERR_GENERIC;
+}
+
+/*
+ * Create a list of indices into the preauth_systems array, sorted by order of
+ * preference.
+ */
+static krb5_boolean
+pa_list_includes(krb5_pa_data **pa_data, krb5_preauthtype pa_type)
+{
+    while (*pa_data != NULL) {
+       if ((*pa_data)->pa_type == pa_type)
+           return TRUE;
+       pa_data++;
+    }
+    return FALSE;
+}
+static void
+sort_pa_order(krb5_context context, krb5_kdc_req *request, int *pa_order)
+{
+    int i, j, k, n_repliers, n_key_replacers;
+
+    /* First, set up the default order. */
+    i = 0;
+    for (j = 0; j < n_preauth_systems; j++) {
+        if (preauth_systems[j].return_padata != NULL)
+           pa_order[i++] = j;
+    }
+    n_repliers = i;
+    pa_order[n_repliers] = -1;
+
+    /* Reorder so that PA_REPLACES_KEY modules are listed first. */
+    for (i = 0; i < n_repliers; i++) {
+       /* If this module replaces the key, then it's okay to leave it where it
+        * is in the order. */
+       if (preauth_systems[pa_order[i]].flags & PA_REPLACES_KEY)
+           continue;
+       /* If not, search for a module which does, and swap in the first one we
+        * find. */
+        for (j = i + 1; j < n_repliers; j++) {
+           if (preauth_systems[pa_order[j]].flags & PA_REPLACES_KEY) {
+                k = pa_order[j];
+               pa_order[j] = pa_order[i];
+               pa_order[i] = k;
+               break;
+           }
+        }
+    }
+
+    /* Now sort just the modules which replace the key, placing those which
+     * handle the pa_data types provided by the client ahead of the others. */
+    for (i = 0; preauth_systems[pa_order[i]].flags & PA_REPLACES_KEY; i++) {
+       continue;
+    }
+    n_key_replacers = i;
+    for (i = 0; i < n_key_replacers; i++) {
+       if (pa_list_includes(request->padata,
+                            preauth_systems[pa_order[i]].type))
+           continue;
+        for (j = i + 1; j < n_key_replacers; j++) {
+           if (pa_list_includes(request->padata,
+                                preauth_systems[pa_order[j]].type)) {
+                k = pa_order[j];
+               pa_order[j] = pa_order[i];
+               pa_order[i] = k;
+               break;
+           }
+        }
+    }
+#ifdef DEBUG
+    krb5_klog_syslog(LOG_DEBUG, "original preauth mechanism list:");
+    for (i = 0; i < n_preauth_systems; i++) {
+       if (preauth_systems[i].return_padata != NULL)
+            krb5_klog_syslog(LOG_DEBUG, "... %s(%d)", preauth_systems[i].name,
+                            preauth_systems[i].type);
+    }
+    krb5_klog_syslog(LOG_DEBUG, "sorted preauth mechanism list:");
+    for (i = 0; pa_order[i] != -1; i++) {
+        krb5_klog_syslog(LOG_DEBUG, "... %s(%d)",
+                        preauth_systems[pa_order[i]].name,
+                        preauth_systems[pa_order[i]].type);
+    }
+#endif
+}
+
 const char *missing_required_preauth(krb5_db_entry *client,
                                     krb5_db_entry *server,
                                     krb5_enc_tkt_part *enc_tkt_reply)
@@ -287,10 +809,10 @@ void get_preauth_hint_list(krb5_kdc_req *request, krb5_db_entry *client,
     e_data->data = 0;
     
     hw_only = isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH);
-    pa_data = malloc(sizeof(krb5_pa_data *) * (MAX_PREAUTH_SYSTEMS+1));
+    pa_data = malloc(sizeof(krb5_pa_data *) * (n_preauth_systems+1));
     if (pa_data == 0)
        return;
-    memset(pa_data, 0, sizeof(krb5_pa_data *) * (MAX_PREAUTH_SYSTEMS+1));
+    memset(pa_data, 0, sizeof(krb5_pa_data *) * (n_preauth_systems+1));
     pa = pa_data;
 
     for (ap = preauth_systems; ap->type != -1; ap++) {
@@ -305,7 +827,8 @@ void get_preauth_hint_list(krb5_kdc_req *request, krb5_db_entry *client,
        (*pa)->magic = KV5M_PA_DATA;
        (*pa)->pa_type = ap->type;
        if (ap->get_edata) {
-         retval = (ap->get_edata)(kdc_context, request, client, server, *pa);
+         retval = (ap->get_edata)(kdc_context, request, client, server,
+                                  get_entry_data, ap->pa_sys_context, *pa);
          if (retval) {
            /* just failed on this type, continue */
            free(*pa);
@@ -335,23 +858,29 @@ errout:
 /*
  * This routine is called to verify the preauthentication information
  * for a V5 request.
- *     
+ *
  * Returns 0 if the pre-authentication is valid, non-zero to indicate
  * an error code of some sort.
  */
 
 krb5_error_code
-check_padata (krb5_context context, krb5_db_entry *client,
-             krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply)
+check_padata (krb5_context context, krb5_db_entry *client, krb5_data *req_pkt,
+             krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply,
+             void **padata_context)
 {
     krb5_error_code retval = 0;
     krb5_pa_data **padata;
     krb5_preauth_systems *pa_sys;
-    int                        pa_ok = 0, pa_found = 0;
+    void **pa_context;
+    int        pa_ok = 0, pa_found = 0;
 
     if (request->padata == 0)
        return 0;
 
+    if (make_padata_context(context, padata_context) != 0) {
+       return KRB5KRB_ERR_GENERIC;
+    }
+
 #ifdef DEBUG
     krb5_klog_syslog (LOG_DEBUG, "checking padata");
 #endif
@@ -361,14 +890,18 @@ check_padata (krb5_context context, krb5_db_entry *client,
 #endif
        if (find_pa_system((*padata)->pa_type, &pa_sys))
            continue;
+       if (find_pa_context(pa_sys, *padata_context, &pa_context))
+           continue;
 #ifdef DEBUG
        krb5_klog_syslog (LOG_DEBUG, ".. pa_type %s", pa_sys->name);
 #endif
        if (pa_sys->verify_padata == 0)
            continue;
        pa_found++;
-       retval = pa_sys->verify_padata(context, client, request,
-                                      enc_tkt_reply, *padata);
+       retval = pa_sys->verify_padata(context, client, req_pkt, request,
+                                      enc_tkt_reply, *padata,
+                                      get_entry_data, pa_sys->pa_sys_context,
+                                      pa_context);
        if (retval) {
            char * emsg = krb5_get_error_message (context, retval);
            krb5_klog_syslog (LOG_INFO, "preauth (%s) verify failure: %s",
@@ -404,9 +937,30 @@ check_padata (krb5_context context, krb5_db_entry *client,
 /* The following switch statement allows us
  * to return some preauth system errors back to the client.
  */
-        switch(retval) {
-       case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+    switch(retval) {
+    case KRB5KRB_AP_ERR_BAD_INTEGRITY:
     case KRB5KRB_AP_ERR_SKEW:
+    case KRB5KDC_ERR_ETYPE_NOSUPP:
+    /* rfc 4556 */
+    case KRB5KDC_ERR_CLIENT_NOT_TRUSTED:
+    case KRB5KDC_ERR_INVALID_SIG:
+    case KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED:
+    case KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE:
+    case KRB5KDC_ERR_INVALID_CERTIFICATE:
+    case KRB5KDC_ERR_REVOKED_CERTIFICATE:
+    case KRB5KDC_ERR_REVOCATION_STATUS_UNKNOWN:
+    case KRB5KDC_ERR_CLIENT_NAME_MISMATCH:
+    case KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE:
+    case KRB5KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED:
+    case KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED:
+    case KRB5KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED:
+    case KRB5KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED:
+    /* earlier drafts of what became rfc 4556 */
+    case KRB5KDC_ERR_CERTIFICATE_MISMATCH:
+    case KRB5KDC_ERR_KDC_NOT_TRUSTED:
+    case KRB5KDC_ERR_REVOCATION_STATUS_UNAVAILABLE:
+    /* This value is shared with KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED. */
+    /* case KRB5KDC_ERR_KEY_TOO_WEAK: */
        return retval;
     default:
        return KRB5KDC_ERR_PREAUTH_FAILED;
@@ -418,9 +972,10 @@ check_padata (krb5_context context, krb5_db_entry *client,
  * structures which should be returned by the KDC to the client
  */
 krb5_error_code
-return_padata(krb5_context context, krb5_db_entry *client,
+return_padata(krb5_context context, krb5_db_entry *client, krb5_data *req_pkt,
              krb5_kdc_req *request, krb5_kdc_rep *reply,
-             krb5_key_data *client_key, krb5_keyblock *encrypting_key)
+             krb5_key_data *client_key, krb5_keyblock *encrypting_key,
+             void **padata_context)
 {
     krb5_error_code            retval;
     krb5_pa_data **            padata;
@@ -428,7 +983,12 @@ return_padata(krb5_context context, krb5_db_entry *client,
     krb5_pa_data **            send_pa;
     krb5_pa_data *             pa = 0;
     krb5_preauth_systems *     ap;
+    int *                      pa_order;
+    int *                      pa_type;
     int                        size = 0;
+    void **                    pa_context;
+    krb5_boolean               key_modified;
+    krb5_keyblock              original_key;
 
     for (ap = preauth_systems; ap->type != -1; ap++) {
        if (ap->return_padata)
@@ -437,13 +997,42 @@ return_padata(krb5_context context, krb5_db_entry *client,
 
     if ((send_pa_list = malloc((size+1) * sizeof(krb5_pa_data *))) == NULL)
        return ENOMEM;
+    if ((pa_order = malloc((size+1) * sizeof(int))) == NULL) {
+       free(send_pa_list);
+       return ENOMEM;
+    }
+    sort_pa_order(context, request, pa_order);
+
+    retval = krb5_copy_keyblock_contents(context, encrypting_key,
+                                        &original_key);
+    if (retval) {
+       free(send_pa_list);
+       free(pa_order);
+       return retval;
+    }
+    key_modified = FALSE;
 
     send_pa = send_pa_list;
     *send_pa = 0;
-    
-    for (ap = preauth_systems; ap->type != -1; ap++) {
+
+    for (pa_type = pa_order; *pa_type != -1; pa_type++) {
+       ap = &preauth_systems[*pa_type];
+        if (!key_modified)
+           if (original_key.enctype != encrypting_key->enctype)
+                key_modified = TRUE;
+        if (!key_modified)
+           if (original_key.length != encrypting_key->length)
+                key_modified = TRUE;
+        if (!key_modified)
+           if (memcmp(original_key.contents, encrypting_key->contents,
+                      original_key.length) != 0)
+                key_modified = TRUE;
+       if (key_modified && (ap->flags & PA_REPLACES_KEY))
+           continue;
        if (ap->return_padata == 0)
            continue;
+       if (find_pa_context(ap, *padata_context, &pa_context))
+           continue;
        pa = 0;
        if (request->padata) {
            for (padata = request->padata; *padata; padata++) {
@@ -453,9 +1042,12 @@ return_padata(krb5_context context, krb5_db_entry *client,
                }
            }
        }
-       if ((retval = ap->return_padata(context, pa, client, request, reply,
-                                       client_key, encrypting_key, send_pa)))
+       if ((retval = ap->return_padata(context, pa, client, req_pkt, request, reply,
+                                       client_key, encrypting_key, send_pa,
+                                       get_entry_data, ap->pa_sys_context,
+                                       pa_context))) {
            goto cleanup;
+       }
 
        if (*send_pa)
            send_pa++;
@@ -470,6 +1062,8 @@ return_padata(krb5_context context, krb5_db_entry *client,
     }
     
 cleanup:
+    krb5_free_keyblock_contents(context, &original_key);
+    free(pa_order);
     if (send_pa_list)
        krb5_free_pa_data(context, send_pa_list);
     return (retval);
@@ -508,8 +1102,12 @@ request_contains_enctype (krb5_context context,  const krb5_kdc_req *request,
 
 static krb5_error_code
 verify_enc_timestamp(krb5_context context, krb5_db_entry *client,
+                    krb5_data *req_pkt,
                     krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply,
-                    krb5_pa_data *pa)
+                    krb5_pa_data *pa,
+                    preauth_get_entry_data_proc get_entry_data,
+                    void *pa_system_context,
+                    void **pa_request_context)
 {
     krb5_pa_enc_ts *           pa_enc = 0;
     krb5_error_code            retval;
@@ -749,6 +1347,8 @@ cleanup:
 static krb5_error_code
 get_etype_info(krb5_context context, krb5_kdc_req *request,
               krb5_db_entry *client, krb5_db_entry *server,
+              preauth_get_entry_data_proc get_entry_data,
+              void *pa_system_context,
               krb5_pa_data *pa_data)
 {
   int i;
@@ -764,6 +1364,8 @@ get_etype_info(krb5_context context, krb5_kdc_req *request,
 static krb5_error_code
 get_etype_info2(krb5_context context, krb5_kdc_req *request,
               krb5_db_entry *client, krb5_db_entry *server,
+              preauth_get_entry_data_proc get_entry_data,
+              void *pa_system_context,
               krb5_pa_data *pa_data)
 {
     return etype_info_helper( context, request, client, server, pa_data, 1);
@@ -849,10 +1451,14 @@ etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata,
 static krb5_error_code
 return_etype_info2(krb5_context context, krb5_pa_data * padata, 
                   krb5_db_entry *client,
+                  krb5_data *req_pkt,
                   krb5_kdc_req *request, krb5_kdc_rep *reply,
                   krb5_key_data *client_key,
                   krb5_keyblock *encrypting_key,
-                  krb5_pa_data **send_pa)
+                  krb5_pa_data **send_pa,
+                  preauth_get_entry_data_proc get_entry_data,
+                  void *pa_system_context,
+                  void **pa_request_context)
 {
     return etype_info_as_rep_helper(context, padata, client, request, reply,
                                    client_key, encrypting_key, send_pa, 1);
@@ -862,10 +1468,14 @@ return_etype_info2(krb5_context context, krb5_pa_data * padata,
 static krb5_error_code
 return_etype_info(krb5_context context, krb5_pa_data * padata, 
                  krb5_db_entry *client,
+                 krb5_data *req_pkt,
                  krb5_kdc_req *request, krb5_kdc_rep *reply,
                  krb5_key_data *client_key,
                  krb5_keyblock *encrypting_key,
-                 krb5_pa_data **send_pa)
+                 krb5_pa_data **send_pa,
+                 preauth_get_entry_data_proc get_entry_data,
+                 void *pa_system_context,
+                 void **pa_request_context)
 {
     return etype_info_as_rep_helper(context, padata, client, request, reply,
                                    client_key, encrypting_key, send_pa, 0);
@@ -873,9 +1483,12 @@ return_etype_info(krb5_context context, krb5_pa_data * padata,
 
 static krb5_error_code
 return_pw_salt(krb5_context context, krb5_pa_data *in_padata,
-              krb5_db_entry *client, krb5_kdc_req *request,
+              krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request,
               krb5_kdc_rep *reply, krb5_key_data *client_key,
-              krb5_keyblock *encrypting_key, krb5_pa_data **send_pa)
+              krb5_keyblock *encrypting_key, krb5_pa_data **send_pa,
+              preauth_get_entry_data_proc get_entry_data,
+              void *pa_system_context,
+              void **pa_request_context)
 {
     krb5_error_code    retval;
     krb5_pa_data *     padata;
@@ -960,9 +1573,12 @@ cleanup:
 
 static krb5_error_code
 return_sam_data(krb5_context context, krb5_pa_data *in_padata,
-               krb5_db_entry *client, krb5_kdc_req *request,
+               krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request,
                krb5_kdc_rep *reply, krb5_key_data *client_key,
-               krb5_keyblock *encrypting_key, krb5_pa_data **send_pa)
+               krb5_keyblock *encrypting_key, krb5_pa_data **send_pa,
+               preauth_get_entry_data_proc get_entry_data,
+               void *pa_system_context,
+               void **pa_request_context)
 {
     krb5_error_code    retval;
     krb5_data          scratch;
@@ -1101,7 +1717,8 @@ static struct {
 static krb5_error_code
 get_sam_edata(krb5_context context, krb5_kdc_req *request,
              krb5_db_entry *client, krb5_db_entry *server,
-             krb5_pa_data *pa_data)
+             preauth_get_entry_data_proc get_entry_data,
+             void *pa_system_context, krb5_pa_data *pa_data)
 {
     krb5_error_code            retval;
     krb5_sam_challenge         sc;
@@ -1472,8 +2089,12 @@ cleanup:
 
 static krb5_error_code
 verify_sam_response(krb5_context context, krb5_db_entry *client,
+                   krb5_data *req_pkt,
                    krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply,
-                   krb5_pa_data *pa)
+                   krb5_pa_data *pa,
+                   preauth_get_entry_data_proc get_entry_data,
+                   void *pa_system_context,
+                   void **pa_request_context)
 {
     krb5_error_code            retval;
     krb5_data                  scratch;
index b15d5d79c37fcbf9593e0fbe8e35a800105008f9..21f7e88f3324fa6e57eefe4d8a4adfca80c58ec8 100644 (file)
@@ -107,7 +107,7 @@ void
 rep_etypes2str(char *s, size_t len, krb5_kdc_rep *rep);
 
 /* do_as_req.c */
-krb5_error_code process_as_req (krb5_kdc_req *,
+krb5_error_code process_as_req (krb5_kdc_req *, krb5_data *,
                                          const krb5_fulladdr *,
                                          krb5_data ** );
 
@@ -146,15 +146,23 @@ void get_preauth_hint_list (krb5_kdc_req * request,
                                      krb5_db_entry *client,
                                      krb5_db_entry *server,
                                      krb5_data *e_data);
+krb5_error_code load_preauth_plugins(krb5_context context);
+krb5_error_code unload_preauth_plugins(krb5_context context);
+
 krb5_error_code check_padata
-    (krb5_context context, krb5_db_entry *client,
-              krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply);
+    (krb5_context context, krb5_db_entry *client, krb5_data *req_pkt,
+              krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply,
+              void **padata_context);
     
 krb5_error_code return_padata
     (krb5_context context, krb5_db_entry *client,
-              krb5_kdc_req *request, krb5_kdc_rep *reply,
-              krb5_key_data *client_key, krb5_keyblock *encrypting_key);
+              krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply,
+              krb5_key_data *client_key, krb5_keyblock *encrypting_key,
+              void **padata_context);
     
+krb5_error_code free_padata_context
+    (krb5_context context, void **padata_context);
+
 /* replay.c */
 krb5_boolean kdc_check_lookaside (krb5_data *, krb5_data **);
 void kdc_insert_lookaside (krb5_data *, krb5_data *);
@@ -191,4 +199,8 @@ void enable_v4_crossrealm(char *);
   ((X) == ADDRTYPE_INET ? AF_INET : -1)
 #endif
 
+/* RFC 4120: KRB5KDC_ERR_KEY_TOO_WEAK
+ * RFC 4556: KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED */
+#define KRB5KDC_ERR_KEY_TOO_WEAK KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED
+
 #endif /* __KRB5_KDC_UTIL__ */
index c9b62a843a71cdbfaebe440bbefd4e6f90219e07..2d9aaf9491038a865b2876cab8b5196e3d5d7dc1 100644 (file)
@@ -711,6 +711,8 @@ int main(int argc, char **argv)
 
     setup_signal_handlers();
 
+    load_preauth_plugins(kcontext);
+
     retval = setup_sam();
     if (retval) {
        com_err(argv[0], retval, "while initializing SAM");
@@ -738,6 +740,7 @@ int main(int argc, char **argv)
        errout++;
     }
     krb5_klog_syslog(LOG_INFO, "shutting down");
+    unload_preauth_plugins(kcontext);
     krb5_klog_close(kdc_context);
     finish_realms(argv[0]);
     if (kdc_realmlist) 
index 918f351c2b4bed536aeece807487a5342c8633bc..92e45ad61645dcd8730d69f399a3d92497ea19b5 100644 (file)
@@ -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"
index 4cbc4b9b05a33db0244d54b1f302cd8d14ed9c8f..aceca17e71c7c72fcb21c8015d259e5f410aea1b 100644 (file)
@@ -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
index 462dc7c823982b57b6ebf7a765433be842e0aec4..53042fb2cab787e004fef9fcbc8d4c84a1020453 100644 (file)
@@ -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;
 
@@ -563,6 +574,11 @@ krb5_get_in_tkt(krb5_context context,
                retval = decode_krb5_padata_sequence(&err_reply->e_data,
                                                     &preauth_to_use);
                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;
@@ -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)
index 8e4ce8c3b3a4fbc041a3c252bd2f4232bc46ec48..46c3068eee4b64265180b0a5fa79383f71dccb52 100644 (file)
@@ -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;
index e146c3d3a25d7cb0258d093d0de098139423f134..c218389c7094805a6456c60ffa7493b6cd01b97f 100644 (file)
  */
 
 #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);
index 893355ef93d7ae4f9964dd1f51d0d37ca9254ee9..bc7e007ff7d6dc191e4105f14fbab500a0afb9fc 100644 (file)
@@ -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
diff --git a/src/plugins/preauth/cksum_body/Makefile.in b/src/plugins/preauth/cksum_body/Makefile.in
new file mode 100644 (file)
index 0000000..ddac24d
--- /dev/null
@@ -0,0 +1,42 @@
+thisconfigdir=.
+myfulldir=plugins/preauth/cksum_body
+mydir=.
+BUILDTOP=$(REL)..$(S)..$(S)..
+KRB5_RUN_ENV = @KRB5_RUN_ENV@
+KRB5_CONFIG_SETUP = KRB5_CONFIG=$(SRCTOP)/config-files/krb5.conf ; export KRB5_CONFIG ;
+PROG_LIBPATH=-L$(TOPLIBD)
+PROG_RPATH=$(KRB5_LIBDIR)
+MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR)
+DEFS=@DEFS@
+
+LOCALINCLUDES = -I../../../include/krb5
+
+LIBBASE=cksum_body
+LIBMAJOR=0
+LIBMINOR=0
+SO_EXT=.so
+RELDIR=../plugins/preauth/cksum_body
+# Depends on libk5crypto and libkrb5
+SHLIB_EXPDEPS = \
+       $(TOPLIBD)/libk5crypto$(SHLIBEXT) \
+       $(TOPLIBD)/libkrb5$(SHLIBEXT)
+SHLIB_EXPLIBS= -lkrb5 -lcom_err -lk5crypto $(SUPPORT_LIB) $(LIBS)
+
+SHLIB_DIRS=-L$(TOPLIBD)
+SHLIB_RDIRS=$(KRB5_LIBDIR)
+STOBJLISTS=OBJS.ST
+STLIBOBJS=src/cksum_body.o
+
+SRCS= $(srcdir)/src/cksum_body.c
+
+all-unix:: $(LIBBASE)$(SO_EXT)
+install-unix:: install-libs
+clean-unix:: clean-libs clean-libobjs
+
+clean::
+       $(RM) lib$(LIBBASE)$(SO_EXT)
+
+@libnover_frag@
+@libobj_frag@
+
+# +++ Dependency line eater +++
diff --git a/src/plugins/preauth/cksum_body/cksum_body.exports b/src/plugins/preauth/cksum_body/cksum_body.exports
new file mode 100644 (file)
index 0000000..ff5e3f1
--- /dev/null
@@ -0,0 +1 @@
+preauthentication0
diff --git a/src/plugins/preauth/cksum_body/configure.in b/src/plugins/preauth/cksum_body/configure.in
new file mode 100644 (file)
index 0000000..868dfd5
--- /dev/null
@@ -0,0 +1,14 @@
+K5_AC_INIT(configure.in)
+enable_shared=yes
+build_dynobj=yes
+CONFIG_RULES
+
+AC_CHECK_HEADERS(errno.h string.h)
+
+KRB5_RUN_FLAGS
+dnl The following is for check...
+KRB5_BUILD_PROGRAM
+KRB5_BUILD_LIBOBJS
+KRB5_BUILD_LIBRARY_WITH_DEPS
+AC_CONFIG_HEADERS(config.h)
+V5_AC_OUTPUT_MAKEFILE
diff --git a/src/plugins/preauth/cksum_body/src/cksum_body.c b/src/plugins/preauth/cksum_body/src/cksum_body.c
new file mode 100644 (file)
index 0000000..ff4cc84
--- /dev/null
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2006 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Red Hat, Inc., nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Checksum the request body with the user's long-term key.
+ *
+ * The e-data from the KDC is a list of network-byte-order 32-bit integers
+ * listing key types which the KDC has for the user.
+ *
+ * The client uses one of these key types to generate a checksum over the body
+ * of the request, and includes the checksum in the AS-REQ as preauthentication
+ * data.
+ *
+ * The AS-REP carries no preauthentication data for this scheme.
+ */
+
+#ident "$Id$"
+
+#ifdef HAVE_CONFIG_H
+#include "../config.h"
+#endif
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include <arpa/inet.h>
+#include <stdio.h>
+
+#include <krb5/krb5.h>
+#include <krb5/preauth_plugin.h>
+
+/* This is not a standardized value.  It's defined here only to make it easier
+ * to change in this module. */
+#define KRB5_PADATA_CKSUM_BODY_REQ 130
+
+struct server_stats{
+    int successes, failures;
+};
+
+static int
+client_get_flags(krb5_context kcontext, krb5_preauthtype pa_type)
+{
+    return PA_REAL;
+}
+
+static krb5_error_code
+client_process(krb5_context kcontext,
+              void *client_module_context,
+              void **client_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_pa_data *send_pa;
+    krb5_checksum checksum;
+    krb5_enctype enctype;
+    krb5_cksumtype *cksumtypes;
+    krb5_error_code status;
+    krb5_int32 cksumtype, *enctypes;
+    unsigned int i, n_enctypes, cksumtype_count;
+
+    memset(&checksum, 0, sizeof(checksum));
+
+    /* Get the user's long-term key if we haven't asked for it yet.  Try
+     * all of the encryption types which the server supports. */
+    if (as_key->length == 0) {
+       if ((pa_data != NULL) && (pa_data->length >= 4)) {
+#ifdef DEBUG
+           fprintf(stderr, "%d bytes of preauth data.\n", pa_data->length);
+#endif
+           n_enctypes = pa_data->length / 4;
+           enctypes = (krb5_int32*) pa_data->contents;
+       } else {
+           n_enctypes = request->nktypes;
+       }
+       for (i = 0; i < n_enctypes; i++) {
+           if ((pa_data != NULL) && (pa_data->length >= 4)) {
+               memcpy(&enctype, pa_data->contents + 4 * i, 4);
+               enctype = ntohl(enctype);
+           } else {
+               enctype = request->ktype[i];
+           }
+#ifdef DEBUG
+           fprintf(stderr, "Asking for AS key (type = %d).\n", enctype);
+#endif
+           status = (*gak_fct)(kcontext, request->client, enctype,
+                               prompter, prompter_data,
+                               salt, s2kparams, as_key, gak_data);
+           if (status == 0)
+               break;
+       }
+       if (status != 0)
+           return status;
+    }
+#ifdef DEBUG
+    fprintf(stderr, "Got AS key (type = %d).\n", as_key->enctype);
+#endif
+
+    /* Determine an appropriate checksum type for this key. */
+    cksumtype_count = 0;
+    cksumtypes = NULL;
+    status = krb5_c_keyed_checksum_types(kcontext, as_key->enctype,
+                                        &cksumtype_count, &cksumtypes);
+    if (status != 0)
+       return status;
+
+    /* Generate the checksum. */
+    for (i = 0; i < cksumtype_count; i++) {
+       status = krb5_c_make_checksum(kcontext, cksumtypes[i], as_key,
+                                     KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
+                                     encoded_request_body,
+                                     &checksum);
+       if (status == 0) {
+#ifdef DEBUG
+           fprintf(stderr, "Made checksum (type = %d, %d bytes).\n",
+                   checksum.checksum_type, encoded_request_body->length);
+#endif
+           break;
+       }
+    }
+    cksumtype = htonl(cksumtypes[i]);
+    krb5_free_cksumtypes(kcontext, cksumtypes);
+    if (status != 0) {
+       if (checksum.length > 0)
+           krb5_free_checksum_contents(kcontext, &checksum);
+       return status;
+    }
+
+    /* Allocate the preauth data structure. */
+    send_pa = malloc(sizeof(krb5_pa_data));
+    if (send_pa == NULL) {
+       krb5_free_checksum_contents(kcontext, &checksum);
+       return ENOMEM;
+    }
+    send_pa->pa_type = KRB5_PADATA_CKSUM_BODY_REQ;
+    send_pa->length = 4 + checksum.length;
+    send_pa->contents = malloc(4 + checksum.length);
+    if (send_pa->contents == NULL) {
+       krb5_free_checksum_contents(kcontext, &checksum);
+       free(send_pa);
+       return ENOMEM;
+    }
+
+    /* Store the checksum. */
+    memcpy(send_pa->contents, &cksumtype, 4);
+    memcpy(send_pa->contents + 4, checksum.contents, checksum.length);
+    *out_pa_data = send_pa;
+
+    /* Clean up. */
+    krb5_free_checksum_contents(kcontext, &checksum);
+
+    return 0;
+}
+
+/* Initialize and tear down the server-side module, and do stat tracking. */
+static krb5_error_code
+server_init(krb5_context kcontext, krb5_preauthtype pa_type,
+           void **module_context)
+{
+    struct server_stats *stats;
+    stats = malloc(sizeof(struct server_stats));
+    if (stats == NULL)
+        return ENOMEM;
+    stats->successes = 0;
+    stats->failures = 0;
+    *module_context = stats;
+    return 0;
+}
+static void
+server_fini(krb5_context kcontext, krb5_preauthtype pa_type,
+           void *module_context)
+{
+    struct server_stats *stats;
+    stats = module_context;
+    if (stats != NULL) {
+#ifdef DEBUG
+       fprintf(stderr, "Total %d clients failed pa_type %d, %d succeeded.\n",
+               stats->failures, pa_type, stats->successes);
+#endif
+       free(stats);
+    }
+}
+
+/* Obtain and return any preauthentication data (which is destined for the
+ * client) which matches type data->pa_type. */
+static krb5_error_code
+server_get_edata(krb5_context kcontext,
+                krb5_kdc_req *request,
+                struct _krb5_db_entry_new *client,
+                struct _krb5_db_entry_new *server,
+                preauth_get_entry_data_proc server_get_entry_data,
+                void *pa_module_context,
+                krb5_pa_data *data)
+{
+    krb5_data *key_data;
+    krb5_keyblock *keys, *key;
+    krb5_int32 *enctypes, enctype;
+    int i;
+
+    /* Retrieve the client's keys. */
+    key_data = NULL;
+    if ((*server_get_entry_data)(kcontext, request, client,
+                                krb5plugin_preauth_keys, &key_data) != 0) {
+#ifdef DEBUG
+       fprintf(stderr, "Error retrieving client keys.\n");
+#endif
+       return KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
+    }
+
+    /* Count which types of keys we've got, freeing the contents, which we
+     * don't need at this point. */
+    keys = (krb5_keyblock *) key_data->data;
+    key = NULL;
+    for (i = 0; keys[i].enctype != 0; i++)
+       krb5_free_keyblock_contents(kcontext, &keys[i]);
+
+    /* Return the list of encryption types. */
+    enctypes = malloc(i * 4);
+    if (enctypes == NULL) {
+       krb5_free_data(kcontext, key_data);
+       return ENOMEM;
+    }
+#ifdef DEBUG
+    fprintf(stderr, "Supported enctypes = {");
+#endif
+    for (i = 0; keys[i].enctype != 0; i++) {
+#ifdef DEBUG
+       fprintf(stderr, "%s%d", (i > 0) ? ", " : "", keys[i].enctype);
+#endif
+       enctype = htonl(keys[i].enctype);
+       memcpy(&enctypes[i], &enctype, 4);
+    }
+#ifdef DEBUG
+    fprintf(stderr, "}.\n");
+#endif
+    data->pa_type = KRB5_PADATA_CKSUM_BODY_REQ;
+    data->length = (i * 4);
+    data->contents = (unsigned char *) enctypes;
+    krb5_free_data(kcontext, key_data);
+    return 0;
+}
+
+/* Verify a request from a client. */
+static krb5_error_code
+server_verify(krb5_context kcontext,
+             struct _krb5_db_entry_new *client,
+             krb5_data *req_pkt,
+             krb5_kdc_req *request,
+             krb5_enc_tkt_part *enc_tkt_reply,
+             krb5_pa_data *data,
+             preauth_get_entry_data_proc server_get_entry_data,
+             void *pa_module_context,
+             void **pa_request_context)
+{
+    krb5_int32 cksumtype;
+    krb5_checksum checksum;
+    krb5_boolean valid;
+    krb5_data *key_data, *req_body;
+    krb5_keyblock *keys, *key;
+    size_t length;
+    int i;
+    unsigned int j, cksumtypes_count;
+    krb5_cksumtype *cksumtypes;
+    krb5_error_code status;
+    struct server_stats *stats;
+
+    stats = pa_module_context;
+
+    /* Verify the preauth data.  Start with the checksum type. */
+    if (data->length < 4) {
+       stats->failures++;
+       return KRB5KDC_ERR_PREAUTH_FAILED;
+    }
+    memcpy(&cksumtype, data->contents, 4);
+    memset(&checksum, 0, sizeof(checksum));
+    checksum.checksum_type = ntohl(cksumtype);
+
+    /* Verify that the amount of data we have left is what we expect. */
+    if (krb5_c_checksum_length(kcontext, checksum.checksum_type,
+                              &length) != 0) {
+#ifdef DEBUG
+       fprintf(stderr, "Error determining checksum size (type = %d). "
+               "Is it supported?\n", checksum.checksum_type);
+#endif
+       stats->failures++;
+       return KRB5KDC_ERR_SUMTYPE_NOSUPP;
+    }
+    if (data->length - 4 != length) {
+#ifdef DEBUG
+       fprintf(stderr, "Checksum size doesn't match client packet size.\n");
+#endif
+       stats->failures++;
+       return KRB5KDC_ERR_PREAUTH_FAILED;
+    }
+    checksum.length = length;
+
+    /* Pull up the client's keys. */
+    key_data = NULL;
+    if ((*server_get_entry_data)(kcontext, request, client,
+                                krb5plugin_preauth_keys, &key_data) != 0) {
+#ifdef DEBUG
+       fprintf(stderr, "Error retrieving client keys.\n");
+#endif
+       stats->failures++;
+       return KRB5KDC_ERR_PREAUTH_FAILED;
+    }
+
+    /* Find the key which would have been used to generate the checksum. */
+    keys = (krb5_keyblock *) key_data->data;
+    key = NULL;
+    for (i = 0; keys[i].enctype != 0; i++) {
+       key = &keys[i];
+       cksumtypes_count = 0;
+       cksumtypes = NULL;
+       if (krb5_c_keyed_checksum_types(kcontext, key->enctype,
+                                       &cksumtypes_count, &cksumtypes) != 0)
+           continue;
+       for (j = 0; j < cksumtypes_count; j++) {
+           if (cksumtypes[j] == checksum.checksum_type)
+               break;
+       }
+       if (cksumtypes != NULL)
+           krb5_free_cksumtypes(kcontext, cksumtypes);
+       if (j < cksumtypes_count) {
+#ifdef DEBUG
+           fprintf(stderr, "Found checksum key.\n");
+#endif
+           break;
+       }
+    }
+    if ((key == NULL) || (key->enctype == 0)) {
+       for (i = 0; keys[i].enctype != 0; i++)
+           krb5_free_keyblock_contents(kcontext, &keys[i]);
+       krb5_free_data(kcontext, key_data);
+       stats->failures++;
+       return KRB5KDC_ERR_SUMTYPE_NOSUPP;
+    }
+
+    /* Save a copy of the key. */
+    if (krb5_copy_keyblock(kcontext, &keys[i], &key) != 0) {
+       for (i = 0; keys[i].enctype != 0; i++)
+           krb5_free_keyblock_contents(kcontext, &keys[i]);
+       krb5_free_data(kcontext, key_data);
+       stats->failures++;
+       return KRB5KDC_ERR_SUMTYPE_NOSUPP;
+    }
+    for (i = 0; keys[i].enctype != 0; i++)
+       krb5_free_keyblock_contents(kcontext, &keys[i]);
+    krb5_free_data(kcontext, key_data);
+
+    /* Rebuild a copy of the client's request-body.  If we were serious
+     * about doing this with any chance of working interoperability, we'd
+     * extract the structure directly from the req_pkt structure.  This
+     * will probably work if it's us on both ends, though. */
+    req_body = NULL;
+    if ((*server_get_entry_data)(kcontext, request, client,
+                                krb5plugin_preauth_request_body,
+                                &req_body) != 0) {
+       krb5_free_keyblock(kcontext, key);
+       stats->failures++;
+       return KRB5KDC_ERR_PREAUTH_FAILED;
+    }
+
+#ifdef DEBUG
+    fprintf(stderr, "AS key type %d, checksum type %d, %d bytes.\n",
+           key->enctype, checksum.checksum_type, req_body->length);
+#endif
+
+    /* Verify the checksum itself. */
+    checksum.contents = data->contents + 4;
+    valid = FALSE;
+    status = krb5_c_verify_checksum(kcontext, key,
+                                   KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
+                                   req_body, &checksum, &valid);
+
+    /* Clean up. */
+    krb5_free_data(kcontext, req_body);
+    krb5_free_keyblock(kcontext, key);
+
+    /* Evaluate our results. */
+    if ((status != 0) || (!valid)) {
+#ifdef DEBUG
+       if (status != 0) {
+           fprintf(stderr, "Error in checksum verification.\n");
+       } else {
+           fprintf(stderr, "Checksum mismatch.\n");
+       }
+#endif
+       stats->failures++;
+       return KRB5KDC_ERR_PREAUTH_FAILED;
+    }
+
+    /* Note that preauthentication succeeded. */
+    enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
+    stats->successes++;
+    return 0;
+}
+
+/* Create the response for a client. */
+static krb5_error_code
+server_return(krb5_context kcontext,
+             krb5_pa_data *padata,
+             struct _krb5_db_entry_new *client,
+             krb5_data *req_pkt,
+             krb5_kdc_req *request,
+             krb5_kdc_rep *reply,
+             struct _krb5_key_data *client_key,
+             krb5_keyblock *encrypting_key,
+             krb5_pa_data **send_pa,
+             preauth_get_entry_data_proc server_get_entry_data,
+             void *pa_module_context,
+             void **pa_request_context)
+{
+    /* We don't need to send data back on the return trip. */
+    *send_pa = NULL;
+    return 0;
+}
+
+static int
+server_get_flags(krb5_context kcontext, krb5_preauthtype pa_type)
+{
+    return PA_SUFFICIENT;
+}
+
+static krb5_preauthtype supported_client_pa_types[] = {
+    KRB5_PADATA_CKSUM_BODY_REQ, 0,
+};
+static krb5_preauthtype supported_server_pa_types[] = {
+    KRB5_PADATA_CKSUM_BODY_REQ, 0,
+};
+
+struct krb5plugin_preauth_ftable_v0 preauthentication0 = {
+    "cksum_body",
+    &supported_client_pa_types[0],
+    &supported_server_pa_types[0],
+    NULL,
+    NULL,
+    NULL,
+    client_get_flags,
+    NULL,
+    client_process,
+    NULL,
+    server_init,
+    server_fini,
+    server_get_flags,
+    server_get_edata,
+    server_verify,
+    server_return,
+    NULL
+};
diff --git a/src/plugins/preauth/wpse/Makefile.in b/src/plugins/preauth/wpse/Makefile.in
new file mode 100644 (file)
index 0000000..6b18a7c
--- /dev/null
@@ -0,0 +1,42 @@
+thisconfigdir=.
+myfulldir=plugins/preauth/wpse
+mydir=.
+BUILDTOP=$(REL)..$(S)..$(S)..
+KRB5_RUN_ENV = @KRB5_RUN_ENV@
+KRB5_CONFIG_SETUP = KRB5_CONFIG=$(SRCTOP)/config-files/krb5.conf ; export KRB5_CONFIG ;
+PROG_LIBPATH=-L$(TOPLIBD)
+PROG_RPATH=$(KRB5_LIBDIR)
+MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR)
+DEFS=@DEFS@
+
+LOCALINCLUDES = -I../../../include/krb5
+
+LIBBASE=wpse
+LIBMAJOR=0
+LIBMINOR=0
+SO_EXT=.so
+RELDIR=../plugins/preauth/wpse
+# Depends on libk5crypto and libkrb5
+SHLIB_EXPDEPS = \
+       $(TOPLIBD)/libk5crypto$(SHLIBEXT) \
+       $(TOPLIBD)/libkrb5$(SHLIBEXT)
+SHLIB_EXPLIBS= -lkrb5 -lcom_err -lk5crypto $(SUPPORT_LIB) $(LIBS)
+
+SHLIB_DIRS=-L$(TOPLIBD)
+SHLIB_RDIRS=$(KRB5_LIBDIR)
+STOBJLISTS=OBJS.ST
+STLIBOBJS=src/wpse.o
+
+SRCS= $(srcdir)/src/wpse.c
+
+all-unix:: $(LIBBASE)$(SO_EXT)
+install-unix:: install-libs
+clean-unix:: clean-libs clean-libobjs
+
+clean::
+       $(RM) lib$(LIBBASE)$(SO_EXT)
+
+@libnover_frag@
+@libobj_frag@
+
+# +++ Dependency line eater +++
diff --git a/src/plugins/preauth/wpse/configure.in b/src/plugins/preauth/wpse/configure.in
new file mode 100644 (file)
index 0000000..868dfd5
--- /dev/null
@@ -0,0 +1,14 @@
+K5_AC_INIT(configure.in)
+enable_shared=yes
+build_dynobj=yes
+CONFIG_RULES
+
+AC_CHECK_HEADERS(errno.h string.h)
+
+KRB5_RUN_FLAGS
+dnl The following is for check...
+KRB5_BUILD_PROGRAM
+KRB5_BUILD_LIBOBJS
+KRB5_BUILD_LIBRARY_WITH_DEPS
+AC_CONFIG_HEADERS(config.h)
+V5_AC_OUTPUT_MAKEFILE
diff --git a/src/plugins/preauth/wpse/src/wpse.c b/src/plugins/preauth/wpse/src/wpse.c
new file mode 100644 (file)
index 0000000..579c63b
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2006 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Red Hat, Inc., nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Worst. Preauthentication. Scheme. Ever. */
+
+#ident "$Id$"
+
+#ifdef HAVE_CONFIG_H
+#include "../config.h"
+#endif
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include <arpa/inet.h>
+#include <stdio.h>
+
+#include <krb5/krb5.h>
+#include <krb5/preauth_plugin.h>
+
+/* This is not a standardized value.  It's defined here only to make it easier
+ * to change in this module. */
+#define KRB5_PADATA_WPSE_REQ 131
+
+static int
+client_get_flags(krb5_context kcontext, krb5_preauthtype pa_type)
+{
+    return PA_REAL;
+}
+
+static krb5_error_code
+client_init(krb5_context kcontext, krb5_preauthtype pa_type, void **ctx)
+{
+    int *mctx;
+
+    mctx = malloc(sizeof(int));
+    if (mctx == NULL)
+       return ENOMEM;
+    *mctx = 0;
+    *ctx = mctx;
+    return 0;
+}
+
+static void
+client_fini(krb5_context kcontext, krb5_preauthtype pa_type, void *ctx)
+{
+    int *mctx;
+
+    mctx = ctx;
+    if (mctx) {
+#ifdef DEBUG
+        fprintf(stderr, "wpse module called total of %d times\n", *mctx);
+#endif
+        free(mctx);
+    }
+}
+
+static krb5_error_code
+client_process(krb5_context kcontext,
+              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_pa_data *send_pa;
+    krb5_int32 nnonce, enctype;
+    krb5_keyblock *kb;
+    krb5_error_code status;
+    int *mctx;
+
+#ifdef DEBUG
+    fprintf(stderr, "%d bytes of preauthentication data (type %d)\n",
+           pa_data->length, pa_data->pa_type);
+#endif
+
+    mctx = module_context;
+    if (mctx) {
+       (*mctx)++;
+    }
+
+    if (pa_data->length == 0) {
+       /* Create preauth data. */
+       send_pa = malloc(sizeof(krb5_pa_data));
+       if (send_pa == NULL)
+           return ENOMEM;
+       send_pa->pa_type = KRB5_PADATA_WPSE_REQ;
+       send_pa->length = 4;
+       send_pa->contents = malloc(4);
+       if (send_pa->contents == NULL) {
+           free(send_pa);
+           return ENOMEM;
+       }
+       /* Store the preauth data. */
+       nnonce = htonl(request->nonce);
+       memcpy(send_pa->contents, &nnonce, 4);
+       *out_pa_data = send_pa;
+       /* Allocate a context. Useful for verifying that we do in fact
+        * do per-request cleanup. */
+       if (*request_context == NULL)
+           *request_context = malloc(4);
+    } else {
+       /* A reply from the KDC.  Conventionally this would be
+        * indicated by a different preauthentication type, but this
+        * mechanism/implementation doesn't do that. */
+       if (pa_data->length > 4) {
+           memcpy(&enctype, pa_data->contents, 4);
+           kb = NULL;
+           status = krb5_init_keyblock(kcontext, ntohl(enctype),
+                                       pa_data->length - 4, &kb);
+           if (status != 0)
+               return status;
+           memcpy(kb->contents, pa_data->contents + 4, pa_data->length - 4);
+#ifdef DEBUG
+           fprintf(stderr, "Recovered key type=%d, length=%d.\n",
+                   kb->enctype, kb->length);
+#endif
+           status = krb5_copy_keyblock_contents(kcontext, kb, as_key);
+           krb5_free_keyblock(kcontext, kb);
+           return status;
+       }
+       return KRB5KRB_ERR_GENERIC;
+    }
+    return 0;
+}
+
+static void
+client_cleanup(krb5_context kcontext, void *module_context,
+              void **request_context)
+{
+    if (*request_context != NULL) {
+       free(*request_context);
+       *request_context = NULL;
+    }
+    return;
+}
+
+/* Free state. */
+static krb5_error_code
+server_free_pa_request_context(krb5_context kcontext, void *module_context,
+                              void **request_context)
+{
+    if (*request_context != NULL) {
+       free(*request_context);
+       *request_context = NULL;
+    }
+    return 0;
+}
+
+/* Obtain and return any preauthentication data (which is destined for the
+ * client) which matches type data->pa_type. */
+static krb5_error_code
+server_get_edata(krb5_context kcontext,
+                krb5_kdc_req *request,
+                struct _krb5_db_entry_new *client,
+                struct _krb5_db_entry_new *server,
+                preauth_get_entry_data_proc server_get_entry_data,
+                void *pa_module_context,
+                krb5_pa_data *data)
+{
+    /* Return zero bytes of data. */
+    data->length = 0;
+    data->contents = NULL;
+    return 0;
+}
+
+/* Verify a request from a client. */
+static krb5_error_code
+server_verify(krb5_context kcontext,
+             struct _krb5_db_entry_new *client,
+             krb5_data *req_pkt,
+             krb5_kdc_req *request,
+             krb5_enc_tkt_part *enc_tkt_reply,
+             krb5_pa_data *data,
+             preauth_get_entry_data_proc server_get_entry_data,
+             void *pa_module_context,
+             void **pa_request_context)
+{
+    krb5_int32 nnonce;
+    /* Verify the preauth data. */
+    if (data->length != 4)
+       return KRB5KDC_ERR_PREAUTH_FAILED;
+    memcpy(&nnonce, data->contents, 4);
+    nnonce = ntohl(nnonce);
+    if (memcmp(&nnonce, &request->nonce, 4) != 0)
+       return KRB5KDC_ERR_PREAUTH_FAILED;
+    /* Note that preauthentication succeeded. */
+    enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
+    enc_tkt_reply->flags |= TKT_FLG_HW_AUTH;
+    /* Allocate a context. Useful for verifying that we do in fact do
+     * per-request cleanup. */
+    if (*pa_request_context == NULL)
+       *pa_request_context = malloc(4);
+    return 0;
+}
+
+/* Create the response for a client. */
+static krb5_error_code
+server_return(krb5_context kcontext,
+             krb5_pa_data *padata,
+             struct _krb5_db_entry_new *client,
+             krb5_data *req_pkt,
+             krb5_kdc_req *request,
+             krb5_kdc_rep *reply,
+             struct _krb5_key_data *client_key,
+             krb5_keyblock *encrypting_key,
+             krb5_pa_data **send_pa,
+             preauth_get_entry_data_proc server_get_entry_data,
+             void *pa_module_context,
+             void **pa_request_context)
+{
+    /* This module does a couple of dumb things.  It tags its reply with
+     * the same type as the initial challenge (expecting the client to sort
+     * out whether there's anything useful in there).  Oh, and it replaces
+     * the AS reply key with one which is sent in the clear. */
+    krb5_keyblock *kb;
+    krb5_int32 enctype;
+    int i;
+
+    *send_pa = NULL;
+
+    /* We'll want a key with the first supported enctype. */
+    for (i = 0; i < request->nktypes; i++) {
+       kb = NULL;
+       if (krb5_init_keyblock(kcontext, request->ktype[i], 0, &kb) == 0) {
+           break;
+       }
+    }
+    if (i >= request->nktypes) {
+       /* No matching cipher type found. */
+       return 0;
+    }
+
+    /* Randomize a key and save it for the client. */
+    if (krb5_c_make_random_key(kcontext, request->ktype[i], kb) != 0) {
+       krb5_free_keyblock(kcontext, kb);
+       return 0;
+    }
+#ifdef DEBUG
+    fprintf(stderr, "Generated random key, type=%d, length=%d.\n",
+           kb->enctype, kb->length);
+#endif
+
+    *send_pa = malloc(sizeof(krb5_pa_data));
+    if (*send_pa == NULL) {
+       krb5_free_keyblock(kcontext, kb);
+       return ENOMEM;
+    }
+    (*send_pa)->pa_type = KRB5_PADATA_WPSE_REQ;
+    (*send_pa)->length = 4 + kb->length;
+    (*send_pa)->contents = malloc(4 + kb->length);
+    if ((*send_pa)->contents == NULL) {
+       free(*send_pa);
+       *send_pa = NULL;
+       krb5_free_keyblock(kcontext, kb);
+       return ENOMEM;
+    }
+
+    /* Store the preauth data. */
+    enctype = htonl(kb->enctype);
+    memcpy((*send_pa)->contents, &enctype, 4);
+    memcpy((*send_pa)->contents + 4, kb->contents, kb->length);
+    krb5_copy_keyblock_contents(kcontext, kb, encrypting_key);
+
+    /* Clean up. */
+    krb5_free_keyblock(kcontext, kb);
+
+    return 0;
+}
+
+static int
+server_get_flags(krb5_context kcontext, krb5_preauthtype pa_type)
+{
+    return PA_HARDWARE | PA_REPLACES_KEY;
+}
+
+static krb5_preauthtype supported_client_pa_types[] = {KRB5_PADATA_WPSE_REQ, 0};
+static krb5_preauthtype supported_server_pa_types[] = {KRB5_PADATA_WPSE_REQ, 0};
+
+struct krb5plugin_preauth_ftable_v0 preauthentication0 = {
+    "wpse",
+    &supported_client_pa_types[0],
+    &supported_server_pa_types[0],
+    NULL,
+    client_init,
+    client_fini,
+    client_get_flags,
+    client_cleanup,
+    client_process,
+    NULL,
+    NULL,
+    NULL,
+    server_get_flags,
+    server_get_edata,
+    server_verify,
+    server_return,
+    server_free_pa_request_context,
+};
diff --git a/src/plugins/preauth/wpse/wpse.exports b/src/plugins/preauth/wpse/wpse.exports
new file mode 100644 (file)
index 0000000..ff5e3f1
--- /dev/null
@@ -0,0 +1 @@
+preauthentication0