From: Ben Laurie Date: Wed, 31 Dec 2003 11:58:21 +0000 (+0000) Subject: Add forensic logging module. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cb3245a22bf194ce7b28781148c52095ac4acf53;p=thirdparty%2Fapache%2Fhttpd.git Add forensic logging module. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x@102130 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/CHANGES b/src/CHANGES index 461337f7241..89ba3f49654 100644 --- a/src/CHANGES +++ b/src/CHANGES @@ -1,5 +1,8 @@ Changes with Apache 1.3.30 + *) Forensic logging module added (mod_log_forensic). + [Ben Laurie] + *) SECURITY [CAN-2003-0020]: Escape arbitrary data before writing into the errorlog. [André Malo] diff --git a/src/Configuration.tmpl b/src/Configuration.tmpl index 7ec60b640d0..6134397b78d 100644 --- a/src/Configuration.tmpl +++ b/src/Configuration.tmpl @@ -431,6 +431,15 @@ AddModule modules/standard/mod_auth.o # AddModule modules/example/mod_example.o +## mod_log_forensic logs a unique id, the request line and headers +## before processing starts, and again when processing completes. If +## processing does not complete due to a bug or an attack, this can be +## detected from the logs. See support/check_forensic. +## If mod_unique_id is also included, then this will use its unique ID. +## Note that this must come before mod_unique_id. + +# AddModule modules/standard/mod_log_forensic.o + ## mod_unique_id generates unique identifiers for each hit, which are ## available in the environment variable UNIQUE_ID. It may not work on all ## systems, hence it is not included by default. diff --git a/src/modules/standard/mod_log_forensic.c b/src/modules/standard/mod_log_forensic.c new file mode 100644 index 00000000000..7a1c25defc3 --- /dev/null +++ b/src/modules/standard/mod_log_forensic.c @@ -0,0 +1,314 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR + * ITS 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +/* + * See also support/check_forensic. + * Relate the forensic log to the transfer log by including + * %{forensic-id}n in the custom log format, for example: + * CustomLog logs/custom "%h %l %u %t \"%r\" %>s %b %{forensic-id}n" + * + * Credit is due to Tina Bird , whose + * idea this module was. + * + * Ben Laurie 29/12/2003 + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include + +module MODULE_VAR_EXPORT log_forensic_module; + +typedef struct fcfg { + char *logname; + int fd; +} fcfg; + +static int next_id; + +static void *make_forensic_log_scfg(pool *p, server_rec *s) +{ + fcfg *cfg = ap_pcalloc(p, sizeof *cfg); + + cfg->logname = NULL; + cfg->fd = -1; + + return cfg; +} + +static void *merge_forensic_log_scfg(pool *p, void *parent, void *new) +{ + fcfg *cfg = ap_pcalloc(p, sizeof *cfg); + fcfg *pc = parent; + fcfg *nc = new; + + cfg->logname = ap_pstrdup(p, nc->logname ? nc->logname : pc->logname); + cfg->fd = -1; + + return cfg; +} + +static void open_log(server_rec *s, pool *p) +{ + fcfg *cfg = ap_get_module_config(s->module_config, &log_forensic_module); + + if (!cfg->logname || cfg->fd >= 0) + return; + + if (*cfg->logname == '|') { + piped_log *pl; + + pl = ap_open_piped_log(p, cfg->logname+1); + if (pl == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "couldn't spawn forensic log pipe %s", cfg->logname); + exit(1); + } + cfg->fd = ap_piped_log_write_fd(pl); + } + else { + char *fname = ap_server_root_relative(p, cfg->logname); + + if ((cfg->fd = ap_popenf_ex(p, fname, O_WRONLY | O_APPEND | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, 1)) + < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, s, + "could not open forensic log file %s.", fname); + exit(1); + } + } +} + +static void log_init(server_rec *s, pool *p) +{ + for ( ; s ; s = s->next) + open_log(s, p); +} + + +/* e is the first _invalid_ location in q + N.B. returns the terminating NUL. + */ +static char *log_escape(char *q, const char *e, const char *p) +{ + for ( ; *p ; ++p) { + assert(q < e); + if (*p < ' ' || *p >= 0x7f || *p == '|' || *p == ':' || *p == '%') { + assert(q+2 < e); + *q++ = '%'; + sprintf(q, "%02x", *(unsigned char *)p); + q += 2; + } + else + *q++ = *p; + } + assert(q < e); + *q = '\0'; + + return q; +} + +typedef struct hlog { + char *log; + char *pos; + char *end; + pool *p; + int count; +} hlog; + +static int count_string(const char *p) +{ + int n; + + for (n = 0 ; *p ; ++p, ++n) + if (*p < ' ' || *p >= 0x7f || *p == '|' || *p == ':' || *p == '%') + n += 2; + return n; +} + +static int count_headers(void *h_, const char *key, const char *value) +{ + hlog *h = h_; + + h->count += count_string(key)+count_string(value)+2; + + return 1; +} + +static int log_headers(void *h_, const char *key, const char *value) +{ + hlog *h = h_; + + /* note that we don't have to check h->pos here, coz its been done + for us by log_escape */ + *h->pos++ = '|'; + h->pos = log_escape(h->pos, h->end, key); + *h->pos++ = ':'; + h->pos = log_escape(h->pos, h->end, value); + + return 1; +} + +/* this structure only exists to allow typesafety */ +typedef struct { + const char *id; +} rcfg; + +static int log_before(request_rec *r) +{ + fcfg *cfg = ap_get_module_config(r->server->module_config, + &log_forensic_module); + static rcfg rcfg; + const char *id; + hlog h; + + if (cfg->fd < 0) + return DECLINED; + + if (!(id = ap_table_get(r->subprocess_env, "UNIQUE_ID"))) { + /* we make the assumption that we can't go through all the PIDs in + under 1 second */ + id = ap_psprintf(r->pool, "%x:%lx:%x", getpid(), time(NULL), next_id++); + } + rcfg.id = id; + ap_set_module_config(r->request_config, &log_forensic_module, &rcfg); + + h.p = r->pool; + h.count = 0; + + ap_table_do(count_headers, &h, r->headers_in, NULL); + + h.count += 1+strlen(id)+1+count_string(r->the_request)+1+1; + h.log = ap_palloc(r->pool, h.count); + h.pos = h.log; + h.end = h.log+h.count; + + *h.pos++ = '+'; + strcpy(h.pos, id); + h.pos += strlen(h.pos); + *h.pos++ = '|'; + h.pos = log_escape(h.pos, h.end, r->the_request); + + ap_table_do(log_headers, &h, r->headers_in, NULL); + + assert(h.pos < h.end); + *h.pos++ = '\n'; + + write(cfg->fd, h.log, h.count-1); + + ap_table_setn(r->notes, "forensic-id", id); + + return OK; +} + +static int log_after(request_rec *r) +{ + fcfg *cfg = ap_get_module_config(r->server->module_config, + &log_forensic_module); + char *s; + rcfg *rcfg; + + if(cfg->fd < 0) + return DECLINED; + + rcfg = ap_get_module_config(r->request_config, &log_forensic_module); + s = ap_pstrcat(r->pool, "-", rcfg->id, "\n", NULL); + write(cfg->fd, s, strlen(s)); + + return OK; +} + +static const char *set_forensic_log(cmd_parms *cmd, void *dummy, char *fn) +{ + fcfg *cfg = ap_get_module_config(cmd->server->module_config, + &log_forensic_module); + + cfg->logname = fn; + return NULL; +} + +static const command_rec forensic_log_cmds[] = +{ + { "ForensicLog", set_forensic_log, NULL, RSRC_CONF, TAKE1, + "the filename of the forensic log (default is logs/forensic_log" }, + { NULL } +}; + +module MODULE_VAR_EXPORT log_forensic_module = +{ + STANDARD_MODULE_STUFF, + log_init, /* initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + make_forensic_log_scfg, /* server config */ + merge_forensic_log_scfg, /* merge server config */ + forensic_log_cmds, /* command table */ + NULL, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + log_after, /* logger */ + NULL, /* header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + log_before /* post read-request */ +}; diff --git a/src/support/check_forensic b/src/support/check_forensic new file mode 100755 index 00000000000..d1512f34cf1 --- /dev/null +++ b/src/support/check_forensic @@ -0,0 +1,14 @@ +#!/bin/sh + +# check_forensic + +# check the forensic log for requests that did not complete +# output the request log for each one + +F=$1 + +cut -f 1 -d '|' $F > /tmp/fc-all.$$ +grep + < /tmp/fc-all.$$ | cut -c2- | sort > /tmp/fc-in.$$ +grep -- - < /tmp/fc-all.$$ | cut -c2- | sort > /tmp/fc-out.$$ +join -v 1 /tmp/fc-in.$$ /tmp/fc-out.$$ | xargs -I xx egrep "^\\+xx" $F +rm /tmp/fc-all.$$ /tmp/fc-in.$$ /tmp/fc-out.$$