Exemplo n.º 1
0
TEST(UtilTest, Chomp) {
    char data[100];
    size_t len;
    int result;

    strcpy(data, "test\r\n");
    len = strlen(data);
    result = htp_chomp((unsigned char*)data, &len);
    EXPECT_EQ(2, result);
    EXPECT_EQ(4, len);

    strcpy(data, "foo\n");
    len = strlen(data);
    result = htp_chomp((unsigned char*)data, &len);
    EXPECT_EQ(1, result);
    EXPECT_EQ(3, len);

    strcpy(data, "arfarf");
    len = strlen(data);
    result = htp_chomp((unsigned char*)data, &len);
    EXPECT_EQ(0, result);
    EXPECT_EQ(6, len);
}
Exemplo n.º 2
0
/**
 * Parses request line.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_REQ_LINE(htp_connp_t *connp) {
    for (;;) {
        // Get one byte
        IN_COPY_BYTE_OR_RETURN(connp);       

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_req_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif

            // Is this a line that should be ignored?
            if (htp_connp_is_line_ignorable(connp, data, len)) {
                // We have an empty/whitespace line, which we'll note, ignore and move on.
                connp->in_tx->request_ignored_lines++;

                // TODO How many empty lines are we willing to accept?

                htp_connp_req_clear_buffer(connp);

                return HTP_OK;
            }

            // Process request line.

            htp_chomp(data, &len);

            connp->in_tx->request_line = bstr_dup_mem(data, len);
            if (connp->in_tx->request_line == NULL) return HTP_ERROR;

            if (connp->cfg->parse_request_line(connp) != HTP_OK) return HTP_ERROR;

            // Finalize request line parsing.

            if (htp_tx_state_request_line(connp->in_tx) != HTP_OK) return HTP_ERROR;

            htp_connp_req_clear_buffer(connp);

            return HTP_OK;
        }
    }

    return HTP_ERROR;
}
Exemplo n.º 3
0
/**
 * Extracts chunk length.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_RES_BODY_CHUNKED_LENGTH(htp_connp_t *connp) {
    for (;;) {
        OUT_COPY_BYTE_OR_RETURN(connp);
        
        // Have we reached the end of the line?
        if (connp->out_next_byte == LF) {
            unsigned char *data;
            size_t len;

            if (htp_connp_res_consolidate_data(connp, &data, &len) != HTP_OK) {
                return HTP_ERROR;
            }

            connp->out_tx->response_message_len += len;

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, "Chunk length line", data, len);
            #endif

            htp_chomp(data, &len);
            
            connp->out_chunked_length = htp_parse_chunked_length(data, len);
            
            htp_connp_res_clear_buffer(connp);

            // Handle chunk length
            if (connp->out_chunked_length > 0) {
                // More data available                
                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->response_progress = HTP_RESPONSE_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;
        }
    }

    return HTP_ERROR;
}
Exemplo n.º 4
0
/**
 * Extracts chunk length.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_REQ_BODY_CHUNKED_LENGTH(htp_connp_t *connp) {
    for (;;) {
        IN_COPY_BYTE_OR_RETURN(connp);

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_req_consolidate_data(connp, &data, &len);

            connp->in_tx->request_message_len += len;

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, "Chunk length line", data, len);
            #endif

            htp_chomp(data, &len);

            connp->in_chunked_length = htp_parse_chunked_length(data, len);

            htp_connp_req_clear_buffer(connp);

            // Handle chunk length.
            if (connp->in_chunked_length > 0) {
                // More data available.
                // TODO Add a check (flag) for excessive chunk length.
                connp->in_state = htp_connp_REQ_BODY_CHUNKED_DATA;
            } else if (connp->in_chunked_length == 0) {
                // End of data
                connp->in_state = htp_connp_REQ_HEADERS;
                connp->in_tx->request_progress = HTP_REQUEST_TRAILER;
            } else {
                // Invalid chunk length.
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Request chunk encoding: Invalid chunk length");
                return HTP_ERROR;
            }

            return HTP_OK;
        }
    }

    return HTP_ERROR;
}
Exemplo n.º 5
0
/**
 * Parse the request line.
 *
 * @param[in] connp
 * @returns HTP_OK on succesful parse, HTP_ERROR on error.
 */
