r21845: Refactor the sessionsetupX code a little to allow us
authorJeremy Allison <jra@samba.org>
Thu, 15 Mar 2007 19:18:18 +0000 (19:18 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 17:18:37 +0000 (12:18 -0500)
to return a NT_STATUS_TIME_DIFFERENCE_AT_DC error to
a client when there's clock skew. Will help people
debug this. Prepare us for being able to return the
correct sessionsetupX "NT_STATUS_MORE_PROCESSING_REQUIRED"
error with associated krb5 clock skew error to allow
clients to re-sync time with us when we're eventually
able to be a KDC.
Jeremy.

source/configure.in
source/include/includes.h
source/libads/kerberos_verify.c
source/libads/krb5_errs.c
source/libsmb/clikrb5.c
source/smbd/sesssetup.c

index 5cd07924f65123829247bd58b4a49d2fffd31b64..0b2f8bd905311b53905fa3dbdcd21758fb82aaea 100644 (file)
@@ -3886,6 +3886,20 @@ if test x"$with_ads_support" != x"no"; then
                [Whether the type krb5_addresses type exists])
   fi
 
+  AC_CACHE_CHECK([whether krb5_mk_error takes 3 arguments MIT or 9 Heimdal],
+               samba_cv_HAVE_SHORT_KRB5_MK_ERROR_INTERFACE, [
+    AC_TRY_COMPILE([#include <krb5.h>],
+    [
+    krb5_mk_error(0,0,0);],
+    samba_cv_HAVE_SHORT_KRB5_MK_ERROR_INTERFACE=yes,
+    samba_cv_HAVE_SHORT_KRB5_MK_ERROR_INTERFACE=no)])
+
+  if test x"$samba_cv_HAVE_SHORT_KRB5_MK_ERROR_INTERFACE" = x"yes"; then
+    AC_DEFINE(HAVE_SHORT_KRB5_MK_ERROR_INTERFACE,1,
+              [whether krb5_mk_error takes 3 arguments MIT or 9 Heimdal])
+  fi
+
+
   #
   #
   # Now the decisions whether we can support krb5
index c71acd3866381cb800684772494dfce06101b192..5b81cfbfab4427315b7ba7eba73e01424ffb19a6 100644 (file)
@@ -1181,6 +1181,11 @@ void smb_krb5_get_init_creds_opt_free(krb5_context context,
                                    krb5_get_init_creds_opt *opt);
 krb5_error_code smb_krb5_get_init_creds_opt_alloc(krb5_context context,
                                    krb5_get_init_creds_opt **opt);
+krb5_error_code smb_krb5_mk_error(krb5_context context,
+                                       krb5_error_code error_code,
+                                       const krb5_principal server,
+                                       krb5_data *reply);
+
 #endif /* HAVE_KRB5 */
 
 
index 2c114b1240e6c61d269bf7826c85f9b52249121d..0ec03ef4bf2c54a2d0ac38f895d8bbfbee0ad4b8 100644 (file)
@@ -7,6 +7,7 @@
    Copyright (C) Guenther Deschner 2003, 2005
    Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
+   Copyright (C) Jeremy Allison 2007
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -37,9 +38,12 @@ const krb5_data *krb5_princ_component(krb5_context, krb5_principal, int );
  ads_keytab_add_entry function for details.
 ***********************************************************************************/
 
-static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context auth_context,
-                       const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt, 
-                       krb5_keyblock **keyblock)
+static BOOL ads_keytab_verify_ticket(krb5_context context,
+                                       krb5_auth_context auth_context,
+                                       const DATA_BLOB *ticket,
+                                       krb5_ticket **pp_tkt,
+                                       krb5_keyblock **keyblock,
+                                       krb5_error_code *perr)
 {
        krb5_error_code ret = 0;
        BOOL auth_ok = False;
@@ -51,6 +55,11 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
        fstring my_name, my_fqdn;
        int i;
        int number_matched_principals = 0;
+       krb5_data packet;
+
+       *pp_tkt = NULL;
+       *keyblock = NULL;
+       *perr = 0;
 
        /* Generate the list of principal names which we expect
         * clients might want to use for authenticating to the file
@@ -103,11 +112,11 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
                        }
 
                        number_matched_principals++;
-                       p_packet->length = ticket->length;
-                       p_packet->data = (char *)ticket->data;
+                       packet.length = ticket->length;
+                       packet.data = (char *)ticket->data;
                        *pp_tkt = NULL;
 
-                       ret = krb5_rd_req_return_keyblock_from_keytab(context, &auth_context, p_packet,
+                       ret = krb5_rd_req_return_keyblock_from_keytab(context, &auth_context, &packet,
                                                                      kt_entry.principal, keytab,
                                                                      NULL, pp_tkt, keyblock);
 
@@ -125,7 +134,8 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
                                * entries - Guenther */
                                        
                                if (ret == KRB5KRB_AP_ERR_TKT_NYV || 
-                                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED) {
+                                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
+                                   ret == KRB5KRB_AP_ERR_SKEW) {
                                        break;
                                }
                        } else {
@@ -184,6 +194,7 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
        if (keytab) {
                krb5_kt_close(context, keytab);
        }
+       *perr = ret;
        return auth_ok;
 }
 
