diff options
Diffstat (limited to 'test/test.c')
-rw-r--r-- | test/test.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..8c12254 --- /dev/null +++ b/test/test.c @@ -0,0 +1,444 @@ +/*************************************************************************** + * Copyright (c) 2009-2010 Open Information Security Foundation + * Copyright (c) 2010-2013 Qualys, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + + * - Neither the name of the Qualys, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************************************************************************/ + +/** + * @file + * @author Ivan Ristic <ivanr@webkreator.com> + */ + +#include <assert.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> + +#include "../htp/htp.h" +#include "test.h" + +/** + * Destroys a test. + * + * @param[in] test + */ +static void test_destroy(test_t *test) { + if (test->buf != NULL) { + free(test->buf); + test->buf = NULL; + } +} + +/** + * Checks if there's a chunk boundary at the given position. + * + * @param[in] test + * @param[in] pos + * @return Zero if there is no boundary, SERVER or CLIENT if a boundary + * was found, and a negative value on error (e.g., not enough data + * to determine if a boundary is present). + */ +static int test_is_boundary(test_t *test, size_t pos) { + // Check that there's enough room + if (pos + 3 >= test->len) return -1; + + if ((test->buf[pos] == '<') && (test->buf[pos + 1] == '<' || test->buf[pos + 1] == '>') && (test->buf[pos + 2] == '<')) { + if (test->buf[pos + 3] == '\n') { + return SERVER; + } + + if (test->buf[pos + 3] == '\r') { + if (pos + 4 >= test->len) return -1; + else if (test->buf[pos + 4] == '\n') { + return SERVER; + } + } + } + + if ((test->buf[pos] == '>') && (test->buf[pos + 1] == '>' || test->buf[pos + 1] == '<') && (test->buf[pos + 2] == '>')) { + if (test->buf[pos + 3] == '\n') { + return CLIENT; + } + + if (test->buf[pos + 3] == '\r') { + if (pos + 4 >= test->len) return -1; + else if (test->buf[pos + 4] == '\n') { + return CLIENT; + } + } + } + + return 0; +} + +/** + * Initializes test by loading the entire data file into a memory block. + * + * @param[in] test + * @param[in] filename + * @return Non-negative value on success, negative value on error. + */ +static int test_init(test_t *test, const char *filename, int clone_count) { + memset(test, 0, sizeof (test_t)); + + int fd = open(filename, O_RDONLY | O_BINARY); + if (fd < 0) return -1; + + struct stat buf; + if (fstat(fd, &buf) < 0) { + close(fd); + return -1; + } + + test->buf = malloc(buf.st_size * clone_count + clone_count - 1); + test->len = 0; + test->pos = 0; + + // Check that we received our memory. + assert(test->buf != NULL); + + int bytes_read = 0; + while ((bytes_read = read(fd, test->buf + test->len, buf.st_size - test->len)) > 0) { + test->len += bytes_read; + } + + if ((int)test->len != buf.st_size) { + free(test->buf); + close(fd); + return -2; + } + + close(fd); + + int i = 1; + for (i = 1; i < clone_count; i++) { + test->buf[i * buf.st_size + (i-1)] = '\n'; + memcpy(test->buf + i * buf.st_size + i, test->buf, buf.st_size); + } + + test->len = buf.st_size * clone_count + clone_count - 1; + + return 1; +} + +static void test_start(test_t *test) { + test->pos = 0; +} + +/** + * Finds the next data chunk in the given test. + * + * @param[in] test + * @return One if a chunk is found or zero if there are no more chunks in the test. On + * success, test->chunk will point to the beginning of the chunk, while + * test->chunk_len will contain its length. + */ +int test_next_chunk(test_t *test) { + if (test->pos >= test->len) { + return 0; + } + + test->chunk = NULL; + int isgap = 0; + + while (test->pos < test->len) { + // Do we need to start another chunk? + if (test->chunk == NULL) { + // Are we at a boundary + test->chunk_direction = test_is_boundary(test, test->pos); + if (test->chunk_direction <= 0) { + // Error + return -1; + } + + if (test->buf[test->pos + 1] != test->buf[test->pos + 2]) { + isgap = 1; + } else { + isgap = 0; + } + // Move over the boundary + test->pos += 4; + if (test->pos >= test->len) { + return 0; + } + if (test->buf[test->pos-1] == '\r') test->pos++; + if (test->pos >= test->len) { + return 0; + } + + // Start new chunk + test->chunk = test->buf + test->pos; + test->chunk_offset = test->pos; + // if it is empty (boundary already), continue to next chunk + if (test_is_boundary(test, test->pos) > 0) { + test->chunk = NULL; + continue; + } + } + + // Are we at the end of a line? + if (test->buf[test->pos] == '\n') { + int r = test_is_boundary(test, test->pos + 1); + if ((r == CLIENT) || (r == SERVER)) { + // We got ourselves a chunk + test->chunk_len = test->pos - test->chunk_offset; + + // Remove one '\r' (in addition to the '\n' that we've already removed), + // which belongs to the next boundary + if ((test->chunk_len > 0) && (test->chunk[test->chunk_len - 1] == '\r')) { + test->chunk_len--; + } + + // Position at the next boundary line + test->pos++; + if (test->pos >= test->len) { + return 0; + } + if (isgap) { + test->chunk = NULL; + } + + return 1; + } + } + + test->pos++; + } + + + if (test->chunk != NULL) { + test->chunk_len = test->pos - test->chunk_offset; + if (isgap) { + test->chunk = NULL; + } + return 1; + } + + return 0; +} + +static int parse_filename(const char *filename, char **remote_addr, int *remote_port, char **local_addr, int *local_port) { + char *copy = strdup(filename); + char *p, *saveptr; + + char *start = copy; + char *q = strrchr(copy, '/'); + if (q != NULL) start = q; + + q = strrchr(start, '\\'); + if (q != NULL) start = q; + + int count = 0; + p = strtok_r(start, "_", &saveptr); + while (p != NULL) { + count++; + // printf("%i %s\n", count, p); + + switch (count) { + case 2: + *remote_addr = strdup(p); + break; + case 3: + *remote_port = atoi(p); + break; + case 4: + *local_addr = strdup(p); + break; + case 5: + *local_port = atoi(p); + break; + } + + p = strtok_r(NULL, "_", &saveptr); + } + + free(copy); + + return 0; +} + +/** + * Runs a single test. + * + * @param[in] filename + * @param[in] cfg + * @return A pointer to the instance of htp_connp_t created during + * the test, or NULL if the test failed for some reason. + */ +int test_run_ex(const char *testsdir, const char *testname, htp_cfg_t *cfg, htp_connp_t **connp, int clone_count) { + char filename[1025]; + test_t test; + struct timeval tv_start, tv_end; + int rc; + + *connp = NULL; + + strncpy(filename, testsdir, 1024); + strncat(filename, "/", 1024 - strlen(filename)); + strncat(filename, testname, 1024 - strlen(filename)); + + // printf("Filename: %s\n", filename); + + // Initinialize test + + rc = test_init(&test, filename, clone_count); + if (rc < 0) { + return rc; + } + + gettimeofday(&tv_start, NULL); + + test_start(&test); + + // Create parser + *connp = htp_connp_create(cfg); + if (*connp == NULL) { + fprintf(stderr, "Failed to create connection parser\n"); + exit(1); + } + + htp_connp_set_user_data(*connp, (void *) 0x02); + + // Does the filename contain connection metdata? + if (strncmp(testname, "stream", 6) == 0) { + // It does; use it + char *remote_addr = NULL, *local_addr = NULL; + int remote_port = -1, local_port = -1; + + parse_filename(testname, &remote_addr, &remote_port, &local_addr, &local_port); + htp_connp_open(*connp, (const char *) remote_addr, remote_port, (const char *) local_addr, local_port, &tv_start); + free(remote_addr); + free(local_addr); + } else { + // No connection metadata; provide some fake information instead + htp_connp_open(*connp, (const char *) "127.0.0.1", 10000, (const char *) "127.0.0.1", 80, &tv_start); + } + + // Find all chunks and feed them to the parser + int in_data_other = 0; + char *in_data = NULL; + size_t in_data_len = 0; + size_t in_data_offset = 0; + + int out_data_other = 0; + char *out_data = NULL; + size_t out_data_len = 0; + size_t out_data_offset = 0; + + for (;;) { + if (test_next_chunk(&test) <= 0) { + break; + } + + if (test.chunk_direction == CLIENT) { + if (in_data_other) { + test_destroy(&test); + fprintf(stderr, "Unable to buffer more than one inbound chunk.\n"); + return -1; + } + + rc = htp_connp_req_data(*connp, &tv_start, test.chunk, test.chunk_len); + if (rc == HTP_STREAM_ERROR) { + test_destroy(&test); + return -101; + } + if (rc == HTP_STREAM_DATA_OTHER) { + // Parser needs to see the outbound stream in order to continue + // parsing the inbound stream. + in_data_other = 1; + in_data = test.chunk; + in_data_len = test.chunk_len; + in_data_offset = htp_connp_req_data_consumed(*connp); + } + } else { + if (out_data_other) { + rc = htp_connp_res_data(*connp, &tv_start, out_data + out_data_offset, out_data_len - out_data_offset); + if (rc == HTP_STREAM_ERROR) { + test_destroy(&test); + return -104; + } + + out_data_other = 0; + } + + rc = htp_connp_res_data(*connp, &tv_start, test.chunk, test.chunk_len); + if (rc == HTP_STREAM_ERROR) { + test_destroy(&test); + return -102; + } + if (rc == HTP_STREAM_DATA_OTHER) { + // Parser needs to see the outbound stream in order to continue + // parsing the inbound stream. + out_data_other = 1; + out_data = test.chunk; + out_data_len = test.chunk_len; + out_data_offset = htp_connp_res_data_consumed(*connp); + // printf("# YYY out offset is %d\n", out_data_offset); + } + + if (in_data_other) { + rc = htp_connp_req_data(*connp, &tv_start, in_data + in_data_offset, in_data_len - in_data_offset); + if (rc == HTP_STREAM_ERROR) { + test_destroy(&test); + return -103; + } + + in_data_other = 0; + } + } + } + + if (out_data_other) { + rc = htp_connp_res_data(*connp, &tv_start, out_data + out_data_offset, out_data_len - out_data_offset); + if (rc == HTP_STREAM_ERROR) { + test_destroy(&test); + return -104; + } + out_data_other = 0; + } + + gettimeofday(&tv_end, NULL); + + // Close the connection + htp_connp_close(*connp, &tv_end); + + // Clean up + test_destroy(&test); + + return 1; +} + +int test_run(const char *testsdir, const char *testname, htp_cfg_t *cfg, htp_connp_t **connp) { + return test_run_ex(testsdir, testname, cfg, connp, 1); +} |