spnego: Also use mechglue names
[metze/heimdal/wip.git] / lib / gssapi / test_context.c
index 5a502ae7010de235f4400e62c61870d9dafc0c3c..b31a94771324de3abf9691387331ca32dd91fb31 100644 (file)
@@ -1,18 +1,18 @@
 /*
- * Copyright (c) 2006 - 2008 Kungliga Tekniska Högskolan
- * (Royal Institute of Technology, Stockholm, Sweden). 
- * All rights reserved. 
+ * Copyright (c) 2006 - 2008 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
  *
- * Redistribution and use in source and binary forms, with or without 
- * modification, are permitted provided that the following conditions 
- * are met: 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
  *
- * 1. Redistributions of source code must retain the above copyright 
- *    notice, this list of conditions and the following disclaimer. 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
  *
- * 2. 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. 
+ * 2. 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.
  *
  * 3. Neither the name of KTH nor the names of its contributors may be
  *    used to endorse or promote products derived from this software without
 #include "krb5/gsskrb5_locl.h"
 #include <err.h>
 #include <getarg.h>
+#include <gssapi.h>
+#include <gssapi_krb5.h>
+#include <gssapi_spnego.h>
+#include <gssapi_ntlm.h>
 #include "test_common.h"
 
-RCSID("$Id$");
-
 static char *type_string;
 static char *mech_string;
+static char *mechs_string;
 static char *ret_mech_string;
+static char *localname_string;
+static char *client_name;
+static char *client_password;
+static char *localname_string;
 static int dns_canon_flag = -1;
 static int mutual_auth_flag = 0;
 static int dce_style_flag = 0;
 static int wrapunwrap_flag = 0;
+static int iov_flag = 0;
+static int aead_flag = 0;
 static int getverifymic_flag = 0;
 static int deleg_flag = 0;
+static int policy_deleg_flag = 0;
+static int server_no_deleg_flag = 0;
+static int ei_cred_flag = 0;
+static int ei_ctx_flag = 0;
+static char *client_ccache = NULL;
+static char *client_keytab = NULL;
 static char *gsskrb5_acceptor_identity = NULL;
 static char *session_enctype_string = NULL;
+static int client_time_offset = 0;
+static int server_time_offset = 0;
+static int max_loops = 0;
+static char *limit_enctype_string = NULL;
 static int version_flag = 0;
 static int verbose_flag = 0;
 static int help_flag   = 0;
 
+static krb5_context context;
+static krb5_enctype limit_enctype = 0;
+
+static gss_OID_desc test_negoex_1_mech = { 6, "\x69\x85\xa2\xc0\xac\x66" };
+static gss_OID_desc test_negoex_2_mech = { 6, "\x69\x84\xb0\xd1\xa8\x2c" };
+
 static struct {
     const char *name;
-    gss_OID *oid;
+    gss_OID oid;
 } o2n[] = {
-    { "krb5", &GSS_KRB5_MECHANISM },
-    { "spnego", &GSS_SPNEGO_MECHANISM },
-    { "ntlm", &GSS_NTLM_MECHANISM },
-    { "sasl-digest-md5", &GSS_SASL_DIGEST_MD5_MECHANISM }
+    { "krb5", NULL /* GSS_KRB5_MECHANISM */ },
+    { "spnego", NULL /* GSS_SPNEGO_MECHANISM */ },
+    { "ntlm", NULL /* GSS_NTLM_MECHANISM */ },
+    { "sasl-digest-md5", NULL /* GSS_SASL_DIGEST_MD5_MECHANISM */ },
+    { "test_negoex_1", NULL },
+    { "test_negoex_2", NULL },
 };
 
+static void
+init_o2n(void)
+{
+    o2n[0].oid = GSS_KRB5_MECHANISM;
+    o2n[1].oid = GSS_SPNEGO_MECHANISM;
+    o2n[2].oid = GSS_NTLM_MECHANISM;
+    o2n[3].oid = GSS_SASL_DIGEST_MD5_MECHANISM;
+    o2n[4].oid = &test_negoex_1_mech;
+    o2n[5].oid = &test_negoex_2_mech;
+}
+
 static gss_OID
 string_to_oid(const char *name)
 {
-    int i;
+    size_t i;
     for (i = 0; i < sizeof(o2n)/sizeof(o2n[0]); i++)
        if (strcasecmp(name, o2n[i].name) == 0)
-           return *o2n[i].oid;
-    errx(1, "name %s not unknown", name);
+           return o2n[i].oid;
+    errx(1, "name '%s' not known", name);
+}
+
+static void
+string_to_oids(gss_OID_set *oidsetp, gss_OID_set oidset,
+               gss_OID_desc *oidarray, size_t oidarray_len,
+               char *names)
+{
+    char *name;
+    char *s;
+
+    if (names[0] == '\0') {
+        *oidsetp = GSS_C_NO_OID_SET;
+        return;
+    }
+
+    oidset->elements = &oidarray[0];
+    if (strcasecmp(names, "all") == 0) {
+        if (sizeof(o2n)/sizeof(o2n[0]) > oidarray_len)
+            errx(1, "internal error: oidarray must be enlarged");
+        for (oidset->count = 0; oidset->count < oidarray_len; oidset->count++)
+            oidset->elements[oidset->count] = *o2n[oidset->count].oid;
+    } else {
+        for (oidset->count = 0, name = strtok_r(names, ", ", &s);
+             name != NULL;
+             oidset->count++, name = strtok_r(NULL, ", ", &s)) {
+            if (oidset->count >= oidarray_len)
+                errx(1, "too many mech names given");
+            oidset->elements[oidset->count] = *string_to_oid(name);
+        }
+    }
+    *oidsetp = oidset;
 }
 
 static const char *
 oid_to_string(const gss_OID oid)
 {
-    int i;
+    size_t i;
     for (i = 0; i < sizeof(o2n)/sizeof(o2n[0]); i++)
-       if (gss_oid_equal(oid, *o2n[i].oid))
+       if (gss_oid_equal(oid, o2n[i].oid))
            return o2n[i].name;
     return "unknown oid";
 }