htp_status_t htp_connp_REQ_LINE_complete(htp_connp_t *connp) {
    unsigned char *data;
    size_t len;

    if (htp_connp_req_consolidate_data(connp, &data, &len) != HTP_OK) {
        return HTP_ERROR;
    }

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, __FUNCTION__, data, len);
    #endif

    // Is this a line that should be ignored?
    if (htp_connp_is_line_ignorable(connp, data, len)) {
        // We have an empty/whitespace line, which we'll note, ignore and move on.
        connp->in_tx->request_ignored_lines++;

        htp_connp_req_clear_buffer(connp);

        return HTP_OK;
    }

    // Process request line.

    htp_chomp(data, &len);

    connp->in_tx->request_line = bstr_dup_mem(data, len);
    if (connp->in_tx->request_line == NULL)
        return HTP_ERROR;

    if (connp->cfg->parse_request_line(connp) != HTP_OK)
        return HTP_ERROR;

    // Finalize request line parsing.

    if (htp_tx_state_request_line(connp->in_tx) != HTP_OK)
        return HTP_ERROR;

    htp_connp_req_clear_buffer(connp);

    return HTP_OK;
}
Exemplo n.º 6
0
/**
 * 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_REQ_BODY_CHUNKED_LENGTH(htp_connp_t *connp) {
    for (;;) {
        IN_COPY_BYTE_OR_RETURN(connp);

        connp->in_tx->request_message_len++;

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            htp_chomp(connp->in_line, &connp->in_line_len);

            // Extract chunk length
            connp->in_chunked_length = htp_parse_chunked_length(connp->in_line, connp->in_line_len);

            // Cleanup for the next line
            connp->in_line_len = 0;

            // Handle chunk length
            if (connp->in_chunked_length > 0) {
                // More data available
                // TODO Add a check for chunk length
                connp->in_state = htp_connp_REQ_BODY_CHUNKED_DATA;
            } else if (connp->in_chunked_length == 0) {
                // End of data
                connp->in_state = htp_connp_REQ_HEADERS;
                connp->in_tx->progress[0] = TX_PROGRESS_REQ_TRAILER;
            } else {
                // Invalid chunk length
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                    "Request chunk encoding: Invalid chunk length");
                return HTP_ERROR;
            }

            return HTP_OK;
        }
    }
    return HTP_ERROR;
}
Exemplo n.º 7
0
/**
 * Generic response header parser.
 * 
 * @param[in] connp
 * @param[in] h
 * @param[in] data
 * @param[in] len
 * @return HTP status
 */
