Blob Blame History Raw
/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#include <sys/param.h>

#include "alp2.h"

#ifdef DEBUG
#define alp_debug(...) fprintf(stderr, __VA_ARGS__)
#else
#define alp_debug(...)
#endif /* DEBUG */

/**
 * Add one error to the audit log entry.
 */
static void add_error(alp2_t *alp, int is_fatal, const char *text, ...)
{
    char *str = NULL;
    va_list ap;

    if (is_fatal) {
        alp->parse_error = 1;
    }

    va_start(ap, text);
    str = apr_pvsprintf(alp->auditlog->mp, text, ap);
    va_end(ap);

    *(char **)apr_array_push(alp->errors) = str;
}

/**
 * Parse the Response-Body-Transformed trailer header.
 */
static int handle_part_H_parse_ResponseTFN(alp2_t *alp, const char *s)
{
    char *capture = NULL;
    int ovector[33];
    int rc;

    // TODO This header is optional, but is not allowed to appear more than once.


    return 1;
}

/**
 * Parse the Action trailer header.
 */
static int handle_part_H_parse_Action(alp2_t *alp, const char *s)
{
    char *capture = NULL;
    int ovector[33];
    int rc;

    // TODO This header is optional, but is not allowed to appear more than once.

    alp->auditlog->was_intercepted = 1;

    rc = pcre_exec(alp->trailer_action_pattern, NULL, s, strlen(s), 0, 0, ovector, 30);
    if (rc < 0) {
        add_error(alp, 1, "Part H: Failed to parse Action header");
        return -1;
    }

    capture = apr_pstrmemdup(alp->auditlog->mp, s + ovector[2 * 1],
        ovector[2 * 1 + 1] - ovector[2 * 1]);

    alp->auditlog->intercept_phase = atoi(capture);

    return 1;
}

/**
 * Convert two hexadecimal characters into a character.
 */
static uint8_t x2c(uint8_t *what)
{
    register uint8_t digit;

    digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
    digit *= 16;
    digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));

    return digit;
}

/**
 * Remove a layer of encoding from a string. This function needs to be used
 * for every piece of data ModSecurity encoded for a message.
 */
static int remove_slashes(uint8_t *s)
{
    uint8_t *d = s;

    while(*s != '\0') {
        if ((*s == '\\')&&(*(s + 1) != '\0')) {
            s++;

            switch(*s) {
                case 'b' :
                    *d = '\b';
                    break;
                case 'n' :
                    *d = '\n';
                    break;
                case 'r' :
                    *d = '\r';
                    break;
                case 't' :
                    *d = '\t';
                    break;
                case 'v' :
                    *d = '\v';
                    break;
                case '\\' :
                    *d = '\\';
                    break;
                case '"' :
                    *d = '"';
                    break;
                case 'x' :
                    if (  (*(s + 1) != '\0')
                        &&(*(s + 2) != '\0')
                        &&(isxdigit(*(s + 1)))
                        &&(isxdigit(*(s + 2)))
                    ) {
                        *d = x2c(s + 1);
                        s += 2;
                    }
                    else {
                        /* Invalid encoding. */
                        return -1;
                    }
                    break;
                default :
                    /* Invalid encoding. */
                    return -1;
                    break;
            }
        }
        else {
            *d = *s;
        }

        s++;
        d++;
    }

    *d = '\0';

    return 1;
}

/**
 * Process one (ModSecurity message) meta-data fragment.
 */
