/* * 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 #include #include #include #include #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); }