htp_status_t htp_parse_response_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;
    size_t prev;

    htp_chomp(data, &len);

    name_start = 0;

    // Look for the first colon.
    size_t colon_pos = 0;
    while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;

    if (colon_pos == len) {
        // Header line with a missing colon.

        h->flags |= HTP_FIELD_UNPARSEABLE;
        h->flags |= HTP_FIELD_INVALID;

        if (!(connp->out_tx->flags & HTP_FIELD_UNPARSEABLE)) {
            // Only once per transaction.
            connp->out_tx->flags |= HTP_FIELD_UNPARSEABLE;
            connp->out_tx->flags |= HTP_FIELD_INVALID;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: missing colon.");
        }
       
        // Reset the position. We're going to treat this invalid header
        // as a header with an empty name. That will increase the probability
        // that the content will be inspected.
        colon_pos = 0;
        name_end = 0;
        value_start = 0;
    } else {
        // Header line with a colon.
        
        if (colon_pos == 0) {
            // Empty header name.

            h->flags |= HTP_FIELD_INVALID;

            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
                // Only once per transaction.
                connp->out_tx->flags |= HTP_FIELD_INVALID;
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: empty name.");
            }
        }

        name_end = colon_pos;

        // Ignore LWS after field-name.
        prev = name_end;
        while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) {
            prev--;
            name_end--;

            h->flags |= HTP_FIELD_INVALID;

            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
                // Only once per transaction.
                connp->out_tx->flags |= HTP_FIELD_INVALID;
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: LWS after name.");
            }
        }

        value_start = colon_pos + 1;
    }

    // Header value.   

    // 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 = len;    
    
    // 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->out_tx->flags & HTP_FIELD_INVALID)) {
                connp->out_tx->flags |= HTP_FIELD_INVALID;
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header name is not a token.");
            }

            break;
        }

        i++;
    }

    // Now extract the name and the value.
    h->name = bstr_dup_mem(data + name_start, name_end - name_start);
    h->value = bstr_dup_mem(data + value_start, value_end - value_start);
    if ((h->name == NULL) || (h->value == NULL)) {
        bstr_free(h->name);
        bstr_free(h->value);
        return HTP_ERROR;
    }

    return HTP_OK;
}
Exemplo n.º 8
0
/**
 * Parses request headers.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_REQ_HEADERS(htp_connp_t *connp) {
    for (;;) {
        IN_COPY_BYTE_OR_RETURN(connp);

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_req_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif           

            // Should we terminate headers?
            if (htp_connp_is_line_terminator(connp, data, len)) {
                // Parse previous header, if any.
                if (connp->in_header != NULL) {
                    if (connp->cfg->process_request_header(connp, bstr_ptr(connp->in_header),
                            bstr_len(connp->in_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->in_header);
                    connp->in_header = NULL;
                }

                htp_connp_req_clear_buffer(connp);

                // We've seen all the request headers.
                return htp_tx_state_request_headers(connp->in_tx);
            }

            htp_chomp(data, &len);

            // Check for header folding.
            if (htp_connp_is_line_folded(data, len) == 0) {
                // New header line.

                // Parse previous header, if any.
                if (connp->in_header != NULL) {
                    if (connp->cfg->process_request_header(connp, bstr_ptr(connp->in_header),
                            bstr_len(connp->in_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->in_header);
                    connp->in_header = NULL;
                }

                IN_PEEK_NEXT(connp);

                if (htp_is_folding_char(connp->in_next_byte) == 0) {
                    // Because we know this header is not folded, we can process the buffer straight away.
                    if (connp->cfg->process_request_header(connp, data, len) != HTP_OK) return HTP_ERROR;
                } else {
                    // Keep the partial header data for parsing later.
                    connp->in_header = bstr_dup_mem(data, len);
                    if (connp->in_header == NULL) return HTP_ERROR;
                }
            } else {
                // Folding; check that there's a previous header line to add to.
                if (connp->in_header == NULL) {
                    // Invalid folding.

                    // Warn only once per transaction.
                    if (!(connp->in_tx->flags & HTP_INVALID_FOLDING)) {
                        connp->in_tx->flags |= HTP_INVALID_FOLDING;
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid request field folding");
                    }

                    // Keep the header data for parsing later.
                    connp->in_header = bstr_dup_mem(data, len);
                    if (connp->in_header == NULL) return HTP_ERROR;
                } else {
                    // Add to the existing header.                    
                    bstr *new_in_header = bstr_add_mem(connp->in_header, data, len);
                    if (new_in_header == NULL) return HTP_ERROR;
                    connp->in_header = new_in_header;
                }
            }

            htp_connp_req_clear_buffer(connp);
        }
    }

    return HTP_ERROR;
}
Exemplo n.º 9
0
/**
 * Parses request headers.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_REQ_HEADERS(htp_connp_t *connp) {
    for (;;) {
        IN_COPY_BYTE_OR_RETURN(connp);

        if (connp->in_header_line == NULL) {
            connp->in_header_line = calloc(1, sizeof (htp_header_line_t));
            if (connp->in_header_line == NULL) return HTP_ERROR;
            connp->in_header_line->first_nul_offset = -1;
        }

        // Keep track of NUL bytes
        if (connp->in_next_byte == 0) {
            // Store the offset of the first NUL
            if (connp->in_header_line->has_nulls == 0) {
                connp->in_header_line->first_nul_offset = connp->in_line_len;
            }

            // Remember how many NULs there were
            connp->in_header_line->flags |= HTP_FIELD_NUL_BYTE;
            connp->in_header_line->has_nulls++;
        }

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, connp->in_line, connp->in_line_len);
            #endif

            // Should we terminate headers?
            if (htp_connp_is_line_terminator(connp, connp->in_line, connp->in_line_len)) {
                // Terminator line

                // Parse previous header, if any
                if (connp->in_header_line_index != -1) {
                    if (connp->cfg->process_request_header(connp) != HTP_OK) {
                        // Note: downstream responsible for error logging
                        return HTP_ERROR;
                    }

                    // Reset index
                    connp->in_header_line_index = -1;
                }

                // Cleanup
                free(connp->in_header_line);
                connp->in_line_len = 0;
                connp->in_header_line = NULL;                

                // We've seen all request headers
                if (connp->in_chunk_count != connp->in_chunk_request_index) {
                    connp->in_tx->flags |= HTP_MULTI_PACKET_HEAD;
                }

                // Move onto the next processing phase
                if (connp->in_tx->progress[0] == TX_PROGRESS_REQ_HEADERS) {
                    // Determine if this request has a body
                    //connp->in_state = htp_connp_REQ_BODY_DETERMINE;
                    connp->in_state = htp_connp_REQ_CONNECT_CHECK;
                } else {
                    // Run hook REQUEST_TRAILER
                    int rc = hook_run_all(connp->cfg->hook_request_trailer, connp);
                    if (rc != HOOK_OK) {
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                            "Request trailer callback returned error (%d)", rc);
                        return HTP_ERROR;
                    }

                    // We've completed parsing this request
                    connp->in_state = htp_connp_REQ_IDLE;
                    connp->in_tx->progress[0] = TX_PROGRESS_WAIT;
                }

                return HTP_OK;
            }

            // Prepare line for consumption
            size_t raw_in_line_len = connp->in_line_len;
            htp_chomp(connp->in_line, &connp->in_line_len);

            // Check for header folding
            if (htp_connp_is_line_folded(connp->in_line, connp->in_line_len) == 0) {
                // New header line

                // Parse previous header, if any
                if (connp->in_header_line_index != -1) {
                    if (connp->cfg->process_request_header(connp) != HTP_OK) {
                        // Note: downstream responsible for error logging
                        return HTP_ERROR;
                    }

                    // Reset index
                    connp->in_header_line_index = -1;
                }

                // Remember the index of the fist header line
                connp->in_header_line_index = connp->in_header_line_counter;
            } else {
                // Folding; check that there's a previous header line to add to
                if (connp->in_header_line_index == -1) {
                    if (!(connp->in_tx->flags & HTP_INVALID_FOLDING)) {
                        connp->in_tx->flags |= HTP_INVALID_FOLDING;
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
                            "Invalid request field folding");
                    }
                }
            }

            // Add the raw header line to the list
            if (raw_in_line_len > connp->in_line_len) {
                if (raw_in_line_len - connp->in_line_len == 2 &&
                        connp->in_line[connp->in_line_len] == 0x0d &&
                        connp->in_line[connp->in_line_len + 1] == 0x0a) {
                    connp->in_header_line->terminators = NULL;
                } else {
                    connp->in_header_line->terminators =
                        bstr_memdup((char *) connp->in_line + connp->in_line_len,
                                raw_in_line_len - connp->in_line_len);
                    if (connp->in_header_line->terminators == NULL) {
                        return HTP_ERROR;
                    }
                }
            } else {
                connp->in_header_line->terminators = NULL;
            }

            connp->in_header_line->line = bstr_memdup((char *) connp->in_line, connp->in_line_len);
            if (connp->in_header_line->line == NULL) {
                return HTP_ERROR;
            }
            list_add(connp->in_tx->request_header_lines, connp->in_header_line);
            connp->in_header_line = NULL;

            // Cleanup for the next line
            connp->in_line_len = 0;
            if (connp->in_header_line_index == -1) {
                connp->in_header_line_index = connp->in_header_line_counter;
            }

            connp->in_header_line_counter++;
        }
    }
    return HTP_ERROR;
}
Exemplo n.º 10
0
/**
 * Parses response line.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t 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) {
            unsigned char *data;
            size_t len;

            htp_connp_res_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif

            // Is this a line that should be ignored?
            if (htp_connp_is_line_ignorable(connp, data, 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
                htp_connp_res_clear_buffer(connp);

                return HTP_OK;
            }
           
            // Deallocate previous response line allocations, which we would have on a 100 response.

            if (connp->out_tx->response_line != NULL) {
                bstr_free(connp->out_tx->response_line);
                connp->out_tx->response_line = NULL;
            }           

            if (connp->out_tx->response_protocol != NULL) {
                bstr_free(connp->out_tx->response_protocol);
                connp->out_tx->response_protocol = NULL;
            }

            if (connp->out_tx->response_status != NULL) {
                bstr_free(connp->out_tx->response_status);
                connp->out_tx->response_status = NULL;
            }

            if (connp->out_tx->response_message != NULL) {
                bstr_free(connp->out_tx->response_message);
                connp->out_tx->response_message = NULL;
            }

            // Process response line.           

            int chomp_result = htp_chomp(data, &len);

            connp->out_tx->response_line = bstr_dup_mem(data, len);
            if (connp->out_tx->response_line == NULL) return HTP_ERROR;            

            if (connp->cfg->parse_response_line(connp) != HTP_OK) return HTP_ERROR;

            // If the response line is invalid, determine if it _looks_ like
            // a response line. If it does not look like a line, process the
            // data as a response body because that is what browsers do.

            if (htp_treat_response_line_as_body(connp->out_tx)) {
                connp->out_tx->response_content_encoding_processing = HTP_COMPRESSION_NONE;
                
                int rc = htp_tx_res_process_body_data(connp->out_tx, data, len + chomp_result);
                if (rc != HTP_OK) return rc;
               
                // Continue to process response body. Because we don't have
                // any headers to parse, we assume the body continues until
                // the end of the stream.
                connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;
                connp->out_tx->progress = HTP_RESPONSE_BODY;
                connp->out_state = htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE;
                connp->out_body_data_left = -1;
                
                return HTP_OK;
            }
           
            int rc = htp_tx_state_response_line(connp->out_tx);
            if (rc != HTP_OK) return rc;
            
            htp_connp_res_clear_buffer(connp);

            // Move on to the next phase.
            connp->out_state = htp_connp_RES_HEADERS;
            connp->out_tx->progress = HTP_RESPONSE_HEADERS;

            return HTP_OK;
        }
    }

    return HTP_ERROR;
}
Exemplo n.º 11
0
/**
 * Parses response headers.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_RES_HEADERS(htp_connp_t *connp) {
    for (;;) {
        OUT_COPY_BYTE_OR_RETURN(connp);       
        
        // Have we reached the end of the line?
        if (connp->out_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_res_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif

            // Should we terminate headers?
            if (htp_connp_is_line_terminator(connp, data, len)) {
                // Parse previous header, if any.
                if (connp->out_header != NULL) {
                    if (connp->cfg->process_response_header(connp, bstr_ptr(connp->out_header),
                            bstr_len(connp->out_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->out_header);
                    connp->out_header = NULL;
                }
                
                htp_connp_res_clear_buffer(connp);               

                // We've seen all response headers.
                if (connp->out_tx->progress == HTP_RESPONSE_HEADERS) {
                    // Response headers.

                    // The next step is to determine if this response has a body.
                    connp->out_state = htp_connp_RES_BODY_DETERMINE;
                } else {
                    // Response trailer.

                    // Finalize sending raw trailer data.
                    htp_status_t rc = htp_connp_res_receiver_finalize_clear(connp);
                    if (rc != HTP_OK) return rc;

                    // Run hook response_TRAILER.
                    rc = htp_hook_run_all(connp->cfg->hook_response_trailer, connp);
                    if (rc != HTP_OK) return rc;

                    // The next step is to finalize this response.
                    connp->out_state = htp_connp_RES_FINALIZE;
                }

                return HTP_OK;
            }

            htp_chomp(data, &len);

            // Check for header folding.
            if (htp_connp_is_line_folded(data, len) == 0) {
                // New header line.

                // Parse previous header, if any.
                if (connp->out_header != NULL) {
                    if (connp->cfg->process_response_header(connp, bstr_ptr(connp->out_header),
                            bstr_len(connp->out_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->out_header);
                    connp->out_header = NULL;
                }

                OUT_PEEK_NEXT(connp);

                if (htp_is_folding_char(connp->out_next_byte) == 0) {
                    // Because we know this header is not folded, we can process the buffer straight away.
                    if (connp->cfg->process_response_header(connp, data, len) != HTP_OK) return HTP_ERROR;
                } else {
                    // Keep the partial header data for parsing later.
                    connp->out_header = bstr_dup_mem(data, len);
                    if (connp->out_header == NULL) return HTP_ERROR;
                }
            } else {
                // Folding; check that there's a previous header line to add to.
                if (connp->out_header == NULL) {
                    // Invalid folding.

                    // Warn only once per transaction.
                    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");
                    }

                    // Keep the header data for parsing later.
                    connp->out_header = bstr_dup_mem(data, len);
                    if (connp->out_header == NULL) return HTP_ERROR;
                } else {
                    // Add to the existing header.                    
                    bstr *new_out_header = bstr_add_mem(connp->out_header, data, len);
                    if (new_out_header == NULL) return HTP_ERROR;
                    connp->out_header = new_out_header;
                }
            }

            htp_connp_res_clear_buffer(connp);
        }
    }

    return HTP_ERROR;
}
Exemplo n.º 12
0
/**
 * 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
                connp->out_tx->response_headers_sep = bstr_dup_mem((char *)connp->out_line, connp->out_line_len);
                if (connp->out_tx->response_headers_sep == NULL) {
                    return HTP_ERROR;
                }

                // Parse previous header, if any
                if (connp->out_header_line_index != -1) {
                    // Only try to parse a header, but ignore
                    // any problems. That's what browsers do.
                    connp->cfg->process_response_header(connp);

                    // 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
            int chomp_result = 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) {
                    // Only try to parse a header, but ignore
                    // any problems. That's what browsers do.
                    connp->cfg->process_response_header(connp);

                    // 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_dup_mem((char *) connp->out_line, connp->out_line_len + chomp_result);
            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++;
        }
    }
}
Exemplo n.º 13
0
/**
 * Generic request header parser.
 *
 * @param[in] connp
 * @param[in] h
 * @param[in] data
 * @param[in] len
 * @return HTP_OK or HTP_ERROR
 */