static int handle_part_H_parse_Message_meta(alp2_t *alp, alp2_msg_t *message,
    const char *l, const char *string_start, const char *string_end)
{
    const char *value;

    // XXX if ((*string_start != '"')||(*string_end != '"')||(string_end <= string_start)) {
    if (string_end <= string_start) {
        add_error(alp, 1, "Part H: Invalid handle_part_H_parse_Message_meta invocation");
        return -1;
    }

    if ((*string_start != '"')||(*string_end != '"')) {
        value = apr_pstrndup(alp->auditlog->mp, string_start, (string_end - string_start) + 1);
    }
    else {
        value = apr_pstrndup(alp->auditlog->mp, string_start + 1, (string_end - string_start - 1));
    }
    if (value == NULL) {
        return -1;
    }

    if (remove_slashes((uint8_t *)value) < 0) {
        add_error(alp, 1, "Part H: Invalid encoding in meta-data fragment");
        return -1;
    }

    /* Target ( at THE_TARGET) */
    if (strncmp(l, "at ", 3) == 0) {
        if (message->target != NULL) {
            add_error(alp, 1, "Part H: Already seen target");
            return -1;
        }

        message->target = value;

        return 1;
    }

    /* id */
    if (strncmp(l, "id ", 3) == 0) {
        if (message->id != NULL) {
            add_error(alp, 1, "Part H: Already seen meta-data: id");
            return -1;
        }

        message->id = value;

        return 1;
    }

    /* rev */
    if (strncmp(l, "rev ", 4) == 0) {
        if (message->rev != NULL) {
            add_error(alp, 1, "Part H: Already seen meta-data: rev");
            return -1;
        }

        message->rev = value;

        return 1;
    }

    /* msg */
    if (strncmp(l, "msg ", 4) == 0) {
        if (message->msg != NULL) {
            add_error(alp, 1, "Part H: Already seen meta-data: msg");
            return -1;
        }

        message->msg = value;

        return 1;
    }

    /* data */
    if (strncmp(l, "data ", 4) == 0) {
        if (message->data != NULL) {
            add_error(alp, 1, "Part H: Already seen meta-data: data");
            return -1;
        }

        message->data = value;

        return 1;
    }

    /* file */
    if (strncmp(l, "file ", 5) == 0) {
        if (message->file != NULL) {
            add_error(alp, 1, "Part H: Already seen meta-data: file");
            return -1;
        }

        message->file = value;

        return 1;
    }

    /* line */
    if (strncmp(l, "line ", 5) == 0) {
        if (message->file_line != (unsigned long)-1) {
            add_error(alp, 1, "Part H: Already seen meta-data: line");
            return -1;
        }

        // TODO Validate.
        message->file_line = atoi(value);

        return 1;
    }

    /* tag */
    if (strncmp(l, "tag ", 4) == 0) {
        *(char **)apr_array_push(message->tags) = (char *)value;
        return 1;
    }

    /* severity */
    if (strncmp(l, "severity ", 9) == 0) {
        if (  (strcmp(value, "0") == 0)
            ||(strcasecmp(value, "EMERGENCY") == 0))
        {
            message->severity = 0;
            return 1;
        }

        if (  (strcmp(value, "1") == 0)
            ||(strcasecmp(value, "ALERT") == 0))
        {
            message->severity = 1;
            return 1;
        }

        if (  (strcmp(value, "2") == 0)
            ||(strcasecmp(value, "CRITICAL") == 0))
        {
            message->severity = 2;
            return 1;
        }

        if (  (strcmp(value, "3") == 0)
            ||(strcasecmp(value, "ERROR") == 0))
        {
            message->severity = 3;
            return 1;
        }

        if (  (strcmp(value, "4") == 0)
            ||(strcasecmp(value, "WARNING") == 0))
        {
            message->severity = 4;
            return 1;
        }

        if (  (strcmp(value, "5") == 0)
            ||(strcasecmp(value, "NOTICE") == 0))
        {
            message->severity = 5;
            return 1;
        }

        if (  (strcmp(value, "6") == 0)
            ||(strcasecmp(value, "INFO") == 0))
        {
            message->severity = 6;
            return 1;
        }

        if (  (strcmp(value, "7") == 0)
            ||(strcasecmp(value, "DEBUG") == 0))
        {
            message->severity = 7;
            return 1;
        }

        add_error(alp, 1, "Part H: Invalid severity value: %s", value);
        
        return -1;
    }

    /* offset */
    if (strncmp(l, "offset ", 7) == 0) {
        if (message->offset != (size_t)-1) {
            /* Already seen "offset". */
            add_error(alp, 1, "Part H: Already seen fragment offset");
            return -1;
        }

        // TODO Validate.
        message->offset = atoi(value);

        return 1;
    }

    /* Ignore unknown meta-data information. */
    
    return 0;
}

/**
 * Parse the Message trailer header. More than one such header
 * can exist in an audit log, and each represents one ModSecurity
 * message.
 */