@@ -88,19 +157,21 @@ loop(gss_OID mechoid,
      gss_OID nameoid, const char *target,
      gss_cred_id_t init_cred,
      gss_ctx_id_t *sctx, gss_ctx_id_t *cctx,
-     gss_OID *actual_mech, 
+     gss_OID *actual_mech,
      gss_cred_id_t *deleg_cred)
 {
     int server_done = 0, client_done = 0;
+    int num_loops = 0;
     OM_uint32 maj_stat, min_stat;
-    gss_name_t gss_target_name;
+    gss_name_t gss_target_name, src_name;
     gss_buffer_desc input_token, output_token;
     OM_uint32 flags = 0, ret_cflags, ret_sflags;
-    gss_OID actual_mech_client; 
-    gss_OID actual_mech_server; 
+    gss_OID actual_mech_client;
+    gss_OID actual_mech_server;
 
     *actual_mech = GSS_C_NO_OID;
 
+    flags |= GSS_C_REPLAY_FLAG;
     flags |= GSS_C_INTEG_FLAG;
     flags |= GSS_C_CONF_FLAG;
 
@@ -110,6 +181,8 @@ loop(gss_OID mechoid,
        flags |= GSS_C_DCE_STYLE;
     if (deleg_flag)
        flags |= GSS_C_DELEG_FLAG;
+    if (policy_deleg_flag)
+       flags |= GSS_C_DELEG_POLICY_FLAG;
 
     input_token.value = rk_UNCONST(target);
     input_token.length = strlen(target);
@@ -125,14 +198,17 @@ loop(gss_OID mechoid,
     input_token.value = NULL;
 
     while (!server_done || !client_done) {
+       num_loops++;
+
+       gsskrb5_set_time_offset(client_time_offset);
 
        maj_stat = gss_init_sec_context(&min_stat,
                                        init_cred,
                                        cctx,
                                        gss_target_name,
-                                       mechoid, 
+                                       mechoid,
                                        flags,
-                                       0, 
+                                       0,
                                        NULL,
                                        &input_token,
                                        &actual_mech_client,
@@ -147,18 +223,22 @@ loop(gss_OID mechoid,
        else
            client_done = 1;
 
+       gsskrb5_get_time_offset(&client_time_offset);
+
        if (client_done && server_done)
            break;
 
        if (input_token.length != 0)
            gss_release_buffer(&min_stat, &input_token);
 
+       gsskrb5_set_time_offset(server_time_offset);
+
        maj_stat = gss_accept_sec_context(&min_stat,
                                          sctx,
                                          GSS_C_NO_CREDENTIAL,
                                          &output_token,
                                          GSS_C_NO_CHANNEL_BINDINGS,
-                                         NULL,
+                                         &src_name,
                                          &actual_mech_server,
                                          &input_token,
                                          &ret_sflags,
@@ -168,8 +248,7 @@ loop(gss_OID mechoid,
                errx(1, "accept_sec_context: %s",
                     gssapi_err(maj_stat, min_stat, actual_mech_server));
 
-       if (verbose_flag)
-           printf("%.*s", (int)input_token.length, (char *)input_token.value);
+       gsskrb5_get_time_offset(&server_time_offset);
 
        if (output_token.length != 0)
            gss_release_buffer(&min_stat, &output_token);
@@ -178,24 +257,85 @@ loop(gss_OID mechoid,
            ;
        else
            server_done = 1;
-    }  
+    }
     if (output_token.length != 0)
        gss_release_buffer(&min_stat, &output_token);
     if (input_token.length != 0)
        gss_release_buffer(&min_stat, &input_token);
     gss_release_name(&min_stat, &gss_target_name);
 
+    if (deleg_flag || policy_deleg_flag) {
+       if (server_no_deleg_flag) {
+           if (*deleg_cred != GSS_C_NO_CREDENTIAL)
+               errx(1, "got delegated cred but didn't expect one");
+       } else if (*deleg_cred == GSS_C_NO_CREDENTIAL)
+           errx(1, "asked for delegarated cred but did get one");
+    } else if (*deleg_cred != GSS_C_NO_CREDENTIAL)
+         errx(1, "got deleg_cred cred but didn't ask");
+
     if (gss_oid_equal(actual_mech_server, actual_mech_client) == 0)
        errx(1, "mech mismatch");
     *actual_mech = actual_mech_server;
+
+    if (localname_string) {
+        gss_buffer_desc lname;
+
+        maj_stat = gss_localname(&min_stat, src_name, GSS_C_NO_OID, &lname);
+        if (maj_stat != GSS_S_COMPLETE)
+            errx(1, "localname: %s",
+                 gssapi_err(maj_stat, min_stat, GSS_C_NO_OID));
+        if (verbose_flag)
+            printf("localname: %.*s\n", (int)lname.length,
+                   (char *)lname.value);
+        if (lname.length != strlen(localname_string) ||
+            strncmp(localname_string, lname.value, lname.length))
+            errx(1, "localname: expected \"%s\", got \"%.*s\" (1)",
+                 localname_string, (int)lname.length, (char *)lname.value);
+        gss_release_buffer(&min_stat, &lname);
+        maj_stat = gss_localname(&min_stat, src_name, actual_mech_server,
+                                 &lname);
+        if (maj_stat != GSS_S_COMPLETE)
+            errx(1, "localname: %s",
+                 gssapi_err(maj_stat, min_stat, actual_mech_server));
+        if (lname.length != strlen(localname_string) ||
+            strncmp(localname_string, lname.value, lname.length))
+            errx(1, "localname: expected \"%s\", got \"%.*s\" (2)",
+                 localname_string, (int)lname.length, (char *)lname.value);
+        gss_release_buffer(&min_stat, &lname);
+
+        if (!gss_userok(src_name, localname_string))
+            errx(1, "localname is not userok");
+        if (gss_userok(src_name, "nosuchuser:no"))
+            errx(1, "gss_userok() appears broken");
+    }
+    if (verbose_flag) {
+        gss_buffer_desc iname;
+
+        maj_stat = gss_display_name(&min_stat, src_name, &iname, NULL);
+        if (maj_stat != GSS_S_COMPLETE)
+            errx(1, "display_name: %s",
+                 gssapi_err(maj_stat, min_stat, GSS_C_NO_OID));
+        printf("client name: %.*s\n", (int)iname.length, (char *)iname.value);
+        gss_release_buffer(&min_stat, &iname);
+    }
+    gss_release_name(&min_stat, &src_name);
+
+    if (max_loops && num_loops > max_loops)
+       errx(1, "num loops %d was lager then max loops %d",
+            num_loops, max_loops);
+
+    if (verbose_flag) {
+       printf("server time offset: %d\n", server_time_offset);
+       printf("client time offset: %d\n", client_time_offset);
+       printf("num loops %d\n", num_loops);
+    }
 }
 
 static void
-wrapunwrap(gss_ctx_id_t cctx, gss_ctx_id_t sctx, gss_OID mechoid)
+wrapunwrap(gss_ctx_id_t cctx, gss_ctx_id_t sctx, int flags, gss_OID mechoid)
 {
     gss_buffer_desc input_token, output_token, output_token2;
     OM_uint32 min_stat, maj_stat;
-    int32_t flags = 0;
     gss_qop_t qop_state;
     int conf_state;
 
@@ -213,6 +353,219 @@ wrapunwrap(gss_ctx_id_t cctx, gss_ctx_id_t sctx, gss_OID mechoid)
     if (maj_stat != GSS_S_COMPLETE)
        errx(1, "gss_unwrap failed: %s",
             gssapi_err(maj_stat, min_stat, mechoid));
+
+    gss_release_buffer(&min_stat, &output_token);
+    gss_release_buffer(&min_stat, &output_token2);
+
+#if 0 /* doesn't work for NTLM yet */
+    if (!!conf_state != !!flags)
+       errx(1, "conf_state mismatch");
+#endif
+}
+
+#define USE_CONF               1
+#define USE_HEADER_ONLY                2
+#define USE_SIGN_ONLY          4
+#define FORCE_IOV              8
+/* NO_DATA comes from <netdb.h>; we don't use it here; we appropriate it */
+#ifdef NO_DATA
+#undef NO_DATA
+#endif
+#define NO_DATA                        16
+
+static void
+wrapunwrap_iov(gss_ctx_id_t cctx, gss_ctx_id_t sctx, int flags, gss_OID mechoid)
+{
+    krb5_data token, header, trailer;
+    OM_uint32 min_stat, maj_stat;
+    gss_qop_t qop_state;
+    int conf_state, conf_state2;
+    gss_iov_buffer_desc iov[6];
+    unsigned char *p;
+    int iov_len;
+    char header_data[9] = "ABCheader";
+    char trailer_data[10] = "trailerXYZ";
+
+    char token_data[16] = "0123456789abcdef";
+
+    memset(&iov, 0, sizeof(iov));
+
+    if (flags & USE_SIGN_ONLY) {
+       header.data = header_data;
+       header.length = 9;
+       trailer.data = trailer_data;
+       trailer.length = 10;
+    } else {
+       header.data = NULL;
+       header.length = 0;
+       trailer.data = NULL;
+       trailer.length = 0;
+    }
+
+    token.data = token_data;
+    token.length = 16;
+
+    iov_len = sizeof(iov)/sizeof(iov[0]);
+
+    memset(iov, 0, sizeof(iov));
+
+    iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER | GSS_IOV_BUFFER_TYPE_FLAG_ALLOCATE;
+
+    if (header.length != 0) {
+       iov[1].type = GSS_IOV_BUFFER_TYPE_SIGN_ONLY;
+       iov[1].buffer.length = header.length;
+       iov[1].buffer.value = header.data;
+    } else {
+       iov[1].type = GSS_IOV_BUFFER_TYPE_EMPTY;
+       iov[1].buffer.length = 0;
+       iov[1].buffer.value = NULL;
+    }
+    iov[2].type = GSS_IOV_BUFFER_TYPE_DATA;
+    if (flags & NO_DATA) {
+       iov[2].buffer.length = 0;
+    } else {
+       iov[2].buffer.length = token.length;
+    }
+    iov[2].buffer.value = token.data;
+    if (trailer.length != 0) {
+       iov[3].type = GSS_IOV_BUFFER_TYPE_SIGN_ONLY;
+       iov[3].buffer.length = trailer.length;
+       iov[3].buffer.value = trailer.data;
+    } else {
+       iov[3].type = GSS_IOV_BUFFER_TYPE_EMPTY;
+       iov[3].buffer.length = 0;
+       iov[3].buffer.value = NULL;
+    }
+    if (dce_style_flag) {
+       iov[4].type = GSS_IOV_BUFFER_TYPE_EMPTY;
+    } else {
+       iov[4].type = GSS_IOV_BUFFER_TYPE_PADDING | GSS_IOV_BUFFER_TYPE_FLAG_ALLOCATE;
+    }
+    iov[4].buffer.length = 0;
+    iov[4].buffer.value = 0;
+    if (dce_style_flag) {
+       iov[5].type = GSS_IOV_BUFFER_TYPE_EMPTY;
+    } else if (flags & USE_HEADER_ONLY) {
+       iov[5].type = GSS_IOV_BUFFER_TYPE_EMPTY;
+    } else {
+       iov[5].type = GSS_IOV_BUFFER_TYPE_TRAILER | GSS_IOV_BUFFER_TYPE_FLAG_ALLOCATE;
+    }
+    iov[5].buffer.length = 0;
+    iov[5].buffer.value = 0;
+
+    maj_stat = gss_wrap_iov(&min_stat, cctx, dce_style_flag || flags & USE_CONF, 0, &conf_state,
+                           iov, iov_len);
+    if (maj_stat != GSS_S_COMPLETE)
+       errx(1, "gss_wrap_iov failed");
+
+    token.length =
+       iov[0].buffer.length +
+       iov[1].buffer.length +
+       iov[2].buffer.length +
+       iov[3].buffer.length +
+       iov[4].buffer.length +
+       iov[5].buffer.length;
+    token.data = emalloc(token.length);
+
+    p = token.data;
+    memcpy(p, iov[0].buffer.value, iov[0].buffer.length);
+    p += iov[0].buffer.length;
+    memcpy(p, iov[1].buffer.value, iov[1].buffer.length);
+    p += iov[1].buffer.length;
+    memcpy(p, iov[2].buffer.value, iov[2].buffer.length);
+    p += iov[2].buffer.length;
+    memcpy(p, iov[3].buffer.value, iov[3].buffer.length);
+    p += iov[3].buffer.length;
+    memcpy(p, iov[4].buffer.value, iov[4].buffer.length);
+    p += iov[4].buffer.length;
+    memcpy(p, iov[5].buffer.value, iov[5].buffer.length);
+    p += iov[5].buffer.length;
+
+    assert(p - ((unsigned char *)token.data) == token.length);
+
+    if ((flags & (USE_SIGN_ONLY|FORCE_IOV)) == 0) {
+       gss_buffer_desc input, output;
+
+       input.value = token.data;
+       input.length = token.length;
+
+       maj_stat = gss_unwrap(&min_stat, sctx, &input,
+                             &output, &conf_state2, &qop_state);
+
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "gss_unwrap from gss_wrap_iov failed: %s",
+                gssapi_err(maj_stat, min_stat, mechoid));
+
+       gss_release_buffer(&min_stat, &output);
+    } else {
+       maj_stat = gss_unwrap_iov(&min_stat, sctx, &conf_state2, &qop_state,
+                                 iov, iov_len);
+
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "gss_unwrap_iov failed: %x %s", flags,
+                gssapi_err(maj_stat, min_stat, mechoid));
+
+    }
+    if (conf_state2 != conf_state)
+       errx(1, "conf state wrong for iov: %x", flags);
+
+    gss_release_iov_buffer(&min_stat, iov, iov_len);
+
+    free(token.data);
+}
+
+static void
+wrapunwrap_aead(gss_ctx_id_t cctx, gss_ctx_id_t sctx, int flags, gss_OID mechoid)
+{
+    gss_buffer_desc token, assoc, message = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc output;
+    OM_uint32 min_stat, maj_stat;
+    gss_qop_t qop_state;
+    int conf_state, conf_state2;
+    char assoc_data[9] = "ABCheader";
+    char token_data[16] = "0123456789abcdef";
+
+    if (flags & USE_SIGN_ONLY) {
+       assoc.value = assoc_data;
+       assoc.length = 9;
+    } else {
+       assoc.value = NULL;
+       assoc.length = 0;
+    }
+
+    token.value = token_data;
+    token.length = 16;
+
+    maj_stat = gss_wrap_aead(&min_stat, cctx, dce_style_flag || flags & USE_CONF,
+                            GSS_C_QOP_DEFAULT, &assoc, &token,
+                            &conf_state, &message);
+    if (maj_stat != GSS_S_COMPLETE)
+       errx(1, "gss_wrap_aead failed");
+
+    if ((flags & (USE_SIGN_ONLY|FORCE_IOV)) == 0) {
+       maj_stat = gss_unwrap(&min_stat, sctx, &message,
+                             &output, &conf_state2, &qop_state);
+
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "gss_unwrap from gss_wrap_aead failed: %s",
+                gssapi_err(maj_stat, min_stat, mechoid));
+    } else {
+       maj_stat = gss_unwrap_aead(&min_stat, sctx, &message, &assoc,
+                                  &output, &conf_state2, &qop_state);
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "gss_unwrap_aead failed: %x %s", flags,
+                gssapi_err(maj_stat, min_stat, mechoid));
+    }
+
+    if (output.length != token.length)
+       errx(1, "plaintext length wrong for aead");
+    else if (memcmp(output.value, token.value, token.length) != 0)
+       errx(1, "plaintext wrong for aead");
+    if (conf_state2 != conf_state)
+       errx(1, "conf state wrong for aead: %x", flags);
+
+    gss_release_buffer(&min_stat, &message);
+    gss_release_buffer(&min_stat, &output);
 }
 
 static void
@@ -236,8 +589,24 @@ getverifymic(gss_ctx_id_t cctx, gss_ctx_id_t sctx, gss_OID mechoid)
     if (maj_stat != GSS_S_COMPLETE)
        errx(1, "gss_verify_mic failed: %s",
             gssapi_err(maj_stat, min_stat, mechoid));
+
+    gss_release_buffer(&min_stat, &output_token);
 }
 
+static void
+empty_release(void)
+{
+    gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
+    gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
+    gss_name_t name = GSS_C_NO_NAME;
+    gss_OID_set oidset = GSS_C_NO_OID_SET;
+    OM_uint32 junk;
+
+    gss_delete_sec_context(&junk, &ctx, NULL);
+    gss_release_cred(&junk, &cred);
+    gss_release_name(&junk, &name);
+    gss_release_oid_set(&junk, &oidset);
+}
 
 /*
  *
@@ -245,19 +614,36 @@ getverifymic(gss_ctx_id_t cctx, gss_ctx_id_t sctx, gss_OID mechoid)
 
 static struct getargs args[] = {
     {"name-type",0,    arg_string, &type_string,  "type of name", NULL },
-    {"mech-type",0,    arg_string, &mech_string,  "type of mech", NULL },
+    {"mech-type",0,    arg_string, &mech_string,  "mech type (name)", NULL },
+    {"mech-types",0,   arg_string, &mechs_string, "mech types (names)", NULL },
     {"ret-mech-type",0,        arg_string, &ret_mech_string,
      "type of return mech", NULL },
-    {"dns-canonicalize",0,arg_negative_flag, &dns_canon_flag, 
+    {"dns-canonicalize",0,arg_negative_flag, &dns_canon_flag,
      "use dns to canonicalize", NULL },
     {"mutual-auth",0,  arg_flag,       &mutual_auth_flag,"mutual auth", NULL },
+    {"client-ccache",0, arg_string,    &client_ccache, "client credentials cache", NULL },
+    {"client-keytab",0, arg_string,    &client_keytab, "client keytab", NULL },
+    {"client-name", 0,  arg_string,     &client_name, "client name", NULL },
+    {"client-password", 0,  arg_string, &client_password, "client password", NULL },
+    {"limit-enctype",0,        arg_string,     &limit_enctype_string, "enctype", NULL },
     {"dce-style",0,    arg_flag,       &dce_style_flag, "dce-style", NULL },
     {"wrapunwrap",0,   arg_flag,       &wrapunwrap_flag, "wrap/unwrap", NULL },
-    {"getverifymic",0, arg_flag,       &getverifymic_flag, 
+    {"iov", 0,                 arg_flag,       &iov_flag, "wrap/unwrap iov", NULL },
+    {"aead", 0,        arg_flag,       &aead_flag, "wrap/unwrap aead", NULL },
+    {"getverifymic",0, arg_flag,       &getverifymic_flag,
      "get and verify mic", NULL },
     {"delegate",0,     arg_flag,       &deleg_flag, "delegate credential", NULL },
+    {"policy-delegate",0,      arg_flag,       &policy_deleg_flag, "policy delegate credential", NULL },
+    {"server-no-delegate",0,   arg_flag,       &server_no_deleg_flag,
+     "server should get a credential", NULL },
+    {"export-import-context",0,        arg_flag,       &ei_ctx_flag, "test export/import context", NULL },
+    {"export-import-cred",0,   arg_flag,       &ei_cred_flag, "test export/import cred", NULL },
+    {"localname",0,     arg_string, &localname_string, "expected localname for client", "USERNAME"},
     {"gsskrb5-acceptor-identity", 0, arg_string, &gsskrb5_acceptor_identity, "keytab", NULL },
     {"session-enctype",        0, arg_string,  &session_enctype_string, "enctype", NULL },
+    {"client-time-offset",     0, arg_integer, &client_time_offset, "time", NULL },
+    {"server-time-offset",     0, arg_integer, &server_time_offset, "time", NULL },
+    {"max-loops",      0, arg_integer, &max_loops, "time", NULL },
     {"version",        0,      arg_flag,       &version_flag, "print version", NULL },
     {"verbose",        'v',    arg_flag,       &verbose_flag, "verbose", NULL },
     {"help",   0,      arg_flag,       &help_flag,  NULL, NULL }
@@ -274,20 +660,33 @@ usage (int ret)
 int
 main(int argc, char **argv)
 {
-    int optind = 0;
+    int optidx = 0;
     OM_uint32 min_stat, maj_stat;
     gss_ctx_id_t cctx, sctx;
     void *ctx;
-    gss_OID nameoid, mechoid, actual_mech;
-    gss_cred_id_t deleg_cred = GSS_C_NO_CREDENTIAL;
+    gss_OID nameoid, mechoid, actual_mech, actual_mech2;
+    gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL, deleg_cred = GSS_C_NO_CREDENTIAL;
+    gss_name_t cname = GSS_C_NO_NAME;
+    gss_buffer_desc credential_data = GSS_C_EMPTY_BUFFER;
+    gss_OID_desc oids[6];
+    gss_OID_set_desc mechoid_descs;
+    gss_OID_set mechoids = GSS_C_NO_OID_SET;
+    gss_key_value_element_desc client_cred_elements[2];
+    gss_key_value_set_desc client_cred_store;
+    gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
 
     setprogname(argv[0]);
 
+    init_o2n();
+
+    if (krb5_init_context(&context))
+       errx(1, "krb5_init_context");
+
     cctx = sctx = GSS_C_NO_CONTEXT;
 
-    if(getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optind))
+    if(getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
        usage(1);
-    
+
     if (help_flag)
        usage (0);
 
@@ -296,8 +695,8 @@ main(int argc, char **argv)
        exit(0);
     }
 
-    argc -= optind;
-    argv += optind;
+    argc -= optidx;
+    argv += optidx;
 
     if (argc != 1)
        usage(1);
@@ -312,19 +711,177 @@ main(int argc, char **argv)
     else if (strcmp(type_string, "krb5-principal-name") == 0)
        nameoid = GSS_KRB5_NT_PRINCIPAL_NAME;
     else
-       errx(1, "%s not suppported", type_string);
+       errx(1, "%s not supported", type_string);
 
     if (mech_string == NULL)
        mechoid = GSS_KRB5_MECHANISM;
-    else 
+    else
        mechoid = string_to_oid(mech_string);
 
-    if (gsskrb5_acceptor_identity)
-       gsskrb5_register_acceptor_identity(gsskrb5_acceptor_identity);
+    if (mechs_string == NULL) {
+        /*
+         * We ought to be able to use the OID set of the one mechanism
+         * OID given.  But there's some breakage that conspires to make
+         * that fail though it should succeed:
+         *
+         *  - the NTLM gss_acquire_cred() refuses to work with
+         *    desired_name == GSS_C_NO_NAME
+         *  - gss_acquire_cred() with desired_mechs == GSS_C_NO_OID_SET
+         *    does work here because we happen to have Kerberos
+         *    credentials in check-ntlm, and the subsequent
+         *    gss_init_sec_context() call finds no cred element for NTLM
+         *    but plows on anyways, surprisingly enough, and then the
+         *    NTLM gss_init_sec_context() just works.
+         *
+         * In summary, there's some breakage in gss_init_sec_context()
+         * and some breakage in NTLM that conspires against us here.
+         *
+         * We work around this in check-ntlm and check-spnego by adding
+         * --client-name=user1@${R} to the invocations of this test
+         * program that require it.
+         */
+        oids[0] = *mechoid;
+        mechoid_descs.elements = &oids[0];
+        mechoid_descs.count = 1;
+        mechoids = &mechoid_descs;
+    } else {
+        string_to_oids(&mechoids, &mechoid_descs,
+                       oids, sizeof(oids)/sizeof(oids[0]), mechs_string);
+    }
+
+    if (gsskrb5_acceptor_identity) {
+       /* XXX replace this with cred store, but test suites will need work */
+       maj_stat = gsskrb5_register_acceptor_identity(gsskrb5_acceptor_identity);
+       if (maj_stat)
+           errx(1, "gsskrb5_acceptor_identity: %s",
+                gssapi_err(maj_stat, 0, GSS_C_NO_OID));
+    }
+
+    if (client_password && (client_ccache || client_keytab)) {
+       errx(1, "password option mutually exclusive with ccache or keytab option");
+    }
+
+    if (client_password) {
+       credential_data.value = client_password;
+       credential_data.length = strlen(client_password);
+    }
+
+    client_cred_store.count = 0;
+    client_cred_store.elements = client_cred_elements;
+
+    if (client_ccache) {
+       client_cred_store.elements[client_cred_store.count].key = "ccache";
+       client_cred_store.elements[client_cred_store.count].value = client_ccache;
+
+       client_cred_store.count++;
+    }
+
+    if (client_keytab) {
+       client_cred_store.elements[client_cred_store.count].key = "client_keytab";
+       client_cred_store.elements[client_cred_store.count].value = client_keytab;
+
+       client_cred_store.count++;
+    }
+
+    if (client_name) {
+       gss_buffer_desc cn;
+
+       cn.value = client_name;
+       cn.length = strlen(client_name);
+
+       maj_stat = gss_import_name(&min_stat, &cn, GSS_C_NT_USER_NAME, &cname);
+       if (maj_stat)
+           errx(1, "gss_import_name: %s",
+                gssapi_err(maj_stat, min_stat, GSS_C_NO_OID));
+    }
+
+    if (client_password) {
+       maj_stat = gss_acquire_cred_with_password(&min_stat,
+                                                 cname,
+                                                 &credential_data,
+                                                 GSS_C_INDEFINITE,
+                                                 mechoids,
+                                                 GSS_C_INITIATE,
+                                                 &client_cred,
+                                                 &actual_mechs,
+                                                 NULL);
+       if (GSS_ERROR(maj_stat)) {
+            if (mechoids != GSS_C_NO_OID_SET && mechoids->count == 1)
+                mechoid = &mechoids->elements[0];
+            else
+                mechoid = GSS_C_NO_OID;
+           errx(1, "gss_acquire_cred_with_password: %s",
+                gssapi_err(maj_stat, min_stat, mechoid));
+        }
+    } else {
+       maj_stat = gss_acquire_cred_from(&min_stat,
+                                        cname,
+                                        GSS_C_INDEFINITE,
+                                        mechoids,
+                                        GSS_C_INITIATE,
+                                        client_cred_store.count ? &client_cred_store
+                                                                : GSS_C_NO_CRED_STORE,
+                                        &client_cred,
+                                        &actual_mechs,
+                                        NULL);
+       if (GSS_ERROR(maj_stat))
+           errx(1, "gss_acquire_cred: %s",
+                gssapi_err(maj_stat, min_stat, GSS_C_NO_OID));
+    }
+
+    if (verbose_flag) {
+       size_t i;
+
+       printf("cred mechs:");
+       for (i = 0; i < actual_mechs->count; i++)
+           printf(" %s", oid_to_string(&actual_mechs->elements[i]));
+       printf("\n");
+    }
+
+    if (ei_cred_flag) {
+       gss_cred_id_t cred2 = GSS_C_NO_CREDENTIAL;
+       gss_buffer_desc cb;
+
+       maj_stat = gss_export_cred(&min_stat, client_cred, &cb);
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "export cred failed: %s",
+                gssapi_err(maj_stat, min_stat, NULL));
 