@@ -191,32 +202,40 @@ static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context aut
  Try to verify a ticket using the secrets.tdb.
 ***********************************************************************************/
 
-static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context,
-                                     krb5_principal host_princ,
-                                     const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt,
-                                     krb5_keyblock **keyblock)
+static krb5_error_code ads_secrets_verify_ticket(krb5_context context,
+                                               krb5_auth_context auth_context,
+                                               krb5_principal host_princ,
+                                               const DATA_BLOB *ticket,
+                                               krb5_ticket **pp_tkt,
+                                               krb5_keyblock **keyblock,
+                                               krb5_error_code *perr)
 {
        krb5_error_code ret = 0;
        BOOL auth_ok = False;
        char *password_s = NULL;
        krb5_data password;
        krb5_enctype enctypes[4] = { ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, 0, 0 };
+       krb5_data packet;
        int i;
 
+       *pp_tkt = NULL;
+       *keyblock = NULL;
+       *perr = 0;
+
 #if defined(ENCTYPE_ARCFOUR_HMAC)
        enctypes[2] = ENCTYPE_ARCFOUR_HMAC;
 #endif
 
-       ZERO_STRUCTP(keyblock);
-
        if (!secrets_init()) {
                DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n"));
+               *perr = KRB5_CONFIG_CANTOPEN;
                return False;
        }
 
        password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
        if (!password_s) {
                DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n"));
+               *perr = KRB5_LIBOS_CANTREADPWD;
                return False;
        }
 
@@ -225,14 +244,15 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
        /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */
 
-       p_packet->length = ticket->length;
-       p_packet->data = (char *)ticket->data;
+       packet.length = ticket->length;
+       packet.data = (char *)ticket->data;
 
        /* We need to setup a auth context with each possible encoding type in turn. */
        for (i=0;enctypes[i];i++) {
                krb5_keyblock *key = NULL;
 
                if (!(key = SMB_MALLOC_P(krb5_keyblock))) {
+                       ret = ENOMEM;
                        goto out;
                }
        
@@ -243,7 +263,7 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
                krb5_auth_con_setuseruserkey(context, auth_context, key);
 
-               if (!(ret = krb5_rd_req(context, &auth_context, p_packet, 
+               if (!(ret = krb5_rd_req(context, &auth_context, &packet, 
                                        NULL,
                                        NULL, NULL, pp_tkt))) {
                        DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n",
@@ -260,7 +280,8 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
                /* successfully decrypted but ticket is just not valid at the moment */
                if (ret == KRB5KRB_AP_ERR_TKT_NYV || 
-                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED) {
+                   ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
+                   ret == KRB5KRB_AP_ERR_SKEW) {
                        break;
                }
 
@@ -270,7 +291,7 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 
  out:
        SAFE_FREE(password_s);
-
+       *perr = ret;
        return auth_ok;
 }
 
@@ -280,9 +301,11 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au
 ***********************************************************************************/
 
 NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
-                          const char *realm, time_t time_offset,
-                          const DATA_BLOB *ticket, 
-                          char **principal, PAC_DATA **pac_data,
+                          const char *realm,
+                          time_t time_offset,
+                          const DATA_BLOB *ticket,
+                          char **principal,
+                          PAC_DATA **pac_data,
                           DATA_BLOB *ap_rep,
                           DATA_BLOB *session_key)
 {
@@ -296,20 +319,22 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        krb5_rcache rcache = NULL;
        krb5_keyblock *keyblock = NULL;
        time_t authtime;
-       int ret;
-
+       krb5_error_code ret = 0;
+       
        krb5_principal host_princ = NULL;
        krb5_const_principal client_principal = NULL;
        char *host_princ_s = NULL;
-       BOOL got_replay_mutex = False;
-
        BOOL auth_ok = False;
+       BOOL got_replay_mutex = False;
        BOOL got_auth_data = False;
 
        ZERO_STRUCT(packet);
        ZERO_STRUCT(auth_data);
-       ZERO_STRUCTP(ap_rep);
-       ZERO_STRUCTP(session_key);
+
+       *principal = NULL;
+       *pac_data = NULL;
+       *ap_rep = data_blob(NULL,0);
+       *session_key = data_blob(NULL,0);
 
        initialize_krb5_error_table();
        ret = krb5_init_context(&context);
@@ -339,6 +364,10 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        }
 
        asprintf(&host_princ_s, "%s$", global_myname());
+       if (!host_princ_s) {
+               goto out;
+       }
+
        strlower_m(host_princ_s);
        ret = smb_krb5_parse_name(context, host_princ_s, &host_princ);
        if (ret) {
@@ -353,6 +382,7 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
 
        if (!grab_server_mutex("replay cache mutex")) {
                DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n"));
+               ret = KRB5_CC_IO;
                goto out;
        }
 
@@ -375,11 +405,11 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        }
 
        if (lp_use_kerberos_keytab()) {
-               auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &packet, &tkt, &keyblock);
+               auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &tkt, &keyblock, &ret);
        }
        if (!auth_ok) {
                auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ,
-                                                   ticket, &packet, &tkt, &keyblock);
+                                                   ticket, &tkt, &keyblock, &ret);
        }
 
        release_server_mutex();