static int handle_part_H_parse_Message(alp2_t *alp, const char *s)
{
    alp2_msg_t *message = NULL;
    char *l = (char *)(s + strlen(s) - 1);
    char *engine_message_start = (char *)s;
    char *engine_message_end = NULL;
    char *string_start = NULL, *string_end = NULL;
    char *fragment_end = NULL;
    char *tptr;
    int in_string;
    int done;

    /* Create one new message structure. */
    message = apr_pcalloc(alp->auditlog->mp, sizeof(alp2_msg_t));
    if (message == NULL) {
        return -1;
    }

    message->file_line = (unsigned long)-1;
    message->offset = (size_t)-1;
    message->severity = -1;
    message->warning = 0;

    if (strncasecmp("warning. ", s, 9) == 0) {
        message->warning = 1;
        engine_message_start += 9;
    }

    message->tags = apr_array_make(alp->auditlog->mp, 4, sizeof(const char *));

    /* Start at the end of the message and go back identifying
     * the meta-data fragments as we go. Stop when we find the
     * end of the engine message.
     */
    done = in_string = 0;
    while ((l >= s)&&(!done)) {
        if (in_string == 0) {
            /* Outside string. */

            // TODO Make sure this is not an escaped char
            switch(*l) {
                case ' ' :
                    /* Do nothing. */
                    break;
                case ']' :
                    fragment_end = l;
                    break;
                case '[' :
                    if (fragment_end) {
                        /* Found one meta-data fragment. */
                        // TODO This parser implementation allows for invalid
                        //      meta-data fragments to be accepted. It would be
                        //      nice to check the format of the fragment (e.g.
                        //      by matching it against a regular expression
                        //      pattern) before we accept any data. At this point
                        //      l points to the first byte of the fragment, and
                        //      fragment_end to the last.
                        handle_part_H_parse_Message_meta(alp, message,
                            l + 1, string_start, string_end);

                        fragment_end = NULL;
                        string_start = NULL;
                        string_end = NULL;
                    }
                    break;
                case '"' :
                    /* Found the end of a string. */
                    in_string = 1;
                    string_end = l;
                    break;
                default :
                    if (!fragment_end) {
                        /* There are no more meta-data fragments. */
                        engine_message_end = l;
                        done = 1;
                    }
                    break;
            }
        }
        else {
            /* In string. We are only interested
             * in where the string ends.
             */
            if ((*l == '"')&&((l - 1) >= s)&&(*(l - 1) != '\\')) {
                in_string = 0;
                string_start = l;
            }
        }
    
        l--;
    }

    /* Target is between " at " and "." */
    tptr = engine_message_start;
    while ((tptr = strstr(tptr, " at ")) && (tptr < engine_message_end)) {
        char *tend = strchr(tptr, '.');
        if ((tend <= engine_message_end) && (tend - tptr > 5)) {
            int rc = handle_part_H_parse_Message_meta(alp, message, tptr + 1,
                                                      tptr + 4, tend - 1);
            if (rc == 1) {
                /* Remove the target data from the message */
                engine_message_end = tptr;
            }
        }
        break;
    }

    if (engine_message_end == NULL) {
        add_error(alp, 1, "Part H: Failed parsing ModSecurity message: %s", s);
        return -1;
    }

    message->engine_message = apr_pstrndup(alp->auditlog->mp, engine_message_start, (engine_message_end - engine_message_start + 1));

    /* Add this message to the audit log. */
    *(alp2_msg_t **)apr_array_push(alp->auditlog->messages) = message;

    return 1;
}

/**
 * Parse the Stopwatch trailer header.
 */
static int handle_part_H_parse_Stopwatch(alp2_t *alp, const char *s)
{
    int ovector[33];
    int i, rc;

    // TODO This header is required (a check for its appearance is made when
    //      handling the end of an H part), and is not allowed to appear
    //      more than once.

    rc = pcre_exec(alp->trailer_stopwatch_pattern, NULL, s, strlen(s), 0, 0, ovector, 30);
    if (rc < 0) {
        add_error(alp, 1, "Part H: Failed to parse Stopwatch header");
        return -1;
    }

    /* Loop through the captures. */
    for (i = 0; i < rc; i++) {
        char *capture = apr_pstrmemdup(alp->auditlog->mp, s + ovector[2 * i],
            ovector[2 * i + 1] - ovector[2 * i]);

        switch (i) {
            case 1 : /* timestamp */
                // TODO Validate
                alp->auditlog->timestamp = apr_atoi64(capture);
                break;
            case 2 : /* duration */
                // TODO Validate
                alp->auditlog->duration = apr_atoi64(capture);
                break;
            case 3 : /* ignore (group of three further time elements)*/
                break;
            case 4 : /* t1 */
                break;
            case 5 : /* t2 */
                break;
            case 6 : /* t3 */
                break;
        }
    }

    return 1;
}

/**
 * Parse the WebApp-Info trailer header.
 */
static int handle_part_H_parse_WebAppInfo(alp2_t *alp, const char *s)
{
    int ovector[33];
    int i, rc;

    // TODO This header is optional, but it is not allowed to appear more than once.

    rc = pcre_exec(alp->trailer_webappinfo_pattern, NULL, s, strlen(s), 0, 0, ovector, 30);
    if (rc < 0) {
        add_error(alp, 1, "Part H: Failed to parse WebApp-Info header");
        return -1;
    }

    /* Loop through the captures. */
    for (i = 0; i < rc; i++) {
        char *capture = apr_pstrmemdup(alp->auditlog->mp, s + ovector[2 * i],
            ovector[2 * i + 1] - ovector[2 * i]);

        switch (i) {
            case 1 : /* application ID */
                // TODO Validate
                alp->auditlog->application_id = capture;
                break;
            case 2 : /* session ID */
                // TODO Validate
                if (strcmp(capture, "-") != 0) {
                    alp->auditlog->session_id = capture;
                }
                break;
            case 3 : /* user ID */
                // TODO Validate
                if (strcmp(capture, "-") != 0) {
                    alp->auditlog->user_id = capture;
                }
                break;
        }
    }

    return 1;
}