htp_status_t 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] != '\0') && (data[colon_pos] != ':')) colon_pos++;

    if ((colon_pos == len) || (data[colon_pos] == '\0')) {
        // Missing colon.

        h->flags |= HTP_FIELD_UNPARSEABLE;

        // Log only once per transaction.
        if (!(connp->in_tx->flags & HTP_FIELD_UNPARSEABLE)) {
            connp->in_tx->flags |= HTP_FIELD_UNPARSEABLE;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: colon missing");
        }

        // We handle this case as a header with an empty name, with the value equal
        // to the entire input string.

        // TODO Apache will respond to this problem with a 400.

        // Now extract the name and the value
        h->name = bstr_dup_c("");
        if (h->name == NULL) return HTP_ERROR;

        h->value = bstr_dup_mem(data, len);
        if (h->value == NULL) {
            bstr_free(h->name);
            return HTP_ERROR;
        }

        return HTP_OK;
    }

    if (colon_pos == 0) {
        // Empty header name.

        h->flags |= HTP_FIELD_INVALID;

        // Log only once per transaction.
        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: empty name");
        }
    }

    name_end = colon_pos;

    // Ignore LWS after field-name.
    size_t prev = name_end;
    while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) {
        // LWS after header name.

        prev--;
        name_end--;

        h->flags |= HTP_FIELD_INVALID;

        // Log only once per transaction.
        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");
        }
    }

    // Header 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) && (data[value_end] != '\0')) 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])) {
            // Incorrectly formed header name.

            h->flags |= HTP_FIELD_INVALID;

            // Log only once per transaction.
            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_dup_mem(data + name_start, name_end - name_start);
    if (h->name == NULL) return HTP_ERROR;

    h->value = bstr_dup_mem(data + value_start, value_end - value_start);
    if (h->value == NULL) {
        bstr_free(h->name);
        return HTP_ERROR;
    }

    return HTP_OK;
}
Exemplo n.º 14
0
/**
 * 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_apache_2_2(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) 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
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                "Process request header (Apache 2.2): Internal error");
            free(h);
            return HTP_ERROR;
        }

        data = (unsigned char *) bstr_ptr(hl->line);
        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);
            if (hl == NULL) {
                // Internal error
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                        "Process request header (Apache 2.2): Internal error");
                free(h);
                return HTP_ERROR;
            }
            len += bstr_len(hl->line);
        }

        tempstr = bstr_alloc(len);
        if (tempstr == NULL) {
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                "Process request header (Apache 2.2): Failed to allocate bstring of %d bytes", len);
            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);
            if (hl == NULL) {
                // Internal error
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                        "Process request header (Apache 2.2): Internal error");
                bstr_free(tempstr);
                free(h);
                return HTP_ERROR;
            }
            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 parse the header
    if (htp_parse_request_header_apache_2_2(connp, h, data, len) != HTP_OK) {
        // Note: downstream responsible for error logging
        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
        if (h->name != NULL)
            free(h->name);
        if (h->value != NULL)
            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;
}
Exemplo n.º 15
0
/**
 * Parses a message header line as Apache 2.2 does.
 *
 * @param connp
 * @param h
 * @param data
 * @param len
 * @return HTP_OK or HTP_ERROR
 */