@@ -395,6 +425,15 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        if (!auth_ok) {
                DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", 
                         error_message(ret)));
+               /* Try map the error return in case it's something like
+                * a clock skew error.
+                */
+               sret = krb5_to_nt_status(ret);
+               if (NT_STATUS_IS_OK(sret) || NT_STATUS_EQUAL(sret,NT_STATUS_UNSUCCESSFUL)) {
+                       sret = NT_STATUS_LOGON_FAILURE;
+               }
+               DEBUG(10,("ads_verify_ticket: returning error %s\n",
+                       nt_errstr(sret) ));
                goto out;
        } 
        
@@ -409,8 +448,10 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
        }
 
        *ap_rep = data_blob(packet.data, packet.length);
-       SAFE_FREE(packet.data);
-       packet.length = 0;
+       if (packet.data) {
+               kerberos_free_data_contents(context, &packet);
+               ZERO_STRUCT(packet);
+       }
 
        get_krb5_smb_session_key(context, auth_context, session_key, True);
        dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length);
index 89cfc2d14397622538982e4b15f392d61cac6498..c153bee96e6ddebfe9e5ce146324bbf8507fa516 100644 (file)
@@ -59,6 +59,8 @@ static const struct {
        {KRB5_CC_NOTFOUND, NT_STATUS_NO_SUCH_FILE},
        {KRB5_FCC_NOFILE, NT_STATUS_NO_SUCH_FILE},
        {KRB5KDC_ERR_NONE, NT_STATUS_OK},
+       {KRB5_RC_MALLOC, NT_STATUS_NO_MEMORY},
+       {ENOMEM, NT_STATUS_NO_MEMORY},
        {0, NT_STATUS_OK}
 };
 