/**
 * Handle part H events.
 */
static void handle_part_H(alp2_t *alp, int event_type)
{
    /* Part data. */
    if (event_type == ALP2_EVENT_PART_DATA) {
        char *line = alp2_pp_line_chomp(alp->pp);

        /* This part ends with an empty line. */
        if (strlen(line) == 0) {
            alp->part_data_done = 1;
            return;
        }

        /* Extract the header information. */
        {
            char *name = NULL, *value = NULL;
            int ovector[33];
            int i, rc;

            /* Header line. */

            /* Extract the fields. */
            rc = pcre_exec(alp->header_pattern, NULL, line, strlen(line), 0, 0, ovector, 30);
            if (rc < 0) {
                add_error(alp, 1, "Part H: Failed to parse header: %i", rc);
                return;
            }

            /* Loop through the captures. */
            for (i = 0; i < rc; i++) {
                char *capture = apr_pstrmemdup(alp->auditlog->mp, line + ovector[2 * i],
                    ovector[2 * i + 1] - ovector[2 * i]);

                switch(i) {
                    case 1 :
                        name = capture;
                        break;
                    case 2 :
                        value = capture;
                        break;
                }
            }

            /* Add header to the table. */
            apr_table_addn(alp->auditlog->trailer_headers, name, value);
        }

        return;
    }

    /* Part end. */
    if (event_type == ALP2_EVENT_PART_END) {
        const apr_array_header_t *tarr = apr_table_elts(alp->auditlog->trailer_headers);
        apr_table_entry_t *te = NULL;
        const char *s = NULL;
        int stopwatch = 0;
        int rc = 0;
        int i;

        if ((tarr == NULL) || (tarr->nelts == 0)) {
            return;
        }
        
        /* Here we are going to extract certain headers and
         * parse them to populate the corresponding fields in
         * the auditlog structure.
         */

        te = (apr_table_entry_t *)tarr->elts;
        for (i = 0; i < tarr->nelts; i++) {
            const char *key = te[i].key;
            const char *val = te[i].val;

            if ((key == NULL) || (val == NULL)) {
                continue;
            }

            /* Action: optional */
            else if (strcmp("Action", key) == 0) {
                rc = handle_part_H_parse_Action(alp, val);
            }
        
            /* Message: optional */
            else if (strcmp("Message", key) == 0) {
                rc = handle_part_H_parse_Message(alp, val);
            }

            /* Apache-Handler: optional */
            else if (strcmp("Apache-Handler", key) == 0) {
                rc = 0;
                // TODO Only one allowed
                alp->auditlog->handler = apr_pstrdup(alp->auditlog->mp, val);
            }

            /* Producer: optional */
            else if (strcmp("Producer", key) == 0) {
                rc = 0;
                // TODO Only one allowed
                alp->auditlog->producer = apr_pstrdup(alp->auditlog->mp, val);
            }

            /* Server: optional */
            else if (strcmp("Server", key) == 0) {
                rc = 0;
                // TODO Only one allowed
                alp->auditlog->server = apr_pstrdup(alp->auditlog->mp, val);
            }

            /* Response-Body-Transformed: optional */
            else if (strcmp("Response-Body-Transformed", key) == 0) {
                rc = 0;
                // TODO Only one allowed
                alp->auditlog->response_tfn = apr_pstrdup(alp->auditlog->mp, val);
            }

            /* Stopwatch: required */
            else if (strcmp("Stopwatch", key) == 0) {
                stopwatch = 1;
                rc = handle_part_H_parse_Stopwatch(alp, val);
            }

            /* WebApp-Info: optional */
            else if (strcmp("WebApp-Info", key) == 0) {
                rc = handle_part_H_parse_WebAppInfo(alp, val);
            }

            if (rc < 0) {
                /* No need to report anything, it's already been reported. */
            }
        }

        if (stopwatch == 0) {
            add_error(alp, 1, "Part H: Stopwatch header missing");
        }

        return;
    }
}

/**
 * Handle part F events.
 */
