r21460: Fix for server-side processing of SPNEGO auth
authorJeremy Allison <jra@samba.org>
Tue, 20 Feb 2007 06:22:20 +0000 (06:22 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 17:18:06 +0000 (12:18 -0500)
fragmented into "max xmit" size security blob
chunks. Bug #4400. Needs limits adding, and also
a client-side version.
Jeremy.
(This used to be commit aa69f2481aafee5dccc3783b8a6e23ca4eb0dbfa)

source3/include/smb.h
source3/libsmb/asn1.c
source3/smbd/password.c
source3/smbd/sesssetup.c

index cc03722c2b73d9a9f7179fe1e90d653528abd2bd..cea7638d79be0a46a90a6b9cccaeb080d6eb1eb1 100644 (file)
@@ -1719,6 +1719,15 @@ struct pwd_info {
        fstring password;
 };
 
+/* For split krb5 SPNEGO blobs. */
+struct pending_auth_data {
+       struct pending_auth_data *prev, *next;
+       uint16 vuid; /* Tag for this entry. */
+       uint16 smbpid; /* Alternate tag for this entry. */
+       size_t needed_len;
+       DATA_BLOB partial_data;
+};
+
 typedef struct user_struct {
        struct user_struct *next, *prev;
        uint16 vuid; /* Tag for this entry. */
@@ -1748,7 +1757,6 @@ typedef struct user_struct {
        struct auth_serversupplied_info *server_info;
 
        struct auth_ntlmssp_state *auth_ntlmssp_state;
-
 } user_struct;
 
 struct unix_error_map {
index 937e063c62596393498b83f9319d05faecd22e67..6ebe9ab62cf88405106e877b345cac94d503531b 100644 (file)
 /* free an asn1 structure */
 void asn1_free(ASN1_DATA *data)
 {
+       struct nesting *nesting = data->nesting;
+
+       while (nesting) {
+               struct nesting *nnext = nesting->next;
+               free(nesting);
+               nesting = nnext;
+       };
+       data->nesting = NULL;
        SAFE_FREE(data->data);
 }
 
index 10cb9202374281a0f1b88ab9fbd43d19b6a796dd..bf4e9258ff266307887b00223fb29c012e70eba9 100644 (file)
@@ -111,6 +111,7 @@ void invalidate_vuid(uint16 vuid)
 
        SAFE_FREE(vuser->groups);
        TALLOC_FREE(vuser->nt_user_token);
+
        SAFE_FREE(vuser);
        num_validated_vuids--;
 }
index 6c5e8f678f26e063ad10b6ac91971118088e33b6..9b9ae4c3533cee9db6ec8aacdc1c34b3bb2d32ec 100644 (file)
@@ -658,6 +658,183 @@ static int reply_spnego_auth(connection_struct *conn, char *inbuf, char *outbuf,
        return -1;
 }
 
+/****************************************************************************
+ List to store partial SPNEGO auth fragments.
+****************************************************************************/
+
+static struct pending_auth_data *pd_list;
+
+/****************************************************************************
+ Delete an entry on the list.
+****************************************************************************/
+
+static void delete_partial_auth(struct pending_auth_data *pad)
+{
+       DLIST_REMOVE(pd_list, pad);
+       data_blob_free(&pad->partial_data);
+       SAFE_FREE(pad);
+}
+
+/****************************************************************************
+ Search for a partial SPNEGO auth fragment matching an smbpid.
+****************************************************************************/
+
+static struct pending_auth_data *get_pending_auth_data(uint16 smbpid)
+{
+       struct pending_auth_data *pad;
+
+       for (pad = pd_list; pad; pad = pad->next) {
+               if (pad->smbpid == smbpid) {
+                       break;
+               }
+       }
+       return pad;
+}
+
+/****************************************************************************
+ Check the size of an SPNEGO blob. If we need more return NT_STATUS_MORE_PROCESSING_REQUIRED,
+ else return NT_STATUS_OK.
+****************************************************************************/
+
+static NTSTATUS check_spnego_blob_complete(uint16 smbpid, uint16 vuid, DATA_BLOB *pblob)
+{
+       struct pending_auth_data *pad;
+       ASN1_DATA data;
+       size_t needed_len = 0;
+
+       /* Ensure we have some data. */
+       if (pblob->length == 0) {
+               /* Caller can cope. */
+               DEBUG(2,("check_spnego_blob_complete: zero blob length !\n"));
+               delete_partial_auth(pad);
+               return NT_STATUS_OK;
+       }
+
+       pad = get_pending_auth_data(smbpid);
+
+       /* Were we waiting for more data ? */
+       if (pad) {
+               DATA_BLOB tmp_blob;
+
+               /* Integer wrap paranoia.... */
+
+               if (pad->partial_data.length + pblob->length < pad->partial_data.length ||
+                   pad->partial_data.length + pblob->length < pblob->length) {
+
+                       DEBUG(2,("check_spnego_blob_complete: integer wrap "
+                               "pad->partial_data.length = %u, "
+                               "pblob->length = %u\n",
+                               (unsigned int)pad->partial_data.length,
+                               (unsigned int)pblob->length ));
+
+                       delete_partial_auth(pad);
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+
+               DEBUG(10,("check_spnego_blob_complete: "
+                       "pad->partial_data.length = %u, "
+                       "pad->needed_len = %u, "
+                       "pblob->length = %u,\n",
+                       (unsigned int)pad->partial_data.length,
+                       (unsigned int)pad->needed_len,
+                       (unsigned int)pblob->length ));
+
+               tmp_blob = data_blob(NULL,
+                               pad->partial_data.length + pblob->length);
+
+               /* Concatenate the two. */
+               memcpy(tmp_blob.data,
+                       pad->partial_data.data,
+                       pad->partial_data.length);
+               memcpy(tmp_blob.data + pad->partial_data.length,
+                       pblob->data,
+                       pblob->length);
+
+               /* Replace the partial data. */
+               data_blob_free(&pad->partial_data);
+               pad->partial_data = tmp_blob;
+               ZERO_STRUCT(tmp_blob);
+
+               /* Are we done ? */
+               if (pblob->length >= pad->needed_len) {
+                       /* Yes, replace pblob. */
+                       data_blob_free(pblob);
+                       *pblob = pad->partial_data;
+                       ZERO_STRUCT(pad->partial_data);
+                       delete_partial_auth(pad);
+                       return NT_STATUS_OK;
+               }
+
+               /* Still need more data. */
+               pad->needed_len -= pblob->length;
+               return NT_STATUS_MORE_PROCESSING_REQUIRED;
+       }
+
+       if ((pblob->data[0] != ASN1_APPLICATION(0)) &&
+           (pblob->data[0] != ASN1_CONTEXT(1))) {
+               /* Not something we can determine the
+                * length of.
+                */
+               return NT_STATUS_OK;
+       }
+
+       /* This is a new SPNEGO sessionsetup - see if
+        * the data given in this blob is enough.
+        */
+
+       asn1_load(&data, *pblob);
+       asn1_start_tag(&data, pblob->data[0]);
+       if (data.has_error || data.nesting == NULL) {
+               asn1_free(&data);
+               /* Let caller catch. */
+               return NT_STATUS_OK;
+       }
+
+       /* Integer wrap paranoia.... */
+
+       if (data.nesting->taglen + data.nesting->start < data.nesting->taglen ||
+           data.nesting->taglen + data.nesting->start < data.nesting->start) {
+
+               DEBUG(2,("check_spnego_blob_complete: integer wrap "
+                       "data.nesting->taglen = %u, "
+                       "data.nesting->start = %u\n",
+                       (unsigned int)data.nesting->taglen,
+                       (unsigned int)data.nesting->start ));
+
+               asn1_free(&data);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       /* Total length of the needed asn1 is the tag length
+        * plus the current offset. */
+
+       needed_len = data.nesting->taglen + data.nesting->start;
+       asn1_free(&data);
+
+       DEBUG(10,("check_spnego_blob_complete: needed_len = %u, "
+               "pblob->length = %u\n",
+               (unsigned int)needed_len,
+               (unsigned int)pblob->length ));
+
+       if (needed_len <= pblob->length) {
+               /* Nothing to do - blob is complete. */
+               return NT_STATUS_OK;
+       }
+
+       /* We must store this blob until complete. */
+       pad = SMB_MALLOC(sizeof(struct pending_auth_data));
+       if (!pad) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       pad->needed_len = needed_len - pblob->length;
+       pad->partial_data = data_blob(pblob->data, pblob->length);
+       pad->smbpid = smbpid;
+       pad->vuid = vuid;
+       DLIST_ADD(pd_list, pad);
+
+       return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
 /****************************************************************************
  Reply to a session setup command.
  conn POINTER CAN BE NULL HERE !
@@ -677,6 +854,8 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf,
        enum remote_arch_types ra_type = get_remote_arch();
        int vuid = SVAL(inbuf,smb_uid);
        user_struct *vuser = NULL;
+       NTSTATUS status = NT_STATUS_OK;
+       uint16 smbpid = SVAL(inbuf,smb_pid);
 
        DEBUG(3,("Doing spnego session setup\n"));
 
@@ -715,16 +894,28 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf,
                /* Windows 2003 doesn't set the native lanman string, 
                   but does set primary domain which is a bug I think */
                           
-               if ( !strlen(native_lanman) )
+               if ( !strlen(native_lanman) ) {
                        ra_lanman_string( primary_domain );
-               else
+               } else {
                        ra_lanman_string( native_lanman );
+               }
        }
                
        vuser = get_partial_auth_user_struct(vuid);
+       if (!vuser) {
+               struct pending_auth_data *pad = get_pending_auth_data(smbpid);
+               if (pad) {
+                       DEBUG(10,("reply_sesssetup_and_X_spnego: found pending vuid %u\n",
+                               (unsigned int)pad->vuid ));
+                       vuid = pad->vuid;
+                       vuser = get_partial_auth_user_struct(vuid);
+               }
+       }
+
        if (!vuser) {
                vuid = register_vuid(NULL, data_blob(NULL, 0), data_blob(NULL, 0), NULL);
                if (vuid == UID_FIELD_INVALID ) {
+                       data_blob_free(&blob1);
                        return ERROR_NT(nt_status_squash(NT_STATUS_INVALID_PARAMETER));
                }
        
@@ -732,11 +923,27 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf,
        }
 
        if (!vuser) {
+               data_blob_free(&blob1);
                return ERROR_NT(nt_status_squash(NT_STATUS_INVALID_PARAMETER));
        }
        
        SSVAL(outbuf,smb_uid,vuid);
-       
+
+       /* Large (greater than 4k) SPNEGO blobs are split into multiple
+        * sessionsetup requests as the Windows limit on the security blob
+        * field is 4k. Bug #4400. JRA.
+        */
+
+       status = check_spnego_blob_complete(smbpid, vuid, &blob1);
+       if (!NT_STATUS_IS_OK(status)) {
+               if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+                       /* Real error - kill the intermediate vuid */
+                       invalidate_vuid(vuid);
+               }
+               data_blob_free(&blob1);
+               return ERROR_NT(nt_status_squash(status));
+       }
+
        if (blob1.data[0] == ASN1_APPLICATION(0)) {
                /* its a negTokenTarg packet */
                ret = reply_spnego_negotiate(conn, inbuf, outbuf, vuid, length, bufsize, blob1,
@@ -755,25 +962,24 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf,
 
        if (strncmp((char *)(blob1.data), "NTLMSSP", 7) == 0) {
                DATA_BLOB chal;
-               NTSTATUS nt_status;
                if (!vuser->auth_ntlmssp_state) {
-                       nt_status = auth_ntlmssp_start(&vuser->auth_ntlmssp_state);
-                       if (!NT_STATUS_IS_OK(nt_status)) {
+                       status = auth_ntlmssp_start(&vuser->auth_ntlmssp_state);
+                       if (!NT_STATUS_IS_OK(status)) {
                                /* Kill the intermediate vuid */
                                invalidate_vuid(vuid);
-                               
-                               return ERROR_NT(nt_status_squash(nt_status));
+                               data_blob_free(&blob1);
+                               return ERROR_NT(nt_status_squash(status));
                        }
                }
 
-               nt_status = auth_ntlmssp_update(vuser->auth_ntlmssp_state,
+               status = auth_ntlmssp_update(vuser->auth_ntlmssp_state,
                                                blob1, &chal);
                
                data_blob_free(&blob1);
                
                reply_spnego_ntlmssp(conn, inbuf, outbuf, vuid, 
                                           &vuser->auth_ntlmssp_state,
-                                          &chal, nt_status, False);
+                                          &chal, status, False);
                data_blob_free(&chal);
                return -1;
        }