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.
		
		
		
		
		
			
		
			
				
	
	
		
			310 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
			
		
		
	
	
			310 lines
		
	
	
		
			8.5 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 fo the full text of the exception.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
#include "htp.h"
 | 
						|
 | 
						|
/**
 | 
						|
 * Extract one request header. A header can span multiple lines, in
 | 
						|
 * which case they will be folded into one before parsing is attempted.
 | 
						|
 *
 | 
						|
 * @param connp
 | 
						|
 * @return HTP_OK or HTP_ERROR
 | 
						|
 */
 | 
						|
int htp_process_request_header_generic(htp_connp_t *connp) {
 | 
						|
    bstr *tempstr = NULL;
 | 
						|
    unsigned char *data = NULL;
 | 
						|
    size_t len = 0;
 | 
						|
 | 
						|
    // Create new header structure
 | 
						|
    htp_header_t *h = calloc(1, sizeof (htp_header_t));
 | 
						|
    if (h == NULL) {
 | 
						|
        // TODO
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    // Ensure we have the necessary header data in a single buffer
 | 
						|
    if (connp->in_header_line_index + 1 == connp->in_header_line_counter) {
 | 
						|
        // Single line
 | 
						|
        htp_header_line_t *hl = list_get(connp->in_tx->request_header_lines,
 | 
						|
            connp->in_header_line_index);
 | 
						|
        if (hl == NULL) {
 | 
						|
            // Internal error
 | 
						|
            // TODO
 | 
						|
            free(h);
 | 
						|
            return HTP_ERROR;
 | 
						|
        }
 | 
						|
 | 
						|
        data = (unsigned char *)bstr_ptr(hl->line);
 | 
						|
        if (data == NULL) {
 | 
						|
            free(h);
 | 
						|
            return HTP_ERROR;
 | 
						|
        }
 | 
						|
        len = bstr_len(hl->line);
 | 
						|
        hl->header = h;
 | 
						|
    } else {
 | 
						|
        // Multiple lines (folded)
 | 
						|
        int i = 0;
 | 
						|
 | 
						|
        for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) {
 | 
						|
            htp_header_line_t *hl = list_get(connp->in_tx->request_header_lines, i);
 | 
						|
            len += bstr_len(hl->line);
 | 
						|
        }
 | 
						|
 | 
						|
        tempstr = bstr_alloc(len);
 | 
						|
        if (tempstr == NULL) {
 | 
						|
            // TODO
 | 
						|
            free(h);
 | 
						|
            return HTP_ERROR;
 | 
						|
        }
 | 
						|
 | 
						|
        for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) {
 | 
						|
            htp_header_line_t *hl = list_get(connp->in_tx->request_header_lines, i);
 | 
						|
            char *data = bstr_ptr(hl->line);
 | 
						|
            size_t len = bstr_len(hl->line);
 | 
						|
            htp_chomp((unsigned char *)data, &len);
 | 
						|
            bstr_add_mem_noex(tempstr, data, len);
 | 
						|
            hl->header = h;
 | 
						|
        }
 | 
						|
 | 
						|
        data = (unsigned char *)bstr_ptr(tempstr);
 | 
						|
    }
 | 
						|
 | 
						|
    // Now try to oparse the header
 | 
						|
    if (htp_parse_request_header_generic(connp, h, data, len) != HTP_OK) {
 | 
						|
        if (tempstr != NULL) {
 | 
						|
            free(tempstr);
 | 
						|
        }
 | 
						|
 | 
						|
        free(h);
 | 
						|
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    // Do we already have a header with the same name?
 | 
						|
    htp_header_t *h_existing = table_get(connp->in_tx->request_headers, h->name);
 | 
						|
    if (h_existing != NULL) {
 | 
						|
        // TODO Do we want to keep a list of the headers that are
 | 
						|
        //      allowed to be combined in this way?
 | 
						|
 | 
						|
        // Add to existing header
 | 
						|
        h_existing->value = bstr_expand(h_existing->value, bstr_len(h_existing->value)
 | 
						|
            + 2 + bstr_len(h->value));
 | 
						|
        bstr_add_mem_noex(h_existing->value, ", ", 2);
 | 
						|
        bstr_add_str_noex(h_existing->value, h->value);
 | 
						|
 | 
						|
        // The header is no longer needed
 | 
						|
        free(h->name);
 | 
						|
        free(h->value);
 | 
						|
        free(h);
 | 
						|
 | 
						|
        // Keep track of same-name headers        
 | 
						|
        h_existing->flags |= HTP_FIELD_REPEATED;
 | 
						|
    } else {
 | 
						|
        // Add as a new header
 | 
						|
        table_add(connp->in_tx->request_headers, h->name, h);
 | 
						|
    }
 | 
						|
 | 
						|
    if (tempstr != NULL) {
 | 
						|
        free(tempstr);
 | 
						|
    }
 | 
						|
 | 
						|
    return HTP_OK;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Generic request header parser.
 | 
						|
 *
 | 
						|
 * @param connp
 | 
						|
 * @param h
 | 
						|
 * @param data
 | 
						|
 * @param len
 | 
						|
 * @return HTP_OK or HTP_ERROR
 | 
						|
 */
 | 
						|
int htp_parse_request_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) {
 | 
						|
    size_t name_start, name_end;
 | 
						|
    size_t value_start, value_end;
 | 
						|
 | 
						|
    htp_chomp(data, &len);   
 | 
						|
 | 
						|
    name_start = 0;
 | 
						|
 | 
						|
    // Look for the colon
 | 
						|
    size_t colon_pos = 0;
 | 
						|
    
 | 
						|
    while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;
 | 
						|
    
 | 
						|
    if (colon_pos == len) {
 | 
						|
        // Missing colon
 | 
						|
        h->flags |= HTP_FIELD_UNPARSEABLE;
 | 
						|
 | 
						|
        if (!(connp->in_tx->flags & HTP_FIELD_UNPARSEABLE)) {
 | 
						|
            connp->in_tx->flags |= HTP_FIELD_UNPARSEABLE;
 | 
						|
            // Only log once per transaction
 | 
						|
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Request field invalid: colon missing");
 | 
						|
        }
 | 
						|
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    if (colon_pos == 0) {
 | 
						|
        // Empty header name
 | 
						|
        h->flags |= HTP_FIELD_INVALID;
 | 
						|
 | 
						|
        if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) {
 | 
						|
            connp->in_tx->flags |= HTP_FIELD_INVALID;
 | 
						|
            // Only log once per transaction
 | 
						|
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: empty name");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    name_end = colon_pos;
 | 
						|
 | 
						|
    // Ignore LWS after field-name
 | 
						|
    size_t prev = name_end - 1;
 | 
						|
    while ((prev > name_start) && (htp_is_lws(data[prev]))) {
 | 
						|
        prev--;
 | 
						|
        name_end--;
 | 
						|
 | 
						|
        h->flags |= HTP_FIELD_INVALID;
 | 
						|
 | 
						|
        if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) {
 | 
						|
            connp->in_tx->flags |= HTP_FIELD_INVALID;
 | 
						|
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: LWS after name");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Value
 | 
						|
 | 
						|
    value_start = colon_pos;
 | 
						|
 | 
						|
    // Go over the colon
 | 
						|
    if (value_start < len) {
 | 
						|
        value_start++;
 | 
						|
    }
 | 
						|
 | 
						|
    // Ignore LWS before field-content
 | 
						|
    while ((value_start < len) && (htp_is_lws(data[value_start]))) {
 | 
						|
        value_start++;
 | 
						|
    }
 | 
						|
 | 
						|
    // Look for the end of field-content
 | 
						|
    value_end = value_start;
 | 
						|
    
 | 
						|
    while (value_end < len) value_end++;
 | 
						|
 | 
						|
    // Ignore LWS after field-content
 | 
						|
    prev = value_end - 1;
 | 
						|
    while ((prev > value_start) && (htp_is_lws(data[prev]))) {
 | 
						|
        prev--;
 | 
						|
        value_end--;
 | 
						|
    }
 | 
						|
 | 
						|
    // Check that the header name is a token
 | 
						|
    size_t i = name_start;
 | 
						|
    while (i < name_end) {
 | 
						|
        if (!htp_is_token(data[i])) {
 | 
						|
            h->flags |= HTP_FIELD_INVALID;
 | 
						|
 | 
						|
            if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) {
 | 
						|
                connp->in_tx->flags |= HTP_FIELD_INVALID;
 | 
						|
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request header name is not a token");
 | 
						|
            }
 | 
						|
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
 | 
						|
    // Now extract the name and the value
 | 
						|
    h->name = bstr_memdup((char *)data + name_start, name_end - name_start);
 | 
						|
    if (h->name == NULL) {
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
    h->value = bstr_memdup((char *)data + value_start, value_end - value_start);
 | 
						|
    if (h->name == NULL) {
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    return HTP_OK;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Generic request line parser.
 | 
						|
 *
 | 
						|
 * @param connp
 | 
						|
 * @return HTP_OK or HTP_ERROR
 | 
						|
 */
 | 
						|
int htp_parse_request_line_generic(htp_connp_t *connp) {
 | 
						|
    htp_tx_t *tx = connp->in_tx;
 | 
						|
    unsigned char *data = (unsigned char *)bstr_ptr(tx->request_line);
 | 
						|
    size_t len = bstr_len(tx->request_line);
 | 
						|
    size_t pos = 0;
 | 
						|
 | 
						|
    // The request method starts at the beginning of the
 | 
						|
    // line and ends with the first whitespace character.
 | 
						|
    while ((pos < len) && (!htp_is_space(data[pos]))) {
 | 
						|
        pos++;
 | 
						|
    }
 | 
						|
 | 
						|
    // No, we don't care if the method is empty.
 | 
						|
 | 
						|
    tx->request_method = bstr_memdup((char *)data, pos);
 | 
						|
    if (tx->request_method == NULL) {
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
    tx->request_method_number = htp_convert_method_to_number(tx->request_method);
 | 
						|
 | 
						|
    // Ignore whitespace after request method. The RFC allows
 | 
						|
    // for only one SP, but then suggests any number of SP and HT
 | 
						|
    // should be permitted.
 | 
						|
    while ((pos < len) && (isspace(data[pos]))) {
 | 
						|
        pos++;
 | 
						|
    }
 | 
						|
 | 
						|
    size_t start = pos;
 | 
						|
 | 
						|
    // The URI ends with the first whitespace.
 | 
						|
    while ((pos < len) && (!htp_is_space(data[pos]))) {
 | 
						|
        pos++;
 | 
						|
    }
 | 
						|
 | 
						|
    tx->request_uri = bstr_memdup((char *)data + start, pos - start);
 | 
						|
    if (tx->request_uri == NULL) {
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    // Ignore whitespace after URI
 | 
						|
    while ((pos < len) && (htp_is_space(data[pos]))) {
 | 
						|
        pos++;
 | 
						|
    }
 | 
						|
 | 
						|
    // Is there protocol information available?
 | 
						|
    if (pos == len) {
 | 
						|
        // No, this looks like a HTTP/0.9 request.
 | 
						|
        tx->protocol_is_simple = 1;
 | 
						|
        return HTP_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    // The protocol information spreads until the end of the line.
 | 
						|
    tx->request_protocol = bstr_memdup((char *)data + pos, len - pos);
 | 
						|
    if (tx->request_protocol == NULL) {
 | 
						|
        return HTP_ERROR;
 | 
						|
    }
 | 
						|
    tx->request_protocol_number = htp_parse_protocol(tx->request_protocol);
 | 
						|
 | 
						|
    return HTP_OK;
 | 
						|
}
 | 
						|
 |