static void handle_part_F(alp2_t *alp, int event_type)
{
    /* Part data. */
    if (event_type == ALP2_EVENT_PART_DATA) {
        char *line = alp2_pp_line_chomp(alp->pp);

        /* This part ends with an empty line. */
        if (strlen(line) == 0) {
            alp->part_data_done = 1;
            return;
        }

        /* The first line should be the response line. */
        if (alp->part_line_counter == 1) {
            int ovector[33];
            int i, rc;

            /* Response line. */

            /* Extract the fields. */
            rc = pcre_exec(alp->response_line_pattern, NULL, line, strlen(line), 0, 0, ovector, 30);
            if (rc < 0) {
                add_error(alp, 1, "Part F: Failed to parse response line: %i", rc);
                return;
            }

            /* Loop through the captures. */
            for (i = 0; i < rc; i++) {
                char *capture = apr_pstrmemdup(alp->auditlog->mp, line + ovector[2 * i],
                    ovector[2 * i + 1] - ovector[2 * i]);

                switch(i) {
                    case 1 :
                        alp->auditlog->response_protocol = capture;
                        break;
                    case 2 :
                        alp->auditlog->response_status = atoi(capture);
                        break;
                    case 4 :
                        alp->auditlog->response_message = capture;
                        break;
                        break;
                }
            }
        }
        else {
            char *name = NULL, *value = NULL;
            int ovector[33];
            int i, rc;

            /* Response header line. */

            /* Extract the fields. */
            rc = pcre_exec(alp->header_pattern, NULL, line, strlen(line), 0, 0, ovector, 30);
            if (rc < 0) {
                add_error(alp, 1, "Part F: Failed to parse response header: %i", rc);
                return;
            }

            /* Loop through the captures. */
            for (i = 0; i < rc; i++) {
                char *capture = apr_pstrmemdup(alp->auditlog->mp, line + ovector[2 * i],
                    ovector[2 * i + 1] - ovector[2 * i]);

                switch(i) {
                    case 1 :
                        name = capture;
                        break;
                    case 2 :
                        value = capture;
                        break;
                }
            }

            /* Add header to the table. */
            apr_table_addn(alp->auditlog->response_headers, name, value);
        }

        return;
    }

    /* Part end. */
    if (event_type == ALP2_EVENT_PART_END) {
        /* If any of the response headers need
         * special handling, place the code here.
         */
        return;
    }
}

/**
 * Parse the URI. APR-Util does most of the work here.
 */
static int handle_part_B_parse_uri(alp2_t *alp)
{
    char *u = (char *)alp->auditlog->request_uri;
    apr_uri_t *uri = NULL;

    if ((  alp->auditlog->request_method == NULL)
        ||(alp->auditlog->request_uri == NULL))
    {
        return 0;
    }

    /* Since this is not a proper URI but a path, handle
     * the leading double slash.
     */
    while ((u[0] == '/') && (u[1] == '/')) {
        u++;
    }

    uri = apr_pcalloc(alp->auditlog->mp, sizeof(apr_uri_t));    

    if (strcasecmp(alp->auditlog->request_method, "CONNECT") == 0) {
        if (apr_uri_parse_hostinfo(alp->auditlog->mp, u, uri) != APR_SUCCESS) {
            add_error(alp, 0, "Info: Failed to parse request URI (hostinfo)");
            return -1;
        }
    }
    else {
        if (apr_uri_parse(alp->auditlog->mp, u, uri) != APR_SUCCESS) {
            add_error(alp, 0, "Info: Failed to parse request URI");
            return -1;
        }
    }

    alp->auditlog->parsed_uri = uri;

    return 1;
}

/**
 * Handle part B events.
 */