index 43dfddda472355d7ad59c0a478922e43a1d72f5d..659197214fe54b9b0678c70a66c2c99c35d779f9 100644 (file)
@@ -75,6 +75,7 @@ static krb5_error_code smb_krb5_parse_name_norealm_conv(krb5_context context,
        krb5_error_code ret;
        char *utf8_name;
 
+       *principal = NULL;
        if (push_utf8_allocate(&utf8_name, name) == (size_t)-1) {
                return ENOMEM;
        }
@@ -97,6 +98,7 @@ static krb5_error_code smb_krb5_parse_name_norealm_conv(krb5_context context,
        krb5_error_code ret;
        char *utf8_name;
 
+       *unix_name = NULL;
        ret = krb5_unparse_name(context, principal, &utf8_name);
        if (ret) {
                return ret;
@@ -1430,6 +1432,37 @@ done:
        SAFE_FREE(opt);
        opt = NULL;
 #endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_FREE */
+}
+
+ krb5_error_code smb_krb5_mk_error(krb5_context context,
+                               krb5_error_code error_code,
+                               const krb5_principal server,
+                               krb5_data *reply)
+{
+#ifdef HAVE_SHORT_KRB5_MK_ERROR_INTERFACE /* MIT */
+       /*
+        * The MIT interface is *terrible*.
+        * We have to construct this ourselves...
+        */
+       krb5_error e;
+
+       memset(&e, 0, sizeof(e));
+       krb5_us_timeofday(context, &e.stime, &e.susec);
+       e.server = server;
+       e.error = error_code - krb5_err_base;
+
+       return krb5_mk_error(context, &e, reply);
+#else /* Heimdal. */
+       return krb5_mk_error(context,
+                               error_code,
+                               NULL,
+                               NULL, /* e_data */
+                               NULL,
+                               server,
+                               NULL,
+                               NULL,
+                               reply);
+#endif
 }
 
 #else /* HAVE_KRB5 */
index 7a5f8be47ff711b676115c4be8010756fbbd84ca..e678d959d8230adf9ae1d15ee7b6e5298f1d5009 100644 (file)
@@ -158,13 +158,75 @@ static NTSTATUS check_guest_password(auth_serversupplied_info **server_info)
 
 
 #ifdef HAVE_KRB5
+
+#if 0
+/* Experiment that failed. See "only happens with a KDC" comment below. */
+/****************************************************************************
+ Cerate a clock skew error blob for a Windows client.
+****************************************************************************/
+
+static BOOL make_krb5_skew_error(DATA_BLOB *pblob_out)
+{
+       krb5_context context = NULL;
+       krb5_error_code kerr = 0;
+       krb5_data reply;
+       krb5_principal host_princ = NULL;
+       char *host_princ_s = NULL;
+       BOOL ret = False;
+
+       *pblob_out = data_blob(NULL,0);
+
+       kerr = krb5_init_context(&context);
+       if (kerr) {
+               return False;
+       }
+       /* Create server principal. */
+       asprintf(&host_princ_s, "%s$@%s", global_myname(), lp_realm());
+       if (!host_princ_s) {
+               goto out;
+       }
+       strlower_m(host_princ_s);
+
+       kerr = smb_krb5_parse_name(context, host_princ_s, &host_princ);
+       if (kerr) {
+               DEBUG(10,("make_krb5_skew_error: smb_krb5_parse_name failed for name %s: Error %s\n",
+                       host_princ_s, error_message(kerr) ));
+               goto out;
+       }
+       
+       kerr = smb_krb5_mk_error(context, KRB5KRB_AP_ERR_SKEW, host_princ, &reply);
+       if (kerr) {
+               DEBUG(10,("make_krb5_skew_error: smb_krb5_mk_error failed: Error %s\n",
+                       error_message(kerr) ));
+               goto out;
+       }
+
+       *pblob_out = data_blob(reply.data, reply.length);
+       kerberos_free_data_contents(context,&reply);
+       ret = True;
+
+  out:
+
+       if (host_princ_s) {
+               SAFE_FREE(host_princ_s);
+       }
+       if (host_princ) {
+               krb5_free_principal(context, host_princ);
+       }
+       krb5_free_context(context);
+       return ret;
+}
+#endif
+
 /****************************************************************************
-reply to a session setup spnego negotiate packet for kerberos
+ Reply to a session setup spnego negotiate packet for kerberos.
 ****************************************************************************/
+
 static int reply_spnego_kerberos(connection_struct *conn, 
                                 char *inbuf, char *outbuf,
                                 int length, int bufsize,
-                                DATA_BLOB *secblob)
+                                DATA_BLOB *secblob,
+                                BOOL *p_invalidate_vuid)
 {
        TALLOC_CTX *mem_ctx;
        DATA_BLOB ticket;
@@ -191,9 +253,13 @@ static int reply_spnego_kerberos(connection_struct *conn,
        ZERO_STRUCT(ap_rep_wrapped);
        ZERO_STRUCT(response);
 
+       /* Normally we will always invalidate the intermediate vuid. */
+       *p_invalidate_vuid = True;
+
        mem_ctx = talloc_init("reply_spnego_kerberos");
-       if (mem_ctx == NULL)
+       if (mem_ctx == NULL) {
                return ERROR_NT(nt_status_squash(NT_STATUS_NO_MEMORY));
+       }
 
        if (!spnego_parse_krb5_wrap(*secblob, &ticket, tok_id)) {
                talloc_destroy(mem_ctx);
@@ -205,9 +271,50 @@ static int reply_spnego_kerberos(connection_struct *conn,
        data_blob_free(&ticket);
 
        if (!NT_STATUS_IS_OK(ret)) {
-               DEBUG(1,("Failed to verify incoming ticket!\n"));       
+#if 0
+               /* Experiment that failed. See "only happens with a KDC" comment below. */
+
+               if (NT_STATUS_EQUAL(ret, NT_STATUS_TIME_DIFFERENCE_AT_DC)) {
+
+                       /*
+                        * Windows in this case returns NT_STATUS_MORE_PROCESSING_REQUIRED
+                        * with a negTokenTarg blob containing an krb5_error struct ASN1 encoded
+                        * containing KRB5KRB_AP_ERR_SKEW. The client then fixes its
+                        * clock and continues rather than giving an error. JRA.
+                        * -- Looks like this only happens with a KDC. JRA.
+                        */
+
+                       BOOL ok = make_krb5_skew_error(&ap_rep);
+                       if (!ok) {
+                               talloc_destroy(mem_ctx);
+                               return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
+                       }
+                       ap_rep_wrapped = spnego_gen_krb5_wrap(ap_rep, TOK_ID_KRB_ERROR);
+                       response = spnego_gen_auth_response(&ap_rep_wrapped, ret, OID_KERBEROS5_OLD);
+                       reply_sesssetup_blob(conn, outbuf, response, NT_STATUS_MORE_PROCESSING_REQUIRED);
+
+                       /*
+                        * In this one case we don't invalidate the intermediate vuid.
+                        * as we're expecting the client to re-use it for the next
+                        * sessionsetupX packet. JRA.
+                        */
+
+                       *p_invalidate_vuid = False;
+
+                       data_blob_free(&ap_rep);
+                       data_blob_free(&ap_rep_wrapped);
+                       data_blob_free(&response);
+                       talloc_destroy(mem_ctx);
+                       return -1; /* already replied */
+               }
+#else
+               if (!NT_STATUS_EQUAL(ret, NT_STATUS_TIME_DIFFERENCE_AT_DC)) {
+                       ret = NT_STATUS_LOGON_FAILURE;
+               }
+#endif
+               DEBUG(1,("Failed to verify incoming ticket with error %s!\n", nt_errstr(ret))); 
                talloc_destroy(mem_ctx);
-               return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
+               return ERROR_NT(nt_status_squash(ret));
        }
 
        DEBUG(3,("Ticket name is [%s]\n", client));
@@ -523,32 +630,19 @@ static BOOL reply_spnego_ntlmssp(connection_struct *conn, char *inbuf, char *out
 }
 
 /****************************************************************************
- Reply to a session setup spnego negotiate packet.
+ Is this a krb5 mechanism ?
 ****************************************************************************/
 
-static int reply_spnego_negotiate(connection_struct *conn, 
-                                 char *inbuf,
-                                 char *outbuf,
-                                 uint16 vuid,
-                                 int length, int bufsize,
-                                 DATA_BLOB blob1,
-                                 AUTH_NTLMSSP_STATE **auth_ntlmssp_state)
+static NTSTATUS parse_spnego_mechanisms(DATA_BLOB blob_in, DATA_BLOB *pblob_out, BOOL *p_is_krb5)
 {
        char *OIDs[ASN1_MAX_OIDS];
-       DATA_BLOB secblob;
        int i;
-       DATA_BLOB chal;
-#ifdef HAVE_KRB5
-       BOOL got_kerberos_mechanism = False;
-#endif
-       NTSTATUS nt_status;
 
-       /* parse out the OIDs and the first sec blob */
-       if (!parse_negTokenTarg(blob1, OIDs, &secblob)) {
-               /* Kill the intermediate vuid */
-               invalidate_vuid(vuid);
+       *p_is_krb5 = False;
 
-               return ERROR_NT(nt_status_squash(NT_STATUS_LOGON_FAILURE));
+       /* parse out the OIDs and the first sec blob */
+       if (!parse_negTokenTarg(blob_in, OIDs, pblob_out)) {
+               return NT_STATUS_LOGON_FAILURE;
        }
 
        /* only look at the first OID for determining the mechToken --
@@ -564,24 +658,53 @@ static int reply_spnego_negotiate(connection_struct *conn,
 #ifdef HAVE_KRB5       
        if (strcmp(OID_KERBEROS5, OIDs[0]) == 0 ||
            strcmp(OID_KERBEROS5_OLD, OIDs[0]) == 0) {
-               got_kerberos_mechanism = True;
+               *p_is_krb5 = True;
        }
 #endif
                
        for (i=0;OIDs[i];i++) {
-               DEBUG(3,("Got OID %s\n", OIDs[i]));
+               DEBUG(5,("parse_spnego_mechanisms: Got OID %s\n", OIDs[i]));
                free(OIDs[i]);
        }
-       DEBUG(3,("Got secblob of size %lu\n", (unsigned long)secblob.length));
+       return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to a session setup spnego negotiate packet.
+****************************************************************************/
+
+static int reply_spnego_negotiate(connection_struct *conn, 
+                                 char *inbuf,
+                                 char *outbuf,
+                                 uint16 vuid,
+                                 int length, int bufsize,
+                                 DATA_BLOB blob1,
+                                 AUTH_NTLMSSP_STATE **auth_ntlmssp_state)
+{
+       DATA_BLOB secblob;
+       DATA_BLOB chal;
+       BOOL got_kerberos_mechanism = False;
+       NTSTATUS status;
+
+       status = parse_spnego_mechanisms(blob1, &secblob, &got_kerberos_mechanism);
+       if (!NT_STATUS_IS_OK(status)) {
+               /* Kill the intermediate vuid */
+               invalidate_vuid(vuid);
+               return ERROR_NT(nt_status_squash(status));
+       }
+
+       DEBUG(3,("reply_spnego_negotiate: Got secblob of size %lu\n", (unsigned long)secblob.length));
 
 #ifdef HAVE_KRB5
        if ( got_kerberos_mechanism && ((lp_security()==SEC_ADS) || lp_use_kerberos_keytab()) ) {
+               BOOL destroy_vuid = True;
                int ret = reply_spnego_kerberos(conn, inbuf, outbuf, 
-                                               length, bufsize, &secblob);
+                                               length, bufsize, &secblob, &destroy_vuid);
                data_blob_free(&secblob);
-               /* Kill the intermediate vuid */
-               invalidate_vuid(vuid);
-
+               if (destroy_vuid) {
+                       /* Kill the intermediate vuid */
+                       invalidate_vuid(vuid);
+               }
                return ret;
        }
 #endif
@@ -590,28 +713,27 @@ static int reply_spnego_negotiate(connection_struct *conn,
                auth_ntlmssp_end(auth_ntlmssp_state);
        }
 
-       nt_status = auth_ntlmssp_start(auth_ntlmssp_state);
-       if (!NT_STATUS_IS_OK(nt_status)) {
+       status = auth_ntlmssp_start(auth_ntlmssp_state);
+       if (!NT_STATUS_IS_OK(status)) {
                /* Kill the intermediate vuid */
                invalidate_vuid(vuid);
-
-               return ERROR_NT(nt_status_squash(nt_status));
+               return ERROR_NT(nt_status_squash(status));
        }
 
-       nt_status = auth_ntlmssp_update(*auth_ntlmssp_state, 
+       status = auth_ntlmssp_update(*auth_ntlmssp_state, 
                                        secblob, &chal);
 
        data_blob_free(&secblob);
 
        reply_spnego_ntlmssp(conn, inbuf, outbuf, vuid, auth_ntlmssp_state,
-                            &chal, nt_status, True);
+                            &chal, status, True);
 
        data_blob_free(&chal);
 
        /* already replied */
        return -1;
 }
-       
+
 /****************************************************************************
  Reply to a session setup spnego auth packet.
 ****************************************************************************/
@@ -622,8 +744,10 @@ static int reply_spnego_auth(connection_struct *conn, char *inbuf, char *outbuf,
                             DATA_BLOB blob1,
                             AUTH_NTLMSSP_STATE **auth_ntlmssp_state)
 {
-       DATA_BLOB auth, auth_reply;
-       NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER;
+       DATA_BLOB auth = data_blob(NULL,0);
+       DATA_BLOB auth_reply = data_blob(NULL,0);
+       DATA_BLOB secblob = data_blob(NULL,0);
+       NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
 
        if (!spnego_parse_auth(blob1, &auth)) {
 #if 0
@@ -634,6 +758,33 @@ static int reply_spnego_auth(connection_struct *conn, char *inbuf, char *outbuf,
 
                return ERROR_NT(nt_status_squash(NT_STATUS_INVALID_PARAMETER));
        }
+
+       if (auth.data[0] == ASN1_APPLICATION(0)) {
+               /* Might be a second negTokenTarg packet */
+
+               BOOL got_krb5_mechanism = False;
+               status = parse_spnego_mechanisms(auth, &secblob, &got_krb5_mechanism);
+               if (NT_STATUS_IS_OK(status)) {
+                       DEBUG(3,("reply_spnego_auth: Got secblob of size %lu\n", (unsigned long)secblob.length));
+#ifdef HAVE_KRB5
+                       if ( got_krb5_mechanism && ((lp_security()==SEC_ADS) || lp_use_kerberos_keytab()) ) {
+                               BOOL destroy_vuid = True;
+                               int ret = reply_spnego_kerberos(conn, inbuf, outbuf, 
+                                                               length, bufsize, &secblob, &destroy_vuid);
+                               data_blob_free(&secblob);
+                               data_blob_free(&auth);
+                               if (destroy_vuid) {
+                                       /* Kill the intermediate vuid */
+                                       invalidate_vuid(vuid);
+                               }
+                               return ret;
+                       }
+#endif
+               }
+       }
+
+       /* If we get here it wasn't a negTokenTarg auth packet. */
+       data_blob_free(&secblob);
        
        if (!*auth_ntlmssp_state) {
                /* Kill the intermediate vuid */
@@ -643,14 +794,14 @@ static int reply_spnego_auth(connection_struct *conn, char *inbuf, char *outbuf,
                return ERROR_NT(nt_status_squash(NT_STATUS_INVALID_PARAMETER));
        }
        
-       nt_status = auth_ntlmssp_update(*auth_ntlmssp_state, 
+       status = auth_ntlmssp_update(*auth_ntlmssp_state, 
                                        auth, &auth_reply);
 
        data_blob_free(&auth);
 
        reply_spnego_ntlmssp(conn, inbuf, outbuf, vuid, 
                             auth_ntlmssp_state,
-                            &auth_reply, nt_status, True);
+                            &auth_reply, status, True);
                
        data_blob_free(&auth_reply);