-    loop(mechoid, nameoid, argv[0], GSS_C_NO_CREDENTIAL,
+       maj_stat = gss_import_cred(&min_stat, &cb, &cred2);
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "import cred failed: %s",
+                gssapi_err(maj_stat, min_stat, NULL));
+
+       gss_release_buffer(&min_stat, &cb);
+       gss_release_cred(&min_stat, &client_cred);
+       client_cred = cred2;
+    }
+
+    if (limit_enctype_string) {
+       krb5_error_code ret;
+
+       ret = krb5_string_to_enctype(context,
+                                    limit_enctype_string,
+                                    &limit_enctype);
+       if (ret)
+           krb5_err(context, 1, ret, "krb5_string_to_enctype");
+    }
+
+
+    if (limit_enctype) {
+       if (client_cred == NULL)
+           errx(1, "client_cred missing");
+
+       maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, client_cred,
+                                                  1, &limit_enctype);
+       if (maj_stat)
+           errx(1, "gss_krb5_set_allowable_enctypes: %s",
+                gssapi_err(maj_stat, min_stat, GSS_C_NO_OID));
+    }
+
+    loop(mechoid, nameoid, argv[0], client_cred,
         &sctx, &cctx, &actual_mech, &deleg_cred);
-    
+
     if (verbose_flag)
        printf("resulting mech: %s\n", oid_to_string(actual_mech));
 