static void handle_part_B(alp2_t *alp, int event_type)
{
    /* Part data. */
    if (event_type == ALP2_EVENT_PART_DATA) {
        char *line = alp2_pp_line_chomp(alp->pp);

        /* This part ends with an empty line. */
        if (strlen(line) == 0) {
            alp->part_data_done = 1;
            return;
        }

        /* The first line should be the request line. */
        if (alp->part_line_counter == 1) {
            int ovector[33];
            int i, rc;

            /* Request line. */

            /* Extract the fields. */
            rc = pcre_exec(alp->request_line_pattern, NULL, line, strlen(line), 0, 0, ovector, 30);
            if (rc < 0) {
                add_error(alp, 1, "Part B: Failed to parse request line: %i", rc);
                return;
            }

            alp->auditlog->request_line_valid = 1;

            /* Loop through the captures. */
            for (i = 0; i < rc; i++) {
                char *capture = apr_pstrmemdup(alp->auditlog->mp, line + ovector[2 * i],
                    ovector[2 * i + 1] - ovector[2 * i]);

                switch(i) {
                    case 0 :
                        alp->auditlog->request_line = capture;
                        break;
                    case 1 :
                        alp->auditlog->request_method = capture;
                        break;
                    case 2 :
                        alp->auditlog->request_uri = capture;
                        if (handle_part_B_parse_uri(alp) != 1) {
                            // TODO Do we want to do anything on error?
                        }
                        break;
                    case 3 :
                        alp->auditlog->request_protocol = capture;
                        break;
                }
            }
        }
        else {
            char *name = NULL, *value = NULL;
            int ovector[33];
            int i, rc;

            /* Header line. */

            /* Extract the fields. */
            rc = pcre_exec(alp->header_pattern, NULL, line, strlen(line), 0, 0, ovector, 30);
            if (rc < 0) {
                add_error(alp, 1, "Part B: Failed to parse request header: %i", rc);
                return;
            }

            /* Loop through the captures. */
            for (i = 0; i < rc; i++) {
                char *capture = apr_pstrmemdup(alp->auditlog->mp, line + ovector[2 * i],
                    ovector[2 * i + 1] - ovector[2 * i]);

                switch(i) {
                    case 1 :
                        name = capture;
                        break;
                    case 2 :
                        value = capture;
                        break;
                }
            }

            /* ModSecurity 1.9.x adds some requests headers of
             * its own, and we don't want them.
             */
            if (strncmp(name, "mod_security-", 13) != 0) {
                /* Add header to the table. */
                apr_table_addn(alp->auditlog->request_headers, name, value);
            }
        }

        return;
    }

    /* Part end. */
    if (event_type == ALP2_EVENT_PART_END) {
        /* Determine hostname. */

        // TODO I think the right thing to do is use the port numbers
        //      only when the host itself is a numerical IP.

        /* Try the URI first. */
        if (  (alp->auditlog->parsed_uri != NULL)
            &&(alp->auditlog->parsed_uri->hostname != NULL))
        {
            if (   (alp->auditlog->parsed_uri->port != 0)
                && (alp->auditlog->parsed_uri->port != 80)
                && (alp->auditlog->parsed_uri->port != 443) )
            {
                // TODO Do not use the port number if the hostname
                //      is not numeric.
                alp->auditlog->hostname = apr_psprintf(alp->auditlog->mp, "%s:%i", 
                    alp->auditlog->parsed_uri->hostname,  alp->auditlog->parsed_uri->port);
            }
            else {
                // TODO Always use the port number if the hostname
                //      is numeric.
                alp->auditlog->hostname = alp->auditlog->parsed_uri->hostname;
            }
        }
        else {
            /* Try the Host header. */
            char *s = (char *)apr_table_get(alp->auditlog->request_headers, "Host");
            if (s != NULL) {
                // TODO If the hostname is not numeric, remove the port
                //      numbers if present.
                alp->auditlog->hostname = s;
            }
            else {
                /* Use the destination IP and port. */
                alp->auditlog->hostname = apr_psprintf(alp->auditlog->mp, "%s:%i",
                alp->auditlog->dst_ip, alp->auditlog->dst_port);
            }
        }

        return;
    }
}

/**
 * Handle part A events.
 */
static void handle_part_A(alp2_t *alp, int event_type)
{
    /* Part data. */
    if (event_type == ALP2_EVENT_PART_DATA) {
        char *line = alp2_pp_line_chomp(alp->pp);
        int ovector[33];
        int i, rc;

        /* This part can have only one line,
         * so we don't expect to be here again.
         */
        alp->part_data_done = 1;

        /* Extract the fields. */
        rc = pcre_exec(alp->part_a_pattern, NULL, line, strlen(line), 0, 0, ovector, 30);
        if (rc < 0) {
            add_error(alp, 1, "Part A: Parsing failed: %i", rc);
            return;
        }

        /* Loop through the captures. */
        for (i = 0; i < rc; i++) {
            char *capture = apr_pstrmemdup(alp->auditlog->mp, line + ovector[2 * i],
                ovector[2 * i + 1] - ovector[2 * i]);

            switch(i) {
                case 1 : /* timestamp in Apache format */
                    /* We don't need it as we use the one from the H part. */
                    break;
                case 2 : /* transaction ID */
                    alp->auditlog->id = capture;
                    break;
                case 3 : /* source address */
                    // TODO Validate
                    alp->auditlog->src_ip = capture;
                    break;
                case 4 : /* source port */
                    // TODO Validate
                    alp->auditlog->src_port = atoi(capture);
                    break;
                case 5 : /* destination address */
                    // TODO Validate
                    alp->auditlog->dst_ip = capture;
                    break;
                case 6 : /* destinatio port */
                    // TODO Validate
                    alp->auditlog->dst_port = atoi(capture);
                    break;
            }
        }

        return;
    }

    /* Part end. */
    if (event_type == ALP2_EVENT_PART_END) {
        /* Place part post-validation here. */
        return;
    }
}

/**
 * Create a new audit log data structure, allocating
 * memory from the provided memory pool.
 */
