summaryrefslogtreecommitdiffstats
path: root/extras/htptest.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--extras/htptest.c569
1 files changed, 569 insertions, 0 deletions
diff --git a/extras/htptest.c b/extras/htptest.c
new file mode 100644
index 0000000..472db5e
--- /dev/null
+++ b/extras/htptest.c
@@ -0,0 +1,569 @@
+/***************************************************************************
+ * 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>
+ */
+
+/*
+ * This program is a simple example of how to use LibHTP to parse a HTTP
+ * connection stream. It uses libnids for TCP reassembly and LibHTP for
+ * HTTP parsing.
+ *
+ * This program is only meant as an demonstration; it is not suitable
+ * to be used in production. Furthermore, libnids itself was unreliable
+ * in my tests.
+ *
+ * Compile with:
+ *
+ * $ gcc htptest.c -lhtp -lz -lnids -o htptest
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdio.h>
+#include "nids.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <htp/htp.h>
+#include <htp/htp_list.h>
+#include <htp/htp_table.h>
+
+#define DIRECTION_CLIENT 1
+#define DIRECTION_SERVER 2
+
+typedef struct chunk_t chunk_t;
+typedef struct stream_data stream_data;
+
+/** Data chunk structure */
+struct chunk_t {
+ char *data;
+ size_t len;
+ int direction;
+ size_t consumed;
+};
+
+/** Per-stream data structure */
+struct stream_data {
+ int id;
+ htp_connp_t *connp;
+ int direction;
+ int fd;
+ int chunk_counter;
+ int log_level;
+ int req_count;
+ htp_list_t *chunks;
+ htp_list_t *inbound_chunks;
+ htp_list_t *outbound_chunks;
+};
+
+/** LibHTP parser configuration */
+htp_cfg_t *cfg;
+
+/** Connection counter */
+int counter = 1000;
+
+/**
+ * Free stream data.
+ *
+ * @param[in] sd
+ */
+void free_stream_data(stream_data *sd) {
+ if (sd == NULL) return;
+
+ // Free stream chunks, if any
+ if (sd->chunks != NULL) {
+ for (int i = 0, n = htp_list_size(sd->chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->chunks, i);
+ free(chunk->data);
+ free(chunk);
+ }
+
+ htp_list_destroy(sd->chunks);
+ sd->chunked = NULL;
+ }
+
+ // Free inbound chunks, if any
+ if (sd->inbound_chunks != NULL) {
+ for (int i = 0, n = htp_list_size(sd->inbound_chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->inbound_chunkds, i);
+ free(chunk->data);
+ free(chunk);
+ }
+
+ htp_list_destroy(sd->inbound_chunks);
+ sd->inbound_chunks = NULL;
+ }
+
+ // Free outbound chunks, if any
+ if (sd->outbound_chunks != NULL) {
+ for (int i = 0, n = htp_list_size(sd->outbound_chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->outbound_chunkds, i);
+ free(chunk->data);
+ free(chunk);
+ }
+
+ htp_list_destroy(sd->outbound_chunks);
+ sd->outbound_chunks = NULL;
+ }
+
+ // Close the stream file, if we have it open
+ if (sd->fd != -1) {
+ close(sd->fd);
+ }
+
+ free(sd);
+}
+
+/**
+ * Process as much buffered inbound and outbound data as possible
+ * (in that order)
+ *
+ * @param[in] sd
+ */
+void process_stored_stream_data(stream_data *sd) {
+ int loop = 0;
+
+ do {
+ // Reset loop
+ loop = 0;
+
+ // Send as much inbound data as possible
+ while((sd->connp->in_status == HTP_STREAM_DATA)&&(htp_list_size(sd->inbound_chunks) > 0)) {
+ chunk_t *chunk = (chunk_t *)htp_list_get(sd->inbound_chunks, 0);
+
+ int rc = htp_connp_req_data(sd->connp, 0, chunk->data + chunk->consumed, chunk->len - chunk->consumed);
+ if (rc == HTP_STREAM_DATA) {
+ // The entire chunk was consumed
+ htp_list_shift(sd->inbound_chunks);
+ free(chunk->data);
+ free(chunk);
+ } else {
+ // Partial consumption
+ chunk->consumed = htp_connp_req_data_consumed(sd->connp);
+ }
+ }
+
+ // Send as much outbound data as possible
+ while((sd->connp->out_status == HTP_STREAM_DATA)&&(htp_list_size(sd->outbound_chunks) > 0)) {
+ chunk_t *chunk = (chunk_t *)htp_list_get(sd->outbound_chunks, 0);
+
+ int rc = htp_connp_res_data(sd->connp, 0, chunk->data + chunk->consumed, chunk->len - chunk->consumed);
+ if (rc == HTP_STREAM_DATA) {
+ // The entire chunk was consumed
+ htp_list_shift(sd->outbound_chunks);
+ free(chunk->data);
+ free(chunk);
+ } else {
+ // Partial consumption
+ chunk->consumed = htp_connp_res_data_consumed(sd->connp);
+ }
+
+ // Whenever we send some outbound data to the parser,
+ // we need to go back and try to send inbound data
+ // if we have it.
+ loop = 1;
+ }
+ } while(loop);
+}
+
+/**
+ * Process a chunk of the connection stream.
+ *
+ * @param[in] sd
+ * @param[in] direction
+ * @param[in] hlf
+ */
+void process_stream_data(stream_data *sd, int direction, struct half_stream *hlf) {
+ chunk_t *chunk = NULL;
+ int rc;
+
+ //printf("#DATA direction %d bytes %d\n", sd->direction, hlf->count_new);
+
+ if (sd->direction == direction) {
+ // Inbound data
+
+ switch(sd->connp->in_status) {
+ case HTP_STREAM_NEW :
+ case HTP_STREAM_DATA :
+ // Send data to parser
+
+ rc = htp_connp_req_data(sd->connp, 0, hlf->data, hlf->count_new);
+ if (rc == HTP_STREAM_DATA_OTHER) {
+ // Encountered inbound parsing block
+
+ // Store partial chunk for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new - htp_connp_req_data_consumed(sd->connp);
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data + htp_connp_req_data_consumed(sd->connp), chunk->len);
+ htp_list_add(sd->inbound_chunks, chunk);
+ } else
+ if (rc != HTP_STREAM_DATA) {
+ // Inbound parsing error
+ sd->log_level = 0;
+ fprintf(stderr, "[#%d] Inbound parsing error: %d\n", sd->id, rc);
+ // TODO Write connection to disk
+ }
+ break;
+
+ case HTP_STREAM_ERROR :
+ // Do nothing
+ break;
+
+ case HTP_STREAM_DATA_OTHER :
+ // Store data for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new;
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data, chunk->len);
+ htp_list_add(sd->inbound_chunks, chunk);
+ break;
+ }
+ } else {
+ // Outbound data
+ switch(sd->connp->out_status) {
+ case HTP_STREAM_NEW :
+ case HTP_STREAM_DATA :
+ // Send data to parser
+
+ rc = htp_connp_res_data(sd->connp, 0, hlf->data, hlf->count_new);
+ if (rc == HTP_STREAM_DATA_OTHER) {
+ // Encountered outbound parsing block
+
+ // Store partial chunk for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new - htp_connp_res_data_consumed(sd->connp);
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data + htp_connp_res_data_consumed(sd->connp), chunk->len);
+ htp_list_add(sd->outbound_chunks, chunk);
+ } else
+ if (rc != HTP_STREAM_DATA) {
+ // Outbound parsing error
+ sd->log_level = 0;
+ fprintf(stderr, "[#%d] Outbound parsing error: %d\n", sd->id, rc);
+ }
+ break;
+
+ case HTP_STREAM_ERROR :
+ // Do nothing
+ break;
+
+ case HTP_STREAM_DATA_OTHER :
+ // Store data for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new;
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data, chunk->len);
+ htp_list_add(sd->outbound_chunks, chunk);
+ break;
+ }
+ }
+
+ // Process as much stored data as possible
+ process_stored_stream_data(sd);
+}
+
+/**
+ * Called by libnids whenever it has an event we have to handle.
+ *
+ * @param[in] tcp
+ * @param[in] user_data
+ */
+void tcp_callback (struct tcp_stream *tcp, void **user_data) {
+ stream_data *sd = *user_data;
+
+ // New connection
+ if (tcp->nids_state == NIDS_JUST_EST) {
+ tcp->client.collect++;
+ tcp->server.collect++;
+ tcp->server.collect_urg++;
+ tcp->client.collect_urg++;
+
+ // Allocate custom per-stream data
+ sd = calloc(1, sizeof(stream_data));
+ sd->id = counter++;
+ sd->direction = -1;
+ sd->fd = -1;
+ sd->log_level = -1;
+ sd->chunks = htp_list_array_create(16);
+ sd->inbound_chunks = htp_list_array_create(16);
+ sd->outbound_chunks = htp_list_array_create(16);
+ sd->req_count = 1;
+
+ // Init LibHTP parser
+ sd->connp = htp_connp_create(cfg);
+ if (sd->connp == NULL) {
+ fprintf(stderr, "Failed to create LibHTP parser instance.\n");
+ exit(1);
+ }
+
+ // Associate TCP stream information with the HTTP connection parser
+ htp_connp_set_user_data(sd->connp, sd);
+
+ // Associate TCP stream information with the libnids structures
+ *user_data = sd;
+
+ return;
+ }
+
+ // Connection close
+ if (tcp->nids_state == NIDS_CLOSE) {
+ if (sd == NULL) return;
+
+ // Destroy parser
+ htp_connp_destroy_all(sd->connp);
+
+ // Free custom per-stream data
+ free_stream_data(sd);
+
+ return;
+ }
+
+ // Connection close (RST)
+ if (tcp->nids_state == NIDS_RESET) {
+ if (sd == NULL) return;
+
+ // Destroy parser
+ htp_connp_destroy_all(sd->connp);
+
+ // Free custom per-stream data
+ free_stream_data(sd);
+
+ return;
+ }
+
+ if (tcp->nids_state == NIDS_DATA) {
+ struct half_stream *hlf;
+ int direction;
+
+ if (tcp->client.count_new) {
+ hlf = &tcp->client;
+ direction = DIRECTION_SERVER;
+ } else {
+ hlf = &tcp->server;
+ direction = DIRECTION_CLIENT;
+ }
+
+ if (sd == NULL) return;
+
+ if (sd->direction == -1) {
+ sd->direction = direction;
+ }
+
+ // Write data to disk or store for later
+ if (sd->fd == -1) {
+ // Store data, as we may need it later
+ chunk_t *chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->direction = direction;
+ chunk->data = malloc(hlf->count_new);
+ // TODO
+ chunk->len = hlf->count_new;
+ memcpy(chunk->data, hlf->data, chunk->len);
+
+ htp_list_add(sd->chunks, chunk);
+ } else {
+ // No need to store, write directly to file
+
+ if (sd->chunk_counter != 0) {
+ write(sd->fd, "\r\n", 2);
+ }
+
+ if (sd->direction == direction) {
+ write(sd->fd, ">>>\r\n", 5);
+ } else {
+ write(sd->fd, "<<<\r\n", 5);
+ }
+
+ write(sd->fd, hlf->data, hlf->count_new);
+
+ sd->chunk_counter++;
+ }
+
+ // Process data
+ process_stream_data(sd, direction, hlf);
+
+ return;
+ }
+}
+
+/**
+ * Invoked at the end of every transaction.
+ *
+ * @param[in] connp
+ */
+int callback_response(htp_connp_t *connp) {
+ stream_data *sd = (stream_data *)htp_connp_get_user_data(connp);
+
+ char *x = bstr_util_strdup_to_c(connp->out_tx->request_line);
+ fprintf(stdout, "[#%d/%d] %s\n", sd->id, sd->req_count, x);
+ free(x);
+
+ sd->req_count++;
+}
+
+/**
+ * Invoked every time LibHTP wants to log.
+ *
+ * @param[in] log
+ */
+int callback_log(htp_log_t *log) {
+ stream_data *sd = (stream_data *)htp_connp_get_user_data(log->connp);
+
+ if ((sd->log_level == -1)||(sd->log_level > log->level)) {
+ sd->log_level = log->level;
+ }
+
+ if (log->code != 0) {
+ fprintf(stderr, "[#%d/%d][%d][code %d][file %s][line %d] %s\n", sd->id, sd->req_count,
+ log->level, log->code, log->file, log->line, log->msg);
+ } else {
+ fprintf(stderr, "[#%d/%d][%d][file %s][line %d] %s\n", sd->id, sd->req_count,
+ log->level, log->file, log->line, log->msg);
+ }
+
+ // If this is the first time a log message was generated for this connection,
+ // start writing the entire thing to a file on disk.
+ if (sd->fd == -1) {
+ char filename[256];
+
+ // TODO Use IP addresses and ports in filename
+ snprintf(filename, 255, "conn-%d.t", sd->id);
+
+ sd->fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
+ if (sd->fd == -1) {
+ fprintf(stderr, "Failed to create file %s: %s\n", filename, strerror(errno));
+ exit(1);
+ }
+
+ // Write to disk the data we have in memory
+ for (int i = 0, n = htp_list_size(sd->chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->chunks, i);
+
+ if (sd->chunk_counter != 0) {
+ write(sd->fd, "\r\n", 2);
+ }
+
+ if (sd->direction == chunk->direction) {
+ write(sd->fd, ">>>\r\n", 5);
+ } else {
+ write(sd->fd, "<<<\r\n", 5);
+ }
+
+ write(sd->fd, chunk->data, chunk->len);
+
+ sd->chunk_counter++;
+ }
+ }
+}
+
+/**
+ * Prints usage.
+ */
+void print_usage() {
+ fprintf(stdout, "Usage: htpMon [-r file] [\"expression\"]\n");
+}
+
+/**
+ * Main entry point for this program.
+ *
+ * @param[in] argc
+ * @param[in] argv
+ */
+int main(int argc, char *argv[]) {
+ // Check parameters
+ if ((argc < 2)||(argc > 4)) {
+ print_usage();
+ return 1;
+ }
+
+ // Configure libnids
+ if (argc > 2) {
+ if (strcmp(argv[1], "-r") != 0) {
+ print_usage();
+ return 1;
+ }
+
+ nids_params.filename = argv[2];
+
+ if (argc == 4) {
+ nids_params.pcap_filter = argv[3];
+ }
+ } else {
+ nids_params.pcap_filter = argv[1];
+ }
+
+ // Initialize libnids
+ if (!nids_init()) {
+ fprintf(stderr, "libnids initialization failed: %s\n", nids_errbuf);
+ return 1;
+ }
+
+ // Create LibHTP configuration
+ cfg = htp_config_create();
+ htp_config_set_server_personality(cfg, HTP_SERVER_APACHE_2_2);
+
+ htp_config_register_response_complete(cfg, callback_response);
+ htp_config_register_log(cfg, callback_log);
+
+ // Run libnids
+ nids_register_tcp(tcp_callback);
+ nids_run();
+
+ // Destroy LibHTP configuration
+ htp_config_destroy(cfg);
+
+ return 0;
+}
+