Example #1
0
/**
 * The idle state is invoked before and after every transaction. Consequently,
 * it will start a new transaction when data is available and finalise a transaction
 * which has been processed.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_REQ_IDLE(htp_connp_t * connp) {
    // If we're here and a transaction object exists that
    // means we've just completed parsing a request. We need
    // to run the final hook and start over.
    if (connp->in_tx != NULL) {
        // Run hook REQUEST
        int rc = hook_run_all(connp->cfg->hook_request, connp);
        if (rc != HOOK_OK) {
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                "Request callback returned error (%d)", rc);
            return HTP_ERROR;
        }

        // Start afresh
        connp->in_tx = NULL;
    }

    // We want to start parsing the next request (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.
    IN_TEST_NEXT_BYTE_OR_RETURN(connp);

    // Detect pipelining
    if (list_size(connp->conn->transactions) > connp->out_next_tx_index) {
        connp->conn->flags |= PIPELINED_CONNECTION;
    }

    // Parsing a new request
    connp->in_tx = htp_tx_create(connp->cfg, CFG_SHARED, connp->conn);
    if (connp->in_tx == NULL) return HTP_ERROR;

    connp->in_tx->connp = connp;

    list_add(connp->conn->transactions, connp->in_tx);

    connp->in_content_length = -1;
    connp->in_body_data_left = -1;
    connp->in_header_line_index = -1;
    connp->in_header_line_counter = 0;
    connp->in_chunk_request_index = connp->in_chunk_count;

    // Run hook TRANSACTION_START
    int rc = hook_run_all(connp->cfg->hook_transaction_start, connp);
    if (rc != HOOK_OK) {
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
            "Transaction start callback returned error (%d)", rc);
        return HTP_ERROR;
    }

    // Change state into request line parsing
    connp->in_state = htp_connp_REQ_LINE;
    connp->in_tx->progress[0] = TX_PROGRESS_REQ_LINE;

    return HTP_OK;
}
Example #2
0
/**
 * Processes identity request body.
 *
 * @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_IDENTITY(htp_connp_t *connp) {
    htp_tx_data_t d;

    d.tx = connp->in_tx;
    d.data = &connp->in_current_data[connp->in_current_offset];
    d.len = 0;

    for (;;) {
        IN_NEXT_BYTE(connp);

        if (connp->in_next_byte == -1) {
            // End of chunk

            if (d.len != 0) {
                int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
                if (rc != HOOK_OK) {
                    htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                        "Request body data callback returned error (%d)", rc);
                    return HTP_ERROR;
                }
            }

            // Ask for more data
            return HTP_DATA;
        } else {
            connp->in_tx->request_message_len++;
            connp->in_tx->request_entity_len++;
            connp->in_body_data_left--;
            d.len++;

            if (connp->in_body_data_left == 0) {
                // End of body

                if (d.len != 0) {
                    int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
                    if (rc != HOOK_OK) {
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                            "Request body data callback returned error (%d)", rc);
                        return HTP_ERROR;
                    }
                }

                // Done
                connp->in_state = htp_connp_REQ_IDLE;
                connp->in_tx->progress[0] = TX_PROGRESS_WAIT;

                return HTP_OK;
            }
        }
    }
    return HTP_ERROR;
}
Example #3
0
/**
 * 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_REQ_BODY_CHUNKED_DATA(htp_connp_t *connp) {
    htp_tx_data_t d;

    d.tx = connp->in_tx;
    d.data = &connp->in_current_data[connp->in_current_offset];
    d.len = 0;

    for (;;) {
        IN_NEXT_BYTE(connp);

        if (connp->in_next_byte == -1) {
            // Send data to callbacks
            int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
            if (rc != HOOK_OK) {
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                    "Request body data callback returned error (%d)", rc);
                return HTP_ERROR;
            }

            // Ask for more data
            return HTP_DATA;
        } else {
            connp->in_tx->request_message_len++;
            connp->in_tx->request_entity_len++;
            connp->in_chunked_length--;
            d.len++;

            if (connp->in_chunked_length == 0) {
                // End of data chunk

                // Send data to callbacks
                int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
                if (rc != HOOK_OK) {
                    htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                        "Request body data callback returned error (%d)", rc);
                    return HTP_ERROR;
                }

                connp->in_state = htp_connp_REQ_BODY_CHUNKED_DATA_END;

                return HTP_OK;
            }
        }
    }
    return HTP_ERROR;
}
Example #4
0
int htp_mpartp_run_request_file_data_hook(htp_mpart_part_t *part, unsigned char *data, size_t len) {
    if (part->mpartp->connp == NULL) return HTP_OK;   

    htp_file_data_t file_data;

    file_data.tx = part->mpartp->connp->in_tx;
    file_data.file = part->file;
    file_data.file->len += len;
    file_data.data = data;
    file_data.len = len;

    // Send data to callbacks
    int rc = hook_run_all(part->mpartp->connp->cfg->hook_request_file_data, &file_data);
    if (rc != HOOK_OK) {
        // TODO
    }

    return HTP_OK;
}
Example #5
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;
        }
    }
}
Example #6
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++;
        }
    }
}
Example #7
0
/**
 * Determines presence (and encoding) of a request body.
 *
 * @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_DETERMINE(htp_connp_t *connp) {
    htp_header_t *cl = table_get_c(connp->in_tx->request_headers, "content-length");
    htp_header_t *te = table_get_c(connp->in_tx->request_headers, "transfer-encoding");

    // Check for the Transfer-Encoding header, which
    // would indicate a chunked request body
    if (te != NULL) {
        // Make sure it contains "chunked" only
        if (bstr_cmp_c(te->value, "chunked") != 0) {
            // Invalid T-E header value
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                "Invalid T-E value in request");
        }

        // Chunked encoding is a HTTP/1.1 feature. Check
        // that some other protocol is not used. The flag will
        // also be set if the protocol could not be parsed.
        //
        // TODO IIS 7.0, for example, would ignore the T-E header when it
        //      it is used with a protocol below HTTP 1.1.
        if (connp->in_tx->request_protocol_number < HTTP_1_1) {
            connp->in_tx->flags |= HTP_INVALID_CHUNKING;
            // TODO Log
        }

        // If the T-E header is present we are going to use it.
        connp->in_tx->request_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->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        connp->in_state = htp_connp_REQ_BODY_CHUNKED_LENGTH;
        connp->in_tx->progress = TX_PROGRESS_REQ_BODY;
    } else
        // Next check for the presence of the Content-Length header
        if (cl != NULL) {
        // It seems that we have a request body.
        connp->in_tx->request_transfer_coding = IDENTITY;

        // Check for a folded C-L header
        if (cl->flags & HTP_FIELD_FOLDED) {
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        // Check for multiple C-L headers
        if (cl->flags & HTP_FIELD_REPEATED) {
            connp->in_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 request");
            return HTP_ERROR;
        } else {
            connp->in_content_length = i;
            connp->in_body_data_left = connp->in_content_length;

            if (connp->in_content_length != 0) {
                connp->in_state = htp_connp_REQ_BODY_IDENTITY;
                connp->in_tx->progress = TX_PROGRESS_REQ_BODY;
            } else {
                connp->in_state = htp_connp_REQ_IDLE;
                connp->in_tx->progress = TX_PROGRESS_WAIT;
            }
        }
    } else {
        // This request does not have a body, which
        // means that we're done with it
        connp->in_state = htp_connp_REQ_IDLE;
        connp->in_tx->progress = TX_PROGRESS_WAIT;
    }

    // Check for PUT requests, which we need to treat as file uploads
    if (connp->in_tx->request_method_number == M_PUT) {
        if (connp->in_tx->connp->in_tx->request_transfer_coding != 0) {
            // Prepare to treat PUT request body as a file
            connp->put_file = calloc(1, sizeof (htp_file_t));
            if (connp->put_file == NULL) return HTP_ERROR;
            connp->put_file->source = HTP_FILE_PUT;
        } else {
            // TODO Warn about PUT request without a body
        }

        return HTP_OK;
    }

    // Host resolution    
    htp_header_t *h = table_get_c(connp->in_tx->request_headers, "host");
    if (h == NULL) {
        // No host information in the headers

        // HTTP/1.1 requires host information in the headers
        if (connp->in_tx->request_protocol_number >= HTTP_1_1) {
            connp->in_tx->flags |= HTP_HOST_MISSING;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
                "Host information in request headers required by HTTP/1.1");
        }
    } else {
        // Host information available in the headers

        // Is there host information in the URI?
        if (connp->in_tx->parsed_uri->hostname == NULL) {
            // There is no host information in the URI. Place the
            // hostname from the headers into the parsed_uri structure.
            htp_replace_hostname(connp, connp->in_tx->parsed_uri, h->value);
        } else if (bstr_cmp_nocase(h->value, connp->in_tx->parsed_uri->hostname) != 0) {
            // The host information is different in the
            // headers and the URI. The HTTP RFC states that
            // we should ignore the headers copy.
            connp->in_tx->flags |= HTP_AMBIGUOUS_HOST;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Host information ambiguous");
        }
    }

    // Parse Content-Type
    htp_header_t *ct = table_get_c(connp->in_tx->request_headers, "content-type");
    if (ct != NULL) {
        connp->in_tx->request_content_type = bstr_dup_lower(ct->value);
        if (connp->in_tx->request_content_type == NULL) {
            return HTP_ERROR;
        }
        
        // Ignore parameters        
        char *data = bstr_ptr(connp->in_tx->request_content_type);
        size_t len = bstr_len(ct->value);
        size_t newlen = 0;
        while (newlen < len) {
            // TODO Some platforms may do things differently here
            if (htp_is_space(data[newlen]) || (data[newlen] == ';')) {
                bstr_util_adjust_len(connp->in_tx->request_content_type, newlen);
                break;
            }

            newlen++;
        }
    }

    // Parse cookies
    if (connp->cfg->parse_request_cookies) {
        htp_parse_cookies_v0(connp);
    }

    // Parse authentication information
    if (connp->cfg->parse_request_http_authentication) {
        htp_parse_authorization(connp);
    }

    // Run hook REQUEST_HEADERS
    int rc = hook_run_all(connp->cfg->hook_request_headers, 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;
        }
    }

    return HTP_OK;
}
Example #8
0
/**
 * 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 the last RESPONSE_BODY_DATA HOOK, but
        // only if there was a response body
        if (connp->out_tx->response_transfer_coding != -1) {
            htp_tx_data_t d;
            d.data = NULL;
            d.tx = connp->out_tx;
            htp_res_run_hook_body_data(connp, &d);
        }

        // 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;
        }

        if (connp->cfg->tx_auto_destroy) {
            htp_tx_destroy(connp->out_tx);
        }

        // 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;
        
        // Do we have a signal to yield to inbound processing at
        // the end of the next transaction?
        if (connp->out_data_other_at_tx_end) {
            // We do. Let's yield then.
            connp->out_data_other_at_tx_end = 0;
            return HTP_DATA_OTHER;
        }
    }

    // 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;
}
Example #9
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;
        }
    }
}
Example #10
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++;
        }
    }
}
Example #11
0
/**
 * 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) {
        if ((connp->out_tx->response_status_number >= 200)
            && (connp->out_tx->response_status_number <= 299))
        {
            // This is a successful CONNECT stream, which means
            // we need to switch into tunnelling mode.
            connp->in_status = STREAM_STATE_TUNNEL;
            connp->out_status = STREAM_STATE_TUNNEL;
            connp->out_state = htp_connp_RES_IDLE;
            connp->out_tx->progress = TX_PROGRESS_DONE;
            return HTP_OK;
        } else {
            // This is a failed CONNECT stream, which means that
            // we can unblock request parsing
            connp->in_status = STREAM_STATE_DATA;
            
            // We are going to continue processing this transaction,
            // adding a note for ourselves to stop at the end (because
            // we don't want to see the beginning of a new transaction).
            connp->out_data_other_at_tx_end = 1;
        }       
    }

    // 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
    if (connp->cfg->response_decompression_enabled) {
        htp_header_t *ce = table_get_c(connp->out_tx->response_headers, "content-encoding");
        if (ce != NULL) {
            if ((bstr_cmp_c(ce->value, "gzip") == 0) || (bstr_cmp_c(ce->value, "x-gzip") == 0)) {
                connp->out_tx->response_content_encoding = COMPRESSION_GZIP;
            } else if ((bstr_cmp_c(ce->value, "deflate") == 0) || (bstr_cmp_c(ce->value, "x-deflate") == 0)) {
                connp->out_tx->response_content_encoding = COMPRESSION_DEFLATE;
            }

            if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) {
                connp->out_decompressor = (htp_decompressor_t *) htp_gzip_decompressor_create(connp,
                    connp->out_tx->response_content_encoding);
                if (connp->out_decompressor != NULL) {
                    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_get_c(connp->out_tx->response_headers, "content-length");
        htp_header_t *te = table_get_c(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) && (bstr_cmp_c(te->value, "chunked") == 0)) {
            // 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: %d", i);
                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_get_c(connp->out_tx->response_headers, "content-type");
            if (ct != NULL) {
                // TODO Handle multipart/byteranges

                if (bstr_index_of_c_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;
}
Example #12
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;
}
Example #13
0
/**
 * Determines presence (and encoding) of a request body.
 *
 * @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_DETERMINE(htp_connp_t *connp) {
    htp_header_t *cl = table_getc(connp->in_tx->request_headers, "content-length");
    htp_header_t *te = table_getc(connp->in_tx->request_headers, "transfer-encoding");

    // Check for the Transfer-Encoding header, which
    // would indicate a chunked request body
    if (te != NULL && te->value != NULL) {
        // Make sure it contains "chunked" only
        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 request");
        }

        // Chunked encoding is a HTTP/1.1 feature. Check
        // that some other protocol is not used. The flag will
        // also be set if the protocol could not be parsed.
        //
        // TODO IIS 7.0, for example, would ignore the T-E header when it
        //      it is used with a protocol below HTTP 1.1.
        if (connp->in_tx->request_protocol_number < HTTP_1_1) {
            connp->in_tx->flags |= HTP_INVALID_CHUNKING;
            // TODO Log
        }

        // If the T-E header is present we are going to use it.
        connp->in_tx->request_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->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        connp->in_state = htp_connp_REQ_BODY_CHUNKED_LENGTH;
        connp->in_tx->progress[0] = TX_PROGRESS_REQ_BODY;
    } else
        // Next check for the presence of the Content-Length header
        if (cl != NULL && cl->value != NULL) {
        // It seems that we have a request body.
        connp->in_tx->request_transfer_coding = IDENTITY;

        // Check for a folded C-L header
        if (cl->flags & HTP_FIELD_FOLDED) {
            connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
            // TODO Log
        }

        // Check for multiple C-L headers
        if (cl->flags & HTP_FIELD_REPEATED) {
            connp->in_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 request");
            return HTP_ERROR;
        } else {
            connp->in_content_length = i;
            connp->in_body_data_left = connp->in_content_length;

            if (connp->in_content_length != 0) {
                connp->in_state = htp_connp_REQ_BODY_IDENTITY;
                connp->in_tx->progress[0] = TX_PROGRESS_REQ_BODY;
            } else {
                connp->in_state = htp_connp_REQ_IDLE;
                connp->in_tx->progress[0] = TX_PROGRESS_WAIT;
            }
        }
    } else {
        // This request does not have a body, which
        // means that we're done with it
        connp->in_state = htp_connp_REQ_IDLE;
        connp->in_tx->progress[0] = TX_PROGRESS_WAIT;
    }

    // Host resolution    
    htp_header_t *h = table_getc(connp->in_tx->request_headers, "host");
    if (h == NULL) {
        // No host information in the headers

        // HTTP/1.1 requires host information in the headers
        if (connp->in_tx->request_protocol_number >= HTTP_1_1) {
            connp->in_tx->flags |= HTP_HOST_MISSING;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
                "Host information in request headers required by HTTP/1.1");
        }
    } else {
        // Host information available in the headers

        // Is there host information in the URI?
        if (connp->in_tx->parsed_uri->hostname == NULL) {
            // There is no host information in the URI. Place the
            // hostname from the headers into the parsed_uri structure.
            htp_replace_hostname(connp, connp->in_tx->parsed_uri, h->value);
        } else if (bstr_cmp_nocase(h->value, connp->in_tx->parsed_uri->hostname) != 0) {
            // The host information is different in the
            // headers and the URI. The HTTP RFC states that
            // we should ignore the headers copy.
            connp->in_tx->flags |= HTP_AMBIGUOUS_HOST;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Host information ambiguous");
        }
    }

    // Run hook REQUEST_HEADERS
    int rc = hook_run_all(connp->cfg->hook_request_headers, connp);
    if (rc != HOOK_OK) {
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
            "Request headers callback returned error (%d)", rc);
        return HTP_ERROR;
    }

    return HTP_OK;
}