auditlog2_t *alp2_auditlog_create(apr_pool_t *mp)
{
    auditlog2_t *al;

    /* Create a new memory pool and the
     * auditlog structure in it. We will use the
     * parent pool of the parser pool, in order to
     * ensure the auditlog memory structures survive
     * the death of the parser.
     */
    al = apr_pcalloc(mp, sizeof(auditlog2_t));
    al->mp = mp;
    

    al->request_headers = apr_table_make(al->mp, 20);
    al->response_headers = apr_table_make(al->mp, 20);
    al->trailer_headers = apr_table_make(al->mp, 20);
    al->messages = apr_array_make(al->mp, 10, sizeof(const alp2_msg_t *));
    al->intercept_phase = -1;

    return al;
}

/**
 * Destroy the provided audit log entry.
 */
void alp2_auditlog_destroy(auditlog2_t *al)
{
    apr_pool_destroy(al->mp);
}

/**
 * Handle ALP2_EVENT_ENTRY_START.
 */
static void handle_entry_start(alp2_t *alp)
{
    /* Create a new data structure to hold the entry. */
    alp->auditlog = alp2_auditlog_create(alp->pp->current_entry->mp);
    alp->auditlog->pp_entry = alp->pp->current_entry;

    /* Reset entry flags. */
    alp->previous_part_id = 0;
    alp->seen_part_h = 0;    
    alp->parse_error = 0;
    alp->errors = apr_array_make(alp->auditlog->mp, 4, sizeof(const char *));
}

/**
 * Handle ALP2_EVENT_ENTRY_END.
 */
static void handle_entry_end(alp2_t *alp)
{
    if (alp->parse_error) {
        /* No need to validate the entry since we've
         * previously encountered a problem with it.
         */
    }
    else {
        /* Final entry validation. */

        /* Have we seen the H part? (We must have seen the A
         * part, otherwise the entry would have begain in
         * the first place.
         */
        if (alp->seen_part_h == 0) {
            add_error(alp, 1, "Entry does not have part H.");
        }
    }

    /* Invoke the upstream callback to handle the entry. */
    if (alp->user_callback(alp) == 0) {
        alp->done = 1;
    }

    /* Upstream owns the audit log entry now. */
    alp->auditlog = NULL;
}

/**
 * Handle ALP2_EVENT_PART_START.
 */
static void handle_part_start(alp2_t *alp)
{
    if (alp->parse_error) {
        return;
    }

    /* Reset part flags. */
    alp->part_line_counter = 0;
    alp->part_data_done = 0;

    /* Is this part allowed/expected? */
    if (alp->previous_part_id == 0) {
        if (alp->pp->current_part->id != 'A') {
            add_error(alp, 1, "Expected part A but got %c.", alp->pp->current_part->id);
            return;
        }
    }

    /* Invoke the appropriate part handler. */
    switch(alp->pp->current_part->id) {
        case 'A' :
            handle_part_A(alp, ALP2_EVENT_PART_START);
            break;
        case 'B' :
            handle_part_B(alp, ALP2_EVENT_PART_START);
            break;
        case 'F' :
            handle_part_F(alp, ALP2_EVENT_PART_START);
            break;
        case 'H' :
            alp->seen_part_h = 1;
            handle_part_H(alp, ALP2_EVENT_PART_START);
            break;
        default :
            /* Ignore unknown part. */
            break;
    }
}

/*
 * Handle ALP2_EVENT_PART_END.
 */
static void handle_part_end(alp2_t *alp)
{
    if (alp->parse_error) {
        return;
    }

    /* Invoke the appropriate part handler. */
    switch(alp->pp->current_part->id) {
        case 'A' :
            handle_part_A(alp, ALP2_EVENT_PART_END);
            break;
        case 'B' :
            handle_part_B(alp, ALP2_EVENT_PART_END);
        case 'F' :
            handle_part_F(alp, ALP2_EVENT_PART_END);
            break;
        case 'H' :
            handle_part_H(alp, ALP2_EVENT_PART_END);
            break;
        default :
            /* Ignore unknown part. */
            break;
    }

    /* Remember the last part processed. */
    alp->previous_part_id = alp->pp->current_part->id;
}

/*
 * Handle ALP2_EVENT_PART_DATA.
 */
static void handle_part_data(alp2_t *alp)
{
    if (alp->parse_error) {
        return;
    }

    alp->part_line_counter++;

    if (alp->part_data_done) {
        add_error(alp, 1, "Unexpected data for part %c.", alp->pp->current_part->id);
        return;
    }

    /* Invoke the appropriate part handler. */
    switch(alp->pp->current_part->id) {
        case 'A' :
            handle_part_A(alp, ALP2_EVENT_PART_DATA);
            break;
        case 'B' :
            handle_part_B(alp, ALP2_EVENT_PART_DATA);
            break;
        case 'F' :
            handle_part_F(alp, ALP2_EVENT_PART_DATA);
            break;
        case 'H' :
            handle_part_H(alp, ALP2_EVENT_PART_DATA);
            break;
        default :
            /* Ignore unknown part. */
            break;
    }
}

