Index: smbd/sesssetup.c =================================================================== --- smbd/sesssetup.c (revision 21459) +++ smbd/sesssetup.c (working copy) @@ -659,6 +659,183 @@ } /**************************************************************************** + 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 @@ 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 @@ /* 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 @@ } 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 @@ 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; } Index: smbd/password.c =================================================================== --- smbd/password.c (revision 21459) +++ smbd/password.c (working copy) @@ -111,6 +111,7 @@ SAFE_FREE(vuser->groups); TALLOC_FREE(vuser->nt_user_token); + SAFE_FREE(vuser); num_validated_vuids--; } Index: libsmb/asn1.c =================================================================== --- libsmb/asn1.c (revision 21459) +++ libsmb/asn1.c (working copy) @@ -23,6 +23,14 @@ /* 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: include/smb.h =================================================================== --- include/smb.h (revision 21459) +++ include/smb.h (working copy) @@ -1728,6 +1728,15 @@ 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. */ @@ -1757,7 +1766,6 @@ struct auth_serversupplied_info *server_info; struct auth_ntlmssp_state *auth_ntlmssp_state; - } user_struct; struct unix_error_map {