int htp_parse_request_header_apache_2_2(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] != '\0') && (data[colon_pos] != ':')) colon_pos++;

    if ((colon_pos == len) || (data[colon_pos] == '\0')) {
        // 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) && (data[value_end] != '\0')) 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);
    h->value = bstr_memdup((char *) data + value_start, value_end - value_start);

    return HTP_OK;
}
Exemplo n.º 16
0
/**
 * Parses request line.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_REQ_LINE(htp_connp_t *connp) {
    for (;;) {
        // Get one byte
        IN_COPY_BYTE_OR_RETURN(connp);

        // Keep track of NUL bytes
        if (connp->in_next_byte == 0) {
            // Remember how many NULs there were
            connp->in_tx->request_line_nul++;

            // Store the offset of the first NUL byte
            if (connp->in_tx->request_line_nul_offset == -1) {
                connp->in_tx->request_line_nul_offset = connp->in_line_len;
            }
        }

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, connp->in_line, connp->in_line_len);
            #endif

            // Is this a line that should be ignored?
            if (htp_connp_is_line_ignorable(connp, connp->in_line, connp->in_line_len)) {
                // We have an empty/whitespace line, which we'll note, ignore and move on
                connp->in_tx->request_ignored_lines++;

                // TODO How many empty lines are we willing to accept?

                // Start again
                connp->in_line_len = 0;

                return HTP_OK;
            }

            // Process request line

            connp->in_tx->request_line_raw = bstr_dup_mem((char *) connp->in_line, connp->in_line_len);
            if (connp->in_tx->request_line_raw == NULL) {
                return HTP_ERROR;
            }

            /// @todo Would be nice to reference request_line_raw data
            htp_chomp(connp->in_line, &connp->in_line_len);
            connp->in_tx->request_line = bstr_dup_ex(connp->in_tx->request_line_raw, 0, connp->in_line_len);
            if (connp->in_tx->request_line == NULL) {
                return HTP_ERROR;
            }

            // Parse request line
            if (connp->cfg->parse_request_line(connp) != HTP_OK) {
                // Note: downstream responsible for error logging
                return HTP_ERROR;
            }

            if (connp->in_tx->request_method_number == M_CONNECT) {
                // Parse authority
                if (htp_parse_authority(connp, connp->in_tx->request_uri, &(connp->in_tx->parsed_uri_incomplete)) != HTP_OK) {
                    // Note: downstream responsible for error logging
                    return HTP_ERROR;
                }
            } else {
                // Parse the request URI
                if (htp_parse_uri(connp->in_tx->request_uri, &(connp->in_tx->parsed_uri_incomplete)) != HTP_OK) {
                    // Note: downstream responsible for error logging
                    return HTP_ERROR;
                }

                // Keep the original URI components, but
                // create a copy which we can normalize and use internally
                if (htp_normalize_parsed_uri(connp, connp->in_tx->parsed_uri_incomplete, connp->in_tx->parsed_uri)) {
                    // Note: downstream responsible for error logging
                    return HTP_ERROR;
                }

                // Run hook REQUEST_URI_NORMALIZE
                int rc = hook_run_all(connp->cfg->hook_request_uri_normalize, connp);
                if (rc != HOOK_OK) {
                    switch (rc) {
                        case HOOK_STOP:
                            return HTP_STOP;
                        case HOOK_ERROR:
                        case HOOK_DECLINED:
                        default:
                            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                                "Request headers callback returned error (%d)", rc);
                            return HTP_ERROR;
                    }
                }

                // Now is a good time to generate request_uri_normalized, before we finalize
                // parsed_uri (and lose the information which parts were provided in the request and
                // which parts we added).
                if (connp->cfg->generate_request_uri_normalized) {
                    connp->in_tx->request_uri_normalized = htp_unparse_uri_noencode(connp->in_tx->parsed_uri);

                    if (connp->in_tx->request_uri_normalized == NULL) {
                        // There's no sense in logging anything on a memory allocation failure
                        return HTP_ERROR;
                    }

                    #ifdef HTP_DEBUG
                    fprint_raw_data(stderr, "request_uri_normalized",
                        (unsigned char *) bstr_ptr(connp->in_tx->request_uri_normalized),
                        bstr_len(connp->in_tx->request_uri_normalized));
                    #endif
                }

                // Finalize parsed_uri

                // Scheme
                if (connp->in_tx->parsed_uri->scheme != NULL) {
                    if (bstr_cmp_c(connp->in_tx->parsed_uri->scheme, "http") != 0) {
                        // TODO Invalid scheme
                    }
                } else {
                    connp->in_tx->parsed_uri->scheme = bstr_dup_c("http");
                    if (connp->in_tx->parsed_uri->scheme == NULL) {
                        return HTP_ERROR;
                    }
                }

                // Port
                if (connp->in_tx->parsed_uri->port != NULL) {
                    if (connp->in_tx->parsed_uri->port_number != -1) {
                        // Check that the port in the URI is the same
                        // as the port on which the client is talking
                        // to the server
                        if (connp->conn->use_local_port) {
                            if (connp->in_tx->parsed_uri->port_number != connp->conn->local_port) {
                                // Incorrect port; use the real port instead
                                connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
                                // TODO Log
                            }
                        } else {
                            connp->in_tx->parsed_uri->port_number = connp->conn->remote_port;
                        }
                    } else {
                        // Invalid port; use the real port instead
                        if (connp->conn->use_local_port) {
                            connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
                        } else {
                            connp->in_tx->parsed_uri->port_number = connp->conn->remote_port;
                        }
                        // TODO Log
                    }
                } else {
                    if (connp->conn->use_local_port) {
                        connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
                    } else {
                        connp->in_tx->parsed_uri->port_number = connp->conn->remote_port;
                    }
                }

                // Path
                if (connp->in_tx->parsed_uri->path == NULL) {
                    connp->in_tx->parsed_uri->path = bstr_dup_c("/");
                    if (connp->in_tx->parsed_uri->path == NULL) {
                        return HTP_ERROR;
                    }
                }
            }

            // Run hook REQUEST_LINE
            int rc = hook_run_all(connp->cfg->hook_request_line, connp);
            if (rc != HOOK_OK) {
                switch (rc) {
                    case HOOK_STOP:
                        return HTP_STOP;
                    case HOOK_ERROR:
                    case HOOK_DECLINED:
                    default:
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                            "Request headers callback returned error (%d)", rc);
                        return HTP_ERROR;
                }
            }

            // Clean up.
            connp->in_line_len = 0;

            // Move on to the next phase.
            connp->in_state = htp_connp_REQ_PROTOCOL;

            return HTP_OK;
        }
    }
}
Exemplo n.º 17
0
/**
 * Parses request headers.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_REQ_HEADERS(htp_connp_t *connp) {
    for (;;) {
        IN_COPY_BYTE_OR_RETURN(connp);

        // Allocate structure to hold one header line
        if (connp->in_header_line == NULL) {
            connp->in_header_line = calloc(1, sizeof (htp_header_line_t));
            if (connp->in_header_line == NULL) return HTP_ERROR;
            connp->in_header_line->first_nul_offset = -1;
        }

        // Keep track of NUL bytes
        if (connp->in_next_byte == 0) {
            // Store the offset of the first NUL
            if (connp->in_header_line->has_nulls == 0) {
                connp->in_header_line->first_nul_offset = connp->in_line_len;
            }

            // Remember how many NULs there were
            connp->in_header_line->flags |= HTP_FIELD_NUL_BYTE;
            connp->in_header_line->has_nulls++;
        }

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, connp->in_line, connp->in_line_len);
            #endif

            // Should we terminate headers?
            if (htp_connp_is_line_terminator(connp, connp->in_line, connp->in_line_len)) {
                // Terminator line
                connp->in_tx->request_headers_sep = bstr_dup_mem((char *)connp->in_line, connp->in_line_len);
                if (connp->in_tx->request_headers_sep == NULL) {
                    return HTP_ERROR;
                }

                // Parse previous header, if any
                if (connp->in_header_line_index != -1) {
                    if (connp->cfg->process_request_header(connp) != HTP_OK) {
                        // Note: downstream responsible for error logging
                        return HTP_ERROR;
                    }

                    // Reset index
                    connp->in_header_line_index = -1;
                }

                // Cleanup
                free(connp->in_header_line);
                connp->in_line_len = 0;
                connp->in_header_line = NULL;

                // We've seen all request headers

                // Did this request arrive in multiple chunks?
                if (connp->in_chunk_count != connp->in_chunk_request_index) {
                    connp->in_tx->flags |= HTP_MULTI_PACKET_HEAD;
                }

                // Move onto the next processing phase
                if (connp->in_tx->progress == TX_PROGRESS_REQ_HEADERS) {
                    // Remember how many header lines there were before trailers
                    connp->in_tx->request_header_lines_no_trailers = list_size(connp->in_tx->request_header_lines);

                    // Run hook REQUEST_HEADERS_RAW
                    //if (connp->cfg->hook_request_headers_raw != NULL) {
                    //    htp_req_run_hook_request_headers_raw(connp, 0,
                    //        connp->in_tx->request_header_lines_no_trailers);
                    //}

                    // Determine if this request has a body                    
                    connp->in_state = htp_connp_REQ_CONNECT_CHECK;
                } else {
                    // Run hook REQUEST_HEADERS_RAW
                    //if ((connp->cfg->hook_request_headers_raw != NULL)
                    //    && (list_size(connp->in_tx->request_header_lines) > connp->in_tx->request_header_lines_no_trailers)) {
                    //    htp_req_run_hook_request_headers_raw(connp,
                    //        connp->in_tx->request_header_lines_no_trailers,
                    //        list_size(connp->in_tx->request_header_lines));
                    //}

                    // Run hook REQUEST_TRAILER
                    int rc = hook_run_all(connp->cfg->hook_request_trailer, connp);
                    if (rc != HOOK_OK) {
                        switch (rc) {
                            case HOOK_STOP:
                                return HTP_STOP;
                            case HOOK_ERROR:
                            case HOOK_DECLINED:
                            default:
                                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                                    "Request headers callback returned error (%d)", rc);
                                return HTP_ERROR;
                        }
                    }

                    // We've completed parsing this request
                    connp->in_state = htp_connp_REQ_IDLE;
                    connp->in_tx->progress = TX_PROGRESS_WAIT;
                }

                return HTP_OK;
            }

            // Prepare line for consumption
            int chomp_result = htp_chomp(connp->in_line, &connp->in_line_len);

            // Check for header folding
            if (htp_connp_is_line_folded(connp->in_line, connp->in_line_len) == 0) {
                // New header line

                // Parse previous header, if any
                if (connp->in_header_line_index != -1) {
                    if (connp->cfg->process_request_header(connp) != HTP_OK) {
                        // Note: downstream responsible for error logging
                        return HTP_ERROR;
                    }

                    // Reset index
                    connp->in_header_line_index = -1;
                }

                // Remember the index of the fist header line
                connp->in_header_line_index = connp->in_header_line_counter;
            } else {
                // Folding; check that there's a previous header line to add to
                if (connp->in_header_line_index == -1) {
                    if (!(connp->in_tx->flags & HTP_INVALID_FOLDING)) {
                        connp->in_tx->flags |= HTP_INVALID_FOLDING;
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
                            "Invalid request field folding");
                    }
                }
            }

            // Add the raw header line to the list
            connp->in_header_line->line = bstr_dup_mem((char *) connp->in_line, connp->in_line_len + chomp_result);
            if (connp->in_header_line->line == NULL) {
                return HTP_ERROR;
            }
            
            list_add(connp->in_tx->request_header_lines, connp->in_header_line);
            connp->in_header_line = NULL;

            // Cleanup for the next line
            connp->in_line_len = 0;
            if (connp->in_header_line_index == -1) {
                connp->in_header_line_index = connp->in_header_line_counter;
            }

            connp->in_header_line_counter++;
        }
    }
}
Exemplo n.º 18
0
/**
 * 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

            // Deallocate previous response line allocations, which we would 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_raw = bstr_dup_mem((char *) connp->out_line, connp->out_line_len);
            if (connp->out_tx->response_line_raw == NULL) {
                return HTP_ERROR;
            }

            /// @todo Would be nice to reference response_line_raw data
            int chomp_result = htp_chomp(connp->out_line, &connp->out_line_len);
            connp->out_tx->response_line = bstr_dup_ex(connp->out_tx->response_line_raw, 0, 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;
            }

            // Is the response line valid?
            if ((connp->out_tx->response_protocol_number < 0)
                || (connp->out_tx->response_status_number < 0)
                || (connp->out_tx->response_status_number < HTP_VALID_STATUS_MIN)
                || (connp->out_tx->response_status_number > HTP_VALID_STATUS_MAX)) {
                // Response line is invalid
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response line");

                connp->out_tx->flags |= HTP_STATUS_LINE_INVALID;
            }

            // Even when the response line is invalid, determine if it looks like
            // a response line (which is what browsers do).
            if (htp_resembles_response_line(connp->out_tx) == 0) {
                // Process this line as response body data
                htp_tx_data_t d;

                d.tx = connp->out_tx;
                d.data = connp->out_line;
                d.len = connp->out_line_len + chomp_result;

                connp->out_tx->response_message_len += d.len;
                connp->out_tx->response_entity_len += d.len;
                
                int rc = htp_res_run_hook_body_data(connp, &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;
                }

                // Continue to process response body
                connp->out_tx->response_transfer_coding = IDENTITY;
                connp->out_state = htp_connp_RES_BODY_IDENTITY;
                connp->out_tx->progress = TX_PROGRESS_RES_BODY;

                return HTP_OK;
            }

            // 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;
        }
    }
}
Exemplo n.º 19
0
/**
 * Extract one request header. A header can span multiple lines, in
 * which case they will be folded into one before parsing is attempted.
 *
 * @param[in] 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) {        
        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 = htp_list_get(connp->in_tx->request_header_lines,
            connp->in_header_line_index);
        if (hl == NULL) {
            // Internal error            
            free(h);
            return HTP_ERROR;
        }

        data = (unsigned char *)bstr_ptr(hl->line);
        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 = htp_list_get(connp->in_tx->request_header_lines, i);
            len += bstr_len(hl->line);
        }

        tempstr = bstr_alloc(len);
        if (tempstr == NULL) {            
            free(h);
            return HTP_ERROR;
        }

        for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) {
            htp_header_line_t *hl = htp_list_get(connp->in_tx->request_header_lines, i);
            unsigned char *line = bstr_ptr(hl->line);
            size_t llen = bstr_len(hl->line);
            htp_chomp((unsigned char *)line, &llen);
            bstr_add_mem_noex(tempstr, line, llen);
            hl->header = h;

            if (i != connp->in_header_line_index) {
                hl->flags |= HTP_FIELD_FOLDED;
            }
        }

        h->flags |= HTP_FIELD_FOLDED;

        data = (unsigned char *)bstr_ptr(tempstr);
        len = bstr_len(tempstr);
    }

    // Now try to parse the header
    if (htp_parse_request_header_generic(connp, h, data, len) != HTP_OK) {
        bstr_free(tempstr);
        free(h);
        return HTP_ERROR;
    }

    // Do we already have a header with the same name?
    htp_header_t *h_existing = htp_table_get(connp->in_tx->request_headers, h->name);
    if (h_existing != NULL) {
        // repeated header
        int i = 0;

        // TODO Do we want to keep a list of the headers that are
        //      allowed to be combined in this way?

        // Add to existing header
        bstr *new_value = bstr_expand(h_existing->value, bstr_len(h_existing->value)
            + 2 + bstr_len(h->value));        
        if (new_value == NULL) {
            bstr_free(h->name);
            bstr_free(h->value);
            free(h);
            bstr_free(tempstr);
            return HTP_ERROR;
        }

        h_existing->value = new_value;
        bstr_add_mem_noex(h_existing->value, (unsigned char *)", ", 2);
        bstr_add_noex(h_existing->value, h->value);

        // replace the header references in all lines
        for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) {
          htp_header_line_t *hl = htp_list_get(connp->in_tx->request_header_lines, i);
          hl->header = h_existing;
        }

        // The header fields are no longer needed
        bstr_free(h->name);
        bstr_free(h->value);
        free(h);

        // Keep track of same-name headers        
        h_existing->flags |= HTP_FIELD_REPEATED;
    } else {
        // Add as a new header
        htp_table_add(connp->in_tx->request_headers, h->name, h);
    }

    bstr_free(tempstr);

    return HTP_OK;
}