/**
 * This function handles callbacks from
 * the lower-level (part) parser.
 */
static int alp2_callback(alp2_pp_t *pp, int event_type)
{
    alp2_t *alp = (alp2_t *)pp->user_data;

    /* Choose where to dispatch the event based
     * on the event type.
     */
    switch(event_type) {
        case ALP2_EVENT_ENTRY_START :
            handle_entry_start(alp);
            break;
        case ALP2_EVENT_ENTRY_END :
            handle_entry_end(alp);
            break;
        case ALP2_EVENT_PART_START :
            handle_part_start(alp);
            break;
        case ALP2_EVENT_PART_END :
            handle_part_end(alp);
            break;
        case ALP2_EVENT_PART_DATA :
            handle_part_data(alp);
            break;
        default :
            /* Unexpected event type. */
            break;
    }

    if (alp->done) {
        /* Stop parsing. */
        return 0; 
    }
    else {
        /* Go on. */
        return 1; 
    }
}

/**
 * Initialise parser.
 */
// XXX Make callback a typedef
int alp2_create(alp2_t **_alp, apr_pool_t *mp,
                void *user_data, int (*user_callback)(alp2_t *alp))
{
    alp2_t *alp;
    apr_pool_t *new_pool;
    const char *errptr = NULL;
    int erroffset;

    /* We require a callback. */
    if (user_callback == NULL) {
        return -1;
    }

    /* We will use our own memory pool. */
    apr_pool_create(&new_pool, mp);
    alp = apr_pcalloc(mp, sizeof(alp2_t));
    *_alp = alp;
    alp->mp = new_pool;

    alp->user_data = user_data;
    alp->user_callback = user_callback;

    /* Initialise the part parser. */
    alp->pp = apr_pcalloc(mp, sizeof(alp2_pp_t));
    if (alp->pp == NULL) return -1;
    if (alp2_pp_init(alp->pp, alp, alp2_callback, mp) < 0) {
        return -2;
    }

    /* Compile the patterns we use for parsing. */

    /* part A pattern */
    if ((alp->part_a_pattern = pcre_compile(
        "^\\[(.+)\\] (\\S+) ([.:0-9a-f]+) (\\d+) ([.:0-9a-f]+) (\\d+)$",
        PCRE_DOTALL, &errptr, &erroffset, NULL)) ==  NULL)
    {
        return -3;
    }

    /* request line pattern */
    if ((alp->request_line_pattern = pcre_compile(
        // TODO Needs improving (e.g. to support simplified HTTP/0.9 requests
        "^(\\S+) (.*?) (HTTP/\\d\\.\\d)$", 
        PCRE_DOTALL, &errptr, &erroffset, NULL)) == NULL)
    {
        return -4;
    }

    /* header pattern */
    if ((alp->header_pattern = pcre_compile(
        "^([^:]+):\\s*(.+)$",
        PCRE_DOTALL, &errptr, &erroffset, NULL)) == NULL)
    {
        return -5;
    }

    /* response line pattern */
    if ((alp->response_line_pattern = pcre_compile(
        "^(HTTP/\\d\\.\\d) (\\d{3})( (.+))?$",
        PCRE_DOTALL, &errptr, &erroffset, NULL)) == NULL)
    {
        return -6;
    }

    /* Action trailer header pattern */
    if ((alp->trailer_action_pattern = pcre_compile(
        "^Intercepted \\(phase (\\d)\\)$",
        PCRE_DOTALL, &errptr, &erroffset, NULL)) == NULL)
    {
        return -7;
    }

    /* Stopwatch trailer header pattern */
    if ((alp->trailer_stopwatch_pattern = pcre_compile(
        "^(\\d+) (\\d+)( \\((-|\\d+)\\*? (-|\\d+) (-|\\d+)\\))?$",
        PCRE_DOTALL, &errptr, &erroffset, NULL)) == NULL)
    {
        return -8;
    }

    /* WebApp-Info trailer header pattern */
    if ((alp->trailer_webappinfo_pattern = pcre_compile(
        "^\"(.*)\" \"(.*)\" \"(.*)\"$",
        PCRE_DOTALL, &errptr, &erroffset, NULL)) == NULL)
    {
        return -9;
    }

    return 1;
}

/**
 * Process a piece of a stream of audit log entries.
 */
int alp2_process(alp2_t *alp, const char *data, size_t len)
{
    alp2_pp_process(alp->pp, data, len);

    if (alp->done) {
        return 0;
    }
    else {
        return 1;
    }
}

/**
 * Destroy the parser.
 */
void alp2_destroy(alp2_t *alp)
{
    apr_pool_destroy(alp->mp);
}