@@ -334,43 +891,38 @@ main(int argc, char **argv)
        retoid = string_to_oid(ret_mech_string);
 
        if (gss_oid_equal(retoid, actual_mech) == 0)
-           errx(1, "actual_mech mech is not the expected type %s", 
+           errx(1, "actual_mech mech is not the expected type %s",
                 ret_mech_string);
     }
 
     /* XXX should be actual_mech */
-    if (gss_oid_equal(mechoid, GSS_KRB5_MECHANISM)) { 
-       krb5_context context;
-       time_t time, skew;
+    if (gss_oid_equal(mechoid, GSS_KRB5_MECHANISM)) {
+       time_t sc_time;
        gss_buffer_desc authz_data;
        gss_buffer_desc in, out1, out2;
        krb5_keyblock *keyblock, *keyblock2;
        krb5_timestamp now;
        krb5_error_code ret;
 
-       ret = krb5_init_context(&context);
+       ret = krb5_timeofday(context, &now);
        if (ret)
-           errx(1, "krb5_init_context");
+           errx(1, "krb5_timeofday failed");
 
-       ret = krb5_timeofday(context, &now);
-       if (ret) 
-               errx(1, "krb5_timeofday failed");
-       
        /* client */
        maj_stat = gss_krb5_export_lucid_sec_context(&min_stat,
                                                     &cctx,
                                                     1, /* version */
                                                     &ctx);
        if (maj_stat != GSS_S_COMPLETE)
-               errx(1, "gss_krb5_export_lucid_sec_context failed: %s",
-                    gssapi_err(maj_stat, min_stat, actual_mech));
-       
-       
+           errx(1, "gss_krb5_export_lucid_sec_context failed: %s",
+                gssapi_err(maj_stat, min_stat, actual_mech));
+
+
        maj_stat = gss_krb5_free_lucid_sec_context(&maj_stat, ctx);
        if (maj_stat != GSS_S_COMPLETE)
            errx(1, "gss_krb5_free_lucid_sec_context failed: %s",
                     gssapi_err(maj_stat, min_stat, actual_mech));
-       
+
        /* server */
        maj_stat = gss_krb5_export_lucid_sec_context(&min_stat,
                                                     &sctx,
@@ -386,18 +938,15 @@ main(int argc, char **argv)
 
        maj_stat = gsskrb5_extract_authtime_from_sec_context(&min_stat,
                                                             sctx,
-                                                            &time);
+                                                            &sc_time);
        if (maj_stat != GSS_S_COMPLETE)
            errx(1, "gsskrb5_extract_authtime_from_sec_context failed: %s",
                     gssapi_err(maj_stat, min_stat, actual_mech));
 
-       skew = abs(time - now);
-       if (skew > krb5_get_max_time_skew(context)) {
+       if (sc_time > now)
            errx(1, "gsskrb5_extract_authtime_from_sec_context failed: "
-                "time skew too great %llu > %llu", 
-                (unsigned long long)skew, 
-                (unsigned long long)krb5_get_max_time_skew(context));
-       }
+                "time authtime is before now: %ld %ld",
+                (long)sc_time, (long)now);
 
        maj_stat = gsskrb5_extract_service_keyblock(&min_stat,
                                                    sctx,
@@ -411,24 +960,28 @@ main(int argc, char **argv)
        maj_stat = gsskrb5_get_subkey(&min_stat,
                                      sctx,
                                      &keyblock);
-       if (maj_stat != GSS_S_COMPLETE 
+       if (maj_stat != GSS_S_COMPLETE
            && (!(maj_stat == GSS_S_FAILURE && min_stat == GSS_KRB5_S_KG_NO_SUBKEY)))
            errx(1, "gsskrb5_get_subkey server failed: %s",
                     gssapi_err(maj_stat, min_stat, actual_mech));
 
        if (maj_stat != GSS_S_COMPLETE)
            keyblock = NULL;
-       
+       else if (limit_enctype && keyblock->keytype != limit_enctype)
+           errx(1, "gsskrb5_get_subkey wrong enctype");
+
        maj_stat = gsskrb5_get_subkey(&min_stat,
                                      cctx,
                                      &keyblock2);
-       if (maj_stat != GSS_S_COMPLETE 
+       if (maj_stat != GSS_S_COMPLETE
            && (!(maj_stat == GSS_S_FAILURE && min_stat == GSS_KRB5_S_KG_NO_SUBKEY)))
            errx(1, "gsskrb5_get_subkey client failed: %s",
                     gssapi_err(maj_stat, min_stat, actual_mech));
 
        if (maj_stat != GSS_S_COMPLETE)
            keyblock2 = NULL;
+       else if (limit_enctype && keyblock->keytype != limit_enctype)
+           errx(1, "gsskrb5_get_subkey wrong enctype");
 
        if (keyblock || keyblock2) {
            if (keyblock == NULL)
@@ -440,7 +993,7 @@ main(int argc, char **argv)
                errx(1, "enctype mismatch");
            if (keyblock->keyvalue.length != keyblock2->keyvalue.length)
                errx(1, "key length mismatch");
-           if (memcmp(keyblock->keyvalue.data, keyblock2->keyvalue.data, 
+           if (memcmp(keyblock->keyvalue.data, keyblock2->keyvalue.data,
                       keyblock2->keyvalue.length) != 0)
                errx(1, "key data mismatch");
        }
@@ -451,7 +1004,7 @@ main(int argc, char **argv)
            ret = krb5_string_to_enctype(context,
                                         session_enctype_string,
                                         &enctype);
-           
+
            if (ret)
                krb5_err(context, 1, ret, "krb5_string_to_enctype");
 
@@ -468,13 +1021,17 @@ main(int argc, char **argv)
        maj_stat = gsskrb5_get_initiator_subkey(&min_stat,
                                                sctx,
                                                &keyblock);
-       if (maj_stat != GSS_S_COMPLETE 
+       if (maj_stat != GSS_S_COMPLETE
            && (!(maj_stat == GSS_S_FAILURE && min_stat == GSS_KRB5_S_KG_NO_SUBKEY)))
            errx(1, "gsskrb5_get_initiator_subkey failed: %s",
                     gssapi_err(maj_stat, min_stat, actual_mech));
 
-       if (maj_stat == GSS_S_COMPLETE)
+       if (maj_stat == GSS_S_COMPLETE) {
+
+           if (limit_enctype && keyblock->keytype != limit_enctype)
+               errx(1, "gsskrb5_get_initiator_subkey wrong enctype");
            krb5_free_keyblock(context, keyblock);
+       }
 
        maj_stat = gsskrb5_extract_authz_data_from_sec_context(&min_stat,
                                                               sctx,
@@ -483,8 +1040,6 @@ main(int argc, char **argv)
        if (maj_stat == GSS_S_COMPLETE)
            gss_release_buffer(&min_stat, &authz_data);
 
-       krb5_free_context(context);
-
 
        memset(&out1, 0, sizeof(out1));
        memset(&out2, 0, sizeof(out2));
@@ -492,19 +1047,19 @@ main(int argc, char **argv)
        in.value = "foo";
        in.length = 3;
 
-       gss_pseudo_random(&min_stat, sctx, GSS_C_PRF_KEY_FULL, &in, 
+       gss_pseudo_random(&min_stat, sctx, GSS_C_PRF_KEY_FULL, &in,
                          100, &out1);
-       gss_pseudo_random(&min_stat, cctx, GSS_C_PRF_KEY_FULL, &in, 
+       gss_pseudo_random(&min_stat, cctx, GSS_C_PRF_KEY_FULL, &in,
                          100, &out2);
 
        if (out1.length != out2.length)
            errx(1, "prf len mismatch");
        if (memcmp(out1.value, out2.value, out1.length) != 0)
            errx(1, "prf data mismatch");
-       
+
        gss_release_buffer(&min_stat, &out1);
 
-       gss_pseudo_random(&min_stat, sctx, GSS_C_PRF_KEY_FULL, &in, 
+       gss_pseudo_random(&min_stat, sctx, GSS_C_PRF_KEY_FULL, &in,
                          100, &out1);
 
        if (out1.length != out2.length)
@@ -518,9 +1073,9 @@ main(int argc, char **argv)
        in.value = "bar";
        in.length = 3;
 
-       gss_pseudo_random(&min_stat, sctx, GSS_C_PRF_KEY_PARTIAL, &in, 
+       gss_pseudo_random(&min_stat, sctx, GSS_C_PRF_KEY_PARTIAL, &in,
                          100, &out1);
-       gss_pseudo_random(&min_stat, cctx, GSS_C_PRF_KEY_PARTIAL, &in, 
+       gss_pseudo_random(&min_stat, cctx, GSS_C_PRF_KEY_PARTIAL, &in,
                          100, &out2);
 
        if (out1.length != out2.length)
@@ -535,12 +1090,140 @@ main(int argc, char **argv)
        getverifymic_flag = 1;
     }
 
+    if (ei_ctx_flag) {
+       gss_buffer_desc ctx_token = GSS_C_EMPTY_BUFFER;
+
+       maj_stat = gss_export_sec_context(&min_stat, &cctx, &ctx_token);
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "export client context failed: %s",
+                gssapi_err(maj_stat, min_stat, NULL));
+
+       if (cctx != GSS_C_NO_CONTEXT)
+           errx(1, "export client context did not release it");
+
+       maj_stat = gss_import_sec_context(&min_stat, &ctx_token, &cctx);
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "import client context failed: %s",
+                gssapi_err(maj_stat, min_stat, NULL));
+
+       gss_release_buffer(&min_stat, &ctx_token);
+
+       maj_stat = gss_export_sec_context(&min_stat, &sctx, &ctx_token);
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "export server context failed: %s",
+                gssapi_err(maj_stat, min_stat, NULL));
+
+       if (sctx != GSS_C_NO_CONTEXT)
+           errx(1, "export server context did not release it");
+
+       maj_stat = gss_import_sec_context(&min_stat, &ctx_token, &sctx);
+       if (maj_stat != GSS_S_COMPLETE)
+           errx(1, "import server context failed: %s",
+                gssapi_err(maj_stat, min_stat, NULL));
+
+       gss_release_buffer(&min_stat, &ctx_token);
+    }
+
     if (wrapunwrap_flag) {
-       wrapunwrap(cctx, sctx, actual_mech);
-       wrapunwrap(cctx, sctx, actual_mech);
-       wrapunwrap(sctx, cctx, actual_mech);
-       wrapunwrap(sctx, cctx, actual_mech);
+       wrapunwrap(cctx, sctx, 0, actual_mech);
+       wrapunwrap(cctx, sctx, 1, actual_mech);
+       wrapunwrap(sctx, cctx, 0, actual_mech);
+       wrapunwrap(sctx, cctx, 1, actual_mech);
+    }
+
+    if (iov_flag) {
+       wrapunwrap_iov(cctx, sctx, 0, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_HEADER_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_HEADER_ONLY, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_HEADER_ONLY|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+/* works */
+       wrapunwrap_iov(cctx, sctx, 0, actual_mech);
+       wrapunwrap_iov(cctx, sctx, FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, USE_CONF, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, USE_SIGN_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_SIGN_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, USE_HEADER_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_HEADER_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, USE_CONF|USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_HEADER_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_HEADER_ONLY, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_HEADER_ONLY|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+ /* works */
+       wrapunwrap_iov(cctx, sctx, NO_DATA, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_SIGN_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_SIGN_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_HEADER_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_HEADER_ONLY, actual_mech);
+       wrapunwrap_iov(cctx, sctx, NO_DATA|USE_CONF|USE_HEADER_ONLY|FORCE_IOV, actual_mech);
+    }
+
+    if (aead_flag) {
+       wrapunwrap_aead(cctx, sctx, 0, actual_mech);
+       wrapunwrap_aead(cctx, sctx, USE_CONF, actual_mech);
+
+       wrapunwrap_aead(cctx, sctx, FORCE_IOV, actual_mech);
+       wrapunwrap_aead(cctx, sctx, USE_CONF|FORCE_IOV, actual_mech);
+
+       wrapunwrap_aead(cctx, sctx, USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+       wrapunwrap_aead(cctx, sctx, USE_CONF|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_aead(cctx, sctx, 0, actual_mech);
+       wrapunwrap_aead(cctx, sctx, FORCE_IOV, actual_mech);
+
+       wrapunwrap_aead(cctx, sctx, USE_CONF, actual_mech);
+       wrapunwrap_aead(cctx, sctx, USE_CONF|FORCE_IOV, actual_mech);
+
+       wrapunwrap_aead(cctx, sctx, USE_SIGN_ONLY, actual_mech);
+       wrapunwrap_aead(cctx, sctx, USE_SIGN_ONLY|FORCE_IOV, actual_mech);
+
+       wrapunwrap_aead(cctx, sctx, USE_CONF|USE_SIGN_ONLY, actual_mech);
+       wrapunwrap_aead(cctx, sctx, USE_CONF|USE_SIGN_ONLY|FORCE_IOV, actual_mech);
     }
+
     if (getverifymic_flag) {
        getverifymic(cctx, sctx, actual_mech);
        getverifymic(cctx, sctx, actual_mech);
@@ -552,13 +1235,92 @@ main(int argc, char **argv)
     gss_delete_sec_context(&min_stat, &sctx, NULL);
 
     if (deleg_cred != GSS_C_NO_CREDENTIAL) {
+       gss_cred_id_t cred2 = GSS_C_NO_CREDENTIAL;
+       gss_buffer_desc cb;
 
-       loop(mechoid, nameoid, argv[0], deleg_cred, &cctx, &sctx, &actual_mech, NULL);
+       if (verbose_flag)
+           printf("checking actual mech (%s) on delegated cred\n",
+                  oid_to_string(actual_mech));
+       loop(actual_mech, nameoid, argv[0], deleg_cred, &sctx, &cctx, &actual_mech2, &cred2);
 
        gss_delete_sec_context(&min_stat, &cctx, NULL);
        gss_delete_sec_context(&min_stat, &sctx, NULL);
 
+       gss_release_cred(&min_stat, &cred2);
+
+#if 0
+        /*
+         * XXX We can't do this.  Delegated credentials only work with
+         * the actual_mech.  We could gss_store_cred the delegated
+         * credentials *then* gss_add/acquire_cred() with SPNEGO, then
+         * we could try loop() with those credentials.
+         */
+       /* try again using SPNEGO */
+       if (verbose_flag)
+           printf("checking spnego on delegated cred\n");
+       loop(GSS_SPNEGO_MECHANISM, nameoid, argv[0], deleg_cred, &sctx, &cctx,
+            &actual_mech2, &cred2);
+
+       gss_delete_sec_context(&min_stat, &cctx, NULL);
+       gss_delete_sec_context(&min_stat, &sctx, NULL);
+
+       gss_release_cred(&min_stat, &cred2);
+#endif
+
+       /* check export/import */
+       if (ei_cred_flag) {
+
+           maj_stat = gss_export_cred(&min_stat, deleg_cred, &cb);
+           if (maj_stat != GSS_S_COMPLETE)
+               errx(1, "export cred failed: %s",
+                    gssapi_err(maj_stat, min_stat, NULL));
+
+           maj_stat = gss_import_cred(&min_stat, &cb, &cred2);
+           if (maj_stat != GSS_S_COMPLETE)
+               errx(1, "import cred failed: %s",
+                    gssapi_err(maj_stat, min_stat, NULL));
+
+           gss_release_buffer(&min_stat, &cb);
+           gss_release_cred(&min_stat, &deleg_cred);
+
+           if (verbose_flag)
+               printf("checking actual mech (%s) on export/imported cred\n",
+                      oid_to_string(actual_mech));
+           loop(actual_mech, nameoid, argv[0], cred2, &sctx, &cctx,
+                &actual_mech2, &deleg_cred);
+
+           gss_release_cred(&min_stat, &deleg_cred);
+
+           gss_delete_sec_context(&min_stat, &cctx, NULL);
+           gss_delete_sec_context(&min_stat, &sctx, NULL);
+
+#if 0
+            /* XXX See above */
+           /* try again using SPNEGO */
+           if (verbose_flag)
+               printf("checking SPNEGO on export/imported cred\n");
+           loop(GSS_SPNEGO_MECHANISM, nameoid, argv[0], cred2, &sctx, &cctx,
+                &actual_mech2, &deleg_cred);
+
+           gss_release_cred(&min_stat, &deleg_cred);
+
+           gss_delete_sec_context(&min_stat, &cctx, NULL);
+           gss_delete_sec_context(&min_stat, &sctx, NULL);
+#endif
+
+           gss_release_cred(&min_stat, &cred2);
+
+       } else  {
+           gss_release_cred(&min_stat, &deleg_cred);
+       }
+
     }
 
+    gss_release_cred(&min_stat, &client_cred);
+    gss_release_oid_set(&min_stat, &actual_mechs);
+    empty_release();
+
+    krb5_free_context(context);
+
     return 0;
 }