mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
811 lines
30 KiB
C
811 lines
30 KiB
C
/*
|
|
* LibHTP (http://www.libhtp.org)
|
|
* Copyright 2009,2010 Ivan Ristic <ivanr@webkreator.com>
|
|
*
|
|
* LibHTP is an open source product, released under terms of the General Public Licence
|
|
* version 2 (GPLv2). Please refer to the file LICENSE, which contains the complete text
|
|
* of the license.
|
|
*
|
|
* In addition, there is a special exception that allows LibHTP to be freely
|
|
* used with any OSI-approved open source licence. Please refer to the file
|
|
* LIBHTP_LICENSING_EXCEPTION for the full text of the exception.
|
|
*
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include "htp.h"
|
|
|
|
/**
|
|
* Invoked whenever decompressed response body data becomes available.
|
|
*
|
|
* @param d
|
|
* @return HTP_OK on state change, HTP_ERROR on error.
|
|
*/
|
|
static int htp_connp_RES_BODY_DECOMPRESSOR_CALLBACK(htp_tx_data_t *d) {
|
|
// Invoke all callbacks
|
|
int rc = hook_run_all(d->tx->connp->cfg->hook_response_body_data, d);
|
|
if (rc != HTP_OK) {
|
|
htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response body data callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
return HTP_OK;
|
|
}
|
|
|
|
/**
|
|
* Consumes bytes until the end of the current line.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_BODY_CHUNKED_DATA_END(htp_connp_t *connp) {
|
|
// TODO We shouldn't really see anything apart from CR and LF,
|
|
// so we should warn about anything else.
|
|
|
|
for (;;) {
|
|
OUT_NEXT_BYTE_OR_RETURN(connp);
|
|
|
|
connp->out_tx->request_message_len++;
|
|
|
|
if (connp->out_next_byte == LF) {
|
|
connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH;
|
|
|
|
return HTP_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes a chunk of data.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_BODY_CHUNKED_DATA(htp_connp_t *connp) {
|
|
htp_tx_data_t d;
|
|
|
|
d.tx = connp->out_tx;
|
|
d.data = &connp->out_current_data[connp->out_current_offset];
|
|
d.len = 0;
|
|
|
|
for (;;) {
|
|
OUT_NEXT_BYTE(connp);
|
|
|
|
if (connp->out_next_byte == -1) {
|
|
if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
|
|
connp->out_decompressor->decompress(connp->out_decompressor, &d);
|
|
} else {
|
|
// Send data to callbacks
|
|
int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
|
|
if (rc != HOOK_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response body data callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
}
|
|
|
|
// Ask for more data
|
|
return HTP_DATA;
|
|
} else {
|
|
connp->out_tx->response_message_len++;
|
|
connp->out_tx->response_entity_len++;
|
|
connp->out_chunked_length--;
|
|
d.len++;
|
|
|
|
if (connp->out_chunked_length == 0) {
|
|
// End of data chunk
|
|
|
|
if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
|
|
connp->out_decompressor->decompress(connp->out_decompressor, &d);
|
|
} else {
|
|
// Send data to callbacks
|
|
int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
|
|
if (rc != HOOK_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response body data callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
}
|
|
|
|
connp->out_state = htp_connp_RES_BODY_CHUNKED_DATA_END;
|
|
|
|
return HTP_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts chunk length.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_BODY_CHUNKED_LENGTH(htp_connp_t *connp) {
|
|
for (;;) {
|
|
OUT_COPY_BYTE_OR_RETURN(connp);
|
|
|
|
connp->out_tx->response_message_len++;
|
|
|
|
// Have we reached the end of the line?
|
|
if (connp->out_next_byte == LF) {
|
|
htp_chomp(connp->out_line, &connp->out_line_len);
|
|
|
|
// Extract chunk length
|
|
connp->out_chunked_length = htp_parse_chunked_length(connp->out_line, connp->out_line_len);
|
|
|
|
// Cleanup for the next line
|
|
connp->out_line_len = 0;
|
|
|
|
// Handle chunk length
|
|
if (connp->out_chunked_length > 0) {
|
|
// More data available
|
|
// TODO Add a check for chunk length
|
|
connp->out_state = htp_connp_RES_BODY_CHUNKED_DATA;
|
|
} else if (connp->out_chunked_length == 0) {
|
|
// End of data
|
|
connp->out_state = htp_connp_RES_HEADERS;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_TRAILER;
|
|
} else {
|
|
// Invalid chunk length
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response chunk encoding: Invalid chunk length: %d", connp->out_chunked_length);
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
return HTP_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes identity response body.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_BODY_IDENTITY(htp_connp_t *connp) {
|
|
htp_tx_data_t d;
|
|
|
|
d.tx = connp->out_tx;
|
|
d.data = &connp->out_current_data[connp->out_current_offset];
|
|
d.len = 0;
|
|
|
|
for (;;) {
|
|
OUT_NEXT_BYTE(connp);
|
|
|
|
if (connp->out_next_byte == -1) {
|
|
// End of chunk
|
|
|
|
// Send data to callbacks
|
|
if (d.len != 0) {
|
|
if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
|
|
connp->out_decompressor->decompress(connp->out_decompressor, &d);
|
|
} else {
|
|
int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
|
|
if (rc != HOOK_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response body data callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we don't know the length, then we must check
|
|
// to see if the stream closed; that would signal the
|
|
// end of the response body (and the end of the transaction).
|
|
if ((connp->out_content_length == -1) && (connp->out_status == STREAM_STATE_CLOSED)) {
|
|
connp->out_state = htp_connp_RES_IDLE;
|
|
connp->out_tx->progress = TX_PROGRESS_DONE;
|
|
|
|
return HTP_OK;
|
|
} else {
|
|
// Ask for more data
|
|
return HTP_DATA;
|
|
}
|
|
} else {
|
|
connp->out_tx->response_message_len++;
|
|
connp->out_tx->response_entity_len++;
|
|
|
|
if (connp->out_body_data_left > 0) {
|
|
// We know the length of response body
|
|
|
|
connp->out_body_data_left--;
|
|
d.len++;
|
|
|
|
if (connp->out_body_data_left == 0) {
|
|
// End of body
|
|
|
|
// Send data to callbacks
|
|
if (d.len != 0) {
|
|
if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
|
|
connp->out_decompressor->decompress(connp->out_decompressor, &d);
|
|
} else {
|
|
int rc = hook_run_all(connp->cfg->hook_response_body_data, &d);
|
|
if (rc != HOOK_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response body data callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Done
|
|
connp->out_state = htp_connp_RES_IDLE;
|
|
connp->out_tx->progress = TX_PROGRESS_DONE;
|
|
|
|
return HTP_OK;
|
|
}
|
|
} else {
|
|
// We don't know the length of the response body, which means
|
|
// that the body will consume all data until the connection
|
|
// is closed.
|
|
//
|
|
// We don't need to do anything here.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines presence (and encoding) of a response body.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_BODY_DETERMINE(htp_connp_t *connp) {
|
|
// If the request uses the CONNECT method, then not only are we
|
|
// to assume there's no body, but we need to ignore all
|
|
// subsequent data in the stream.
|
|
if ((connp->out_tx->request_method_number == M_CONNECT)
|
|
&&(connp->out_tx->response_status_number >= 200)
|
|
&&(connp->out_tx->response_status_number <= 299))
|
|
{
|
|
connp->out_status = STREAM_STATE_TUNNEL;
|
|
connp->out_state = htp_connp_RES_IDLE;
|
|
connp->out_tx->progress = TX_PROGRESS_DONE;
|
|
|
|
return HTP_OK;
|
|
}
|
|
|
|
// Check for an interim "100 Continue"
|
|
// response. Ignore it if found, and revert back to RES_FIRST_LINE.
|
|
if (connp->out_tx->response_status_number == 100) {
|
|
if (connp->out_tx->seen_100continue != 0) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Already seen 100-Continue");
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// Ignore any response headers set
|
|
table_clear(connp->out_tx->response_headers);
|
|
|
|
connp->out_state = htp_connp_RES_LINE;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_LINE;
|
|
connp->out_tx->seen_100continue++;
|
|
|
|
return HTP_OK;
|
|
}
|
|
|
|
// Check for compression
|
|
htp_header_t *ce = table_getc(connp->out_tx->response_headers, "content-encoding");
|
|
if (ce != NULL) {
|
|
// TODO Improve detection
|
|
// TODO How would a Content-Range header affect us?
|
|
if ((bstr_cmpc(ce->value, "gzip") == 0) || (bstr_cmpc(ce->value, "x-gzip") == 0)) {
|
|
connp->out_decompressor = (htp_decompressor_t *) htp_gzip_decompressor_create(connp);
|
|
if (connp->out_decompressor != NULL) {
|
|
connp->out_tx->response_content_encoding = COMPRESSION_GZIP;
|
|
connp->out_decompressor->callback = htp_connp_RES_BODY_DECOMPRESSOR_CALLBACK;
|
|
} else {
|
|
// No need to do anything; the error will have already
|
|
// been reported by the failed decompressor.
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1. Any response message which MUST NOT include a message-body
|
|
// (such as the 1xx, 204, and 304 responses and any response to a HEAD
|
|
// request) is always terminated by the first empty line after the
|
|
// header fields, regardless of the entity-header fields present in the
|
|
// message.
|
|
if (((connp->out_tx->response_status_number >= 100) && (connp->out_tx->response_status_number <= 199))
|
|
|| (connp->out_tx->response_status_number == 204) || (connp->out_tx->response_status_number == 304)
|
|
|| (connp->out_tx->request_method_number == M_HEAD)) {
|
|
// There's no response body
|
|
connp->out_state = htp_connp_RES_IDLE;
|
|
} else {
|
|
// We have a response body
|
|
|
|
htp_header_t *cl = table_getc(connp->out_tx->response_headers, "content-length");
|
|
htp_header_t *te = table_getc(connp->out_tx->response_headers, "transfer-encoding");
|
|
|
|
// 2. If a Transfer-Encoding header field (section 14.40) is present and
|
|
// indicates that the "chunked" transfer coding has been applied, then
|
|
// the length is defined by the chunked encoding (section 3.6).
|
|
if (te != NULL) {
|
|
if (bstr_cmpc(te->value, "chunked") != 0) {
|
|
// Invalid T-E header value
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Invalid T-E value in response");
|
|
}
|
|
|
|
// If the T-E header is present we are going to use it.
|
|
connp->out_tx->response_transfer_coding = CHUNKED;
|
|
|
|
// We are still going to check for the presence of C-L
|
|
if (cl != NULL) {
|
|
// This is a violation of the RFC
|
|
connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
|
|
// TODO
|
|
}
|
|
|
|
connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_BODY;
|
|
}// 3. If a Content-Length header field (section 14.14) is present, its
|
|
// value in bytes represents the length of the message-body.
|
|
else if (cl != NULL) {
|
|
// We know the exact length
|
|
connp->out_tx->response_transfer_coding = IDENTITY;
|
|
|
|
// Check for multiple C-L headers
|
|
if (cl->flags & HTP_FIELD_REPEATED) {
|
|
connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
|
|
// TODO Log
|
|
}
|
|
|
|
// Get body length
|
|
int i = htp_parse_content_length(cl->value);
|
|
if (i < 0) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in response");
|
|
return HTP_ERROR;
|
|
} else {
|
|
connp->out_content_length = i;
|
|
connp->out_body_data_left = connp->out_content_length;
|
|
|
|
if (connp->out_content_length != 0) {
|
|
connp->out_state = htp_connp_RES_BODY_IDENTITY;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_BODY;
|
|
} else {
|
|
connp->out_state = htp_connp_RES_IDLE;
|
|
connp->out_tx->progress = TX_PROGRESS_DONE;
|
|
}
|
|
}
|
|
} else {
|
|
// 4. If the message uses the media type "multipart/byteranges", which is
|
|
// self-delimiting, then that defines the length. This media type MUST
|
|
// NOT be used unless the sender knows that the recipient can parse it;
|
|
// the presence in a request of a Range header with multiple byte-range
|
|
// specifiers implies that the client can parse multipart/byteranges
|
|
// responses.
|
|
htp_header_t *ct = table_getc(connp->out_tx->response_headers, "content-type");
|
|
if (ct != NULL) {
|
|
// TODO Handle multipart/byteranges
|
|
|
|
if (bstr_indexofc_nocase(ct->value, "multipart/byteranges") != -1) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"C-T multipart/byteranges in responses not supported");
|
|
return HTP_ERROR;
|
|
}
|
|
}
|
|
|
|
// 5. By the server closing the connection. (Closing the connection
|
|
// cannot be used to indicate the end of a request body, since that
|
|
// would leave no possibility for the server to send back a response.)
|
|
connp->out_state = htp_connp_RES_BODY_IDENTITY;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_BODY;
|
|
}
|
|
}
|
|
|
|
// NOTE We do not need to check for short-style HTTP/0.9 requests here because
|
|
// that is done earlier, before response line parsing begins
|
|
|
|
// Run hook RESPONSE_HEADERS_COMPLETE
|
|
int rc = hook_run_all(connp->cfg->hook_response_headers, connp);
|
|
if (rc != HOOK_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response headers callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
return HTP_OK;
|
|
}
|
|
|
|
/**
|
|
* Parses response headers.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_HEADERS(htp_connp_t *connp) {
|
|
for (;;) {
|
|
OUT_COPY_BYTE_OR_RETURN(connp);
|
|
|
|
if (connp->out_header_line == NULL) {
|
|
connp->out_header_line = calloc(1, sizeof (htp_header_line_t));
|
|
if (connp->out_header_line == NULL) return HTP_ERROR;
|
|
connp->out_header_line->first_nul_offset = -1;
|
|
}
|
|
|
|
// Keep track of NUL bytes
|
|
if (connp->out_next_byte == 0) {
|
|
// Store the offset of the first NUL
|
|
if (connp->out_header_line->has_nulls == 0) {
|
|
connp->out_header_line->first_nul_offset = connp->out_line_len;
|
|
}
|
|
|
|
// Remember how many NULs there were
|
|
connp->out_header_line->flags |= HTP_FIELD_NUL_BYTE;
|
|
connp->out_header_line->has_nulls++;
|
|
}
|
|
|
|
// Have we reached the end of the line?
|
|
if (connp->out_next_byte == LF) {
|
|
#ifdef HTP_DEBUG
|
|
fprint_raw_data(stderr, __FUNCTION__, connp->out_line, connp->out_line_len);
|
|
#endif
|
|
|
|
// Should we terminate headers?
|
|
if (htp_connp_is_line_terminator(connp, connp->out_line, connp->out_line_len)) {
|
|
// Terminator line
|
|
|
|
// Parse previous header, if any
|
|
if (connp->out_header_line_index != -1) {
|
|
if (connp->cfg->process_response_header(connp) != HTP_OK) {
|
|
// Note: downstream responsible for error logging
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// Reset index
|
|
connp->out_header_line_index = -1;
|
|
}
|
|
|
|
// Cleanup
|
|
free(connp->out_header_line);
|
|
connp->out_line_len = 0;
|
|
connp->out_header_line = NULL;
|
|
|
|
// We've seen all response headers
|
|
if (connp->out_tx->progress == TX_PROGRESS_RES_HEADERS) {
|
|
// Determine if this response has a body
|
|
connp->out_state = htp_connp_RES_BODY_DETERMINE;
|
|
} else {
|
|
// Run hook response_TRAILER
|
|
int rc = hook_run_all(connp->cfg->hook_response_trailer, connp);
|
|
if (rc != HOOK_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response trailer callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// We've completed parsing this response
|
|
connp->out_state = htp_connp_RES_IDLE;
|
|
}
|
|
|
|
return HTP_OK;
|
|
}
|
|
|
|
// Prepare line for consumption
|
|
htp_chomp(connp->out_line, &connp->out_line_len);
|
|
|
|
// Check for header folding
|
|
if (htp_connp_is_line_folded(connp->out_line, connp->out_line_len) == 0) {
|
|
// New header line
|
|
|
|
// Parse previous header, if any
|
|
if (connp->out_header_line_index != -1) {
|
|
if (connp->cfg->process_response_header(connp) != HTP_OK) {
|
|
// Note: downstream responsible for error logging
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// Reset index
|
|
connp->out_header_line_index = -1;
|
|
}
|
|
|
|
// Remember the index of the fist header line
|
|
connp->out_header_line_index = connp->out_header_line_counter;
|
|
} else {
|
|
// Folding; check that there's a previous header line to add to
|
|
if (connp->out_header_line_index == -1) {
|
|
if (!(connp->out_tx->flags & HTP_INVALID_FOLDING)) {
|
|
connp->out_tx->flags |= HTP_INVALID_FOLDING;
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response field folding");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the raw header line to the list
|
|
connp->out_header_line->line = bstr_memdup((char *) connp->out_line, connp->out_line_len);
|
|
if (connp->out_header_line->line == NULL) {
|
|
return HTP_ERROR;
|
|
}
|
|
list_add(connp->out_tx->response_header_lines, connp->out_header_line);
|
|
connp->out_header_line = NULL;
|
|
|
|
// Cleanup for the next line
|
|
connp->out_line_len = 0;
|
|
if (connp->out_header_line_index == -1) {
|
|
connp->out_header_line_index = connp->out_header_line_counter;
|
|
}
|
|
|
|
connp->out_header_line_counter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses response line.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_LINE(htp_connp_t *connp) {
|
|
for (;;) {
|
|
// Get one byte
|
|
OUT_COPY_BYTE_OR_RETURN(connp);
|
|
|
|
// Have we reached the end of the line?
|
|
if (connp->out_next_byte == LF) {
|
|
#ifdef HTP_DEBUG
|
|
fprint_raw_data(stderr, __FUNCTION__, connp->out_line, connp->out_line_len);
|
|
#endif
|
|
|
|
// Is this a line that should be ignored?
|
|
if (htp_connp_is_line_ignorable(connp, connp->out_line, connp->out_line_len)) {
|
|
// We have an empty/whitespace line, which we'll note, ignore and move on
|
|
connp->out_tx->response_ignored_lines++;
|
|
|
|
// TODO How many lines are we willing to accept?
|
|
|
|
// Start again
|
|
connp->out_line_len = 0;
|
|
|
|
return HTP_OK;
|
|
}
|
|
|
|
// Process response line
|
|
|
|
htp_chomp(connp->out_line, &connp->out_line_len);
|
|
|
|
// Deallocate previous response line allocations, which we woud have on a 100 response
|
|
// TODO Consider moving elsewhere; no need to make these checks on every response
|
|
if (connp->out_tx->response_line != NULL) {
|
|
bstr_free(connp->out_tx->response_line);
|
|
}
|
|
|
|
if (connp->out_tx->response_protocol != NULL) {
|
|
bstr_free(connp->out_tx->response_protocol);
|
|
}
|
|
|
|
if (connp->out_tx->response_status != NULL) {
|
|
bstr_free(connp->out_tx->response_status);
|
|
}
|
|
|
|
if (connp->out_tx->response_message != NULL) {
|
|
bstr_free(connp->out_tx->response_message);
|
|
}
|
|
|
|
connp->out_tx->response_line = bstr_memdup((char *) connp->out_line, connp->out_line_len);
|
|
if (connp->out_tx->response_line == NULL) {
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// Parse response line
|
|
if (connp->cfg->parse_response_line(connp) != HTP_OK) {
|
|
// Note: downstream responsible for error logging
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// Run hook RESPONSE_LINE
|
|
int rc = hook_run_all(connp->cfg->hook_response_line, connp);
|
|
if (rc != HOOK_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response line callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// Clean up.
|
|
connp->out_line_len = 0;
|
|
|
|
// Move on to the next phase.
|
|
connp->out_state = htp_connp_RES_HEADERS;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_HEADERS;
|
|
|
|
return HTP_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t htp_connp_res_data_consumed(htp_connp_t *connp) {
|
|
return connp->out_current_offset;
|
|
}
|
|
|
|
/**
|
|
* The response idle state will initialize response processing, as well as
|
|
* finalize each transactions after we are done with it.
|
|
*
|
|
* @param connp
|
|
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
|
|
*/
|
|
int htp_connp_RES_IDLE(htp_connp_t * connp) {
|
|
// If we're here and an outgoing transaction object exists that
|
|
// means we've just completed parsing a response. We need
|
|
// to run the final hook in a transaction and start over.
|
|
if (connp->out_tx != NULL) {
|
|
// Shut down the decompressor, if we've used one
|
|
if (connp->out_decompressor != NULL) {
|
|
connp->out_decompressor->destroy(connp->out_decompressor);
|
|
connp->out_decompressor = NULL;
|
|
}
|
|
|
|
connp->out_tx->progress = TX_PROGRESS_DONE;
|
|
|
|
// Run hook RESPONSE
|
|
int rc = hook_run_all(connp->cfg->hook_response, connp);
|
|
if (rc != HTP_OK) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Response callback returned error (%d)", rc);
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// Check if the inbound parser is waiting on us. If it is that means that
|
|
// there might be request data that the inbound parser hasn't consumed yet.
|
|
// If we don't stop parsing we might encounter a response without a
|
|
// request.
|
|
if ((connp->in_status == STREAM_STATE_DATA_OTHER) && (connp->in_tx == connp->out_tx)) {
|
|
connp->out_tx = NULL;
|
|
return HTP_DATA_OTHER;
|
|
}
|
|
|
|
// Start afresh
|
|
connp->out_tx = NULL;
|
|
}
|
|
|
|
// We want to start parsing the next response (and change
|
|
// the state from IDLE) only if there's at least one
|
|
// byte of data available. Otherwise we could be creating
|
|
// new structures even if there's no more data on the
|
|
// connection.
|
|
OUT_TEST_NEXT_BYTE_OR_RETURN(connp);
|
|
|
|
// Parsing a new response
|
|
|
|
// Find the next outgoing transaction
|
|
connp->out_tx = list_get(connp->conn->transactions, connp->out_next_tx_index);
|
|
if (connp->out_tx == NULL) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
|
|
"Unable to match response to request");
|
|
return HTP_ERROR;
|
|
}
|
|
|
|
// We've used one transaction
|
|
connp->out_next_tx_index++;
|
|
|
|
// TODO Detect state mismatch
|
|
|
|
connp->out_content_length = -1;
|
|
connp->out_body_data_left = -1;
|
|
connp->out_header_line_index = -1;
|
|
connp->out_header_line_counter = 0;
|
|
|
|
// Change state into response line parsing, except if we're following
|
|
// a short HTTP/0.9 request, because such requests to not have a
|
|
// response line and headers.
|
|
if (connp->out_tx->protocol_is_simple) {
|
|
connp->out_tx->response_transfer_coding = IDENTITY;
|
|
connp->out_state = htp_connp_RES_BODY_IDENTITY;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_BODY;
|
|
} else {
|
|
connp->out_state = htp_connp_RES_LINE;
|
|
connp->out_tx->progress = TX_PROGRESS_RES_LINE;
|
|
}
|
|
|
|
return HTP_OK;
|
|
}
|
|
|
|
/**
|
|
* Process a chunk of outbound (server or response) data.
|
|
*
|
|
* @param connp
|
|
* @param timestamp
|
|
* @param data
|
|
* @param len
|
|
* @return HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed
|
|
*/
|
|
int htp_connp_res_data(htp_connp_t *connp, htp_time_t timestamp, unsigned char *data, size_t len) {
|
|
#ifdef HTP_DEBUG
|
|
fprintf(stderr, "htp_connp_res_data(connp->out_status %x)\n", connp->out_status);
|
|
fprint_raw_data(stderr, __FUNCTION__, data, len);
|
|
#endif
|
|
|
|
// Return if the connection has had a fatal error
|
|
if (connp->out_status == STREAM_STATE_ERROR) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Outbound parser is in STREAM_STATE_ERROR");
|
|
|
|
#ifdef HTP_DEBUG
|
|
fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (previous error)\n");
|
|
#endif
|
|
return STREAM_STATE_ERROR;
|
|
}
|
|
|
|
// If the length of the supplied data chunk is zero, proceed
|
|
// only if the stream has been closed. We do not allow zero-sized
|
|
// chunks in the API, but we use it internally to force the parsers
|
|
// to finalize parsing.
|
|
if ((len == 0) && (connp->out_status != STREAM_STATE_CLOSED)) {
|
|
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Zero-length data chunks are not allowed");
|
|
|
|
#ifdef HTP_DEBUG
|
|
fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (zero-length chunk)\n");
|
|
#endif
|
|
return STREAM_STATE_ERROR;
|
|
}
|
|
|
|
// Store the current chunk information
|
|
connp->out_timestamp = timestamp;
|
|
connp->out_current_data = data;
|
|
connp->out_current_len = len;
|
|
connp->out_current_offset = 0;
|
|
connp->conn->out_data_counter += len;
|
|
connp->conn->out_packet_counter++;
|
|
|
|
// Return without processing any data if the stream is in tunneling
|
|
// mode (which it would be after an initial CONNECT transaction.
|
|
if (connp->out_status == STREAM_STATE_TUNNEL) {
|
|
#ifdef HTP_DEBUG
|
|
fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (tunnel)\n");
|
|
#endif
|
|
return STREAM_STATE_DATA;
|
|
}
|
|
|
|
// Invoke a processor, in a loop, until an error
|
|
// occurs or until we run out of data. Many processors
|
|
// will process a request, each pointing to the next
|
|
// processor that needs to run.
|
|
for (;;) {
|
|
#ifdef HTP_DEBUG
|
|
fprintf(stderr, "htp_connp_res_data: out state=%s, progress=%s\n",
|
|
htp_connp_out_state_as_string(connp),
|
|
htp_tx_progress_as_string(connp->out_tx));
|
|
#endif
|
|
// Return if there's been an error
|
|
// or if we've run out of data. We are relying
|
|
// on processors to add error messages, so we'll
|
|
// keep quiet here.
|
|
int rc = connp->out_state(connp);
|
|
if (rc != HTP_OK) {
|
|
// Do we need more data?
|
|
if (rc == HTP_DATA) {
|
|
return STREAM_STATE_DATA;
|
|
}
|
|
|
|
// Check for suspended parsing
|
|
if (rc == HTP_DATA_OTHER) {
|
|
// We might have actually consumed the entire data chunk?
|
|
if (connp->out_current_offset >= connp->out_current_len) {
|
|
// Do not send STREAM_DATE_DATA_OTHER if we've
|
|
// consumed the entire chunk
|
|
#ifdef HTP_DEBUG
|
|
fprintf(stderr, "htp_connp_res_data: returning STREAM_STATE_DATA (suspended parsing)\n");
|
|
#endif
|
|
return STREAM_STATE_DATA;
|
|
} else {
|
|
// Partial chunk consumption
|
|
#ifdef HTP_DEBUG
|
|
fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_DATA_OTHER\n");
|
|
#endif
|
|
return STREAM_STATE_DATA_OTHER;
|
|
}
|
|
}
|
|
|
|
// Remember that we've had an error. Errors are
|
|
// not possible to recover from.
|
|
connp->out_status = STREAM_STATE_ERROR;
|
|
|
|
return STREAM_STATE_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|