aboutsummaryrefslogtreecommitdiff
path: root/www/squid31
diff options
context:
space:
mode:
authorSergey Matveychuk <sem@FreeBSD.org>2006-02-04 11:45:40 +0000
committerSergey Matveychuk <sem@FreeBSD.org>2006-02-04 11:45:40 +0000
commite8de9cc8fd08050a90a9a584aa8b7f3af7ff9897 (patch)
treec59594828d0a34c5ae4988a3ec0aeb51773b3a5a /www/squid31
parent9c95a698755da93c1b0513fe112f4996e20697c8 (diff)
downloadports-e8de9cc8fd08050a90a9a584aa8b7f3af7ff9897.tar.gz
ports-e8de9cc8fd08050a90a9a584aa8b7f3af7ff9897.zip
Notes
Diffstat (limited to 'www/squid31')
-rw-r--r--www/squid31/Makefile4
-rw-r--r--www/squid31/files/customlog-2.5.patch1540
2 files changed, 1544 insertions, 0 deletions
diff --git a/www/squid31/Makefile b/www/squid31/Makefile
index 195e0938355c..22ba826aa36e 100644
--- a/www/squid31/Makefile
+++ b/www/squid31/Makefile
@@ -123,6 +123,7 @@ OPTIONS= SQUID_LDAP_AUTH "Install LDAP authentication helpers" off \
SQUID_STRICT_HTTP "Be strictly HTTP compliant" off \
SQUID_IDENT "Enable ident (RFC 931) lookups" on \
SQUID_USERAGENT_LOG "Enable User-Agent-header logging" off \
+ SQUID_CUSTOM_LOG "Enable custom log format" off \
SQUID_ARP_ACL "Enable ACLs based on ethernet address" off \
SQUID_PF "Enable transparent proxying with PF" off \
SQUID_IPFILTER "Enable transp. proxying with IPFilter" off \
@@ -274,6 +275,9 @@ CONFIGURE_ARGS+= --disable-ident-lookups
.if defined(WITH_SQUID_USERAGENT_LOG)
CONFIGURE_ARGS+= --enable-useragent-log
.endif
+.if defined(WITH_SQUID_CUSTOM_LOG)
+EXTRA_PATCHES+= ${PATCHDIR}/customlog-2.5.patch
+.endif
.if defined(WITH_SQUID_ARP_ACL)
CONFIGURE_ARGS+= --enable-arp-acl
.endif
diff --git a/www/squid31/files/customlog-2.5.patch b/www/squid31/files/customlog-2.5.patch
new file mode 100644
index 000000000000..1ee466346d30
--- /dev/null
+++ b/www/squid31/files/customlog-2.5.patch
@@ -0,0 +1,1540 @@
+! This patch is sourced from http://devel.squid-cache.org/customlog/
+! Modified diff paths to apply cleanly
+
+Index: src/access_log.c
+diff -u src/access_log.c:1.15.6.8 src/access_log.c:1.15.6.3.2.14
+--- src/access_log.c:1.15.6.8 Tue Mar 29 18:17:46 2005
++++ src/access_log.c Thu Sep 1 12:28:46 2005
+@@ -36,9 +36,6 @@
+
+ #include "squid.h"
+
+-static void accessLogSquid(AccessLogEntry * al);
+-static void accessLogCommon(AccessLogEntry * al);
+-static Logfile *logfile = NULL;
+ #if HEADERS_LOG
+ static Logfile *headerslog = NULL;
+ #endif
+@@ -234,8 +231,768 @@
+ return username_quote(name);
+ }
+
++static char *
++log_quoted_string(const char *str)
++{
++ char *out = xmalloc(strlen(str) * 2 + 1);
++ char *p = out;
++ while (*str) {
++ int l = strcspn(str, "\"\\\r\n\t");
++ memcpy(p, str, l);
++ str += l;
++ p += l;
++ switch (*str) {
++ case '\0':
++ break;
++ case '\r':
++ *p++ = '\\';
++ *p++ = 'r';
++ str++;
++ break;
++ case '\n':
++ *p++ = '\\';
++ *p++ = 'n';
++ str++;
++ break;
++ case '\t':
++ *p++ = '\\';
++ *p++ = 't';
++ str++;
++ break;
++ default:
++ *p++ = '\\';
++ *p++ = *str;
++ str++;
++ break;
++ }
++ }
++ *p++ = '\0';
++ return out;
++}
++
++/*
++ * Bytecodes for the configureable logformat stuff
++ */
++typedef enum {
++ LFT_NONE, /* dummy */
++ LFT_STRING,
++
++ LFT_CLIENT_IP_ADDRESS,
++ LFT_CLIENT_FQDN,
++/*LFT_CLIENT_PORT, */
++
++/*LFT_SERVER_IP_ADDRESS, */
++ LFT_SERVER_IP_OR_PEER_NAME,
++/*LFT_SERVER_PORT, */
++
++ LFT_LOCAL_IP,
++ LFT_LOCAL_PORT,
++/*LFT_LOCAL_NAME, */
++
++ LFT_TIME_SECONDS_SINCE_EPOCH,
++ LFT_TIME_SUBSECOND,
++ LFT_TIME_LOCALTIME,
++ LFT_TIME_GMT,
++ LFT_TIME_TO_HANDLE_REQUEST,
++
++ LFT_REQUEST_HEADER,
++ LFT_REQUEST_HEADER_ELEM,
++ LFT_REQUEST_ALL_HEADERS,
++
++ LFT_REPLY_HEADER,
++ LFT_REPLY_HEADER_ELEM,
++ LFT_REPLY_ALL_HEADERS,
++
++ LFT_USER_NAME,
++ LFT_USER_LOGIN,
++ LFT_USER_IDENT,
++/*LFT_USER_REALM, */
++/*LFT_USER_SCHEME, */
++
++ LFT_HTTP_CODE,
++/*LFT_HTTP_STATUS, */
++
++ LFT_SQUID_STATUS,
++/*LFT_SQUID_ERROR, */
++ LFT_SQUID_HIERARCHY,
++
++ LFT_MIME_TYPE,
++
++ LFT_REQUEST_METHOD,
++ LFT_REQUEST_URI,
++/*LFT_REQUEST_QUERY, * // * this is not needed. see strip_query_terms */
++ LFT_REQUEST_VERSION,
++
++/*LFT_REQUEST_SIZE_TOTAL, */
++/*LFT_REQUEST_SIZE_LINE, */
++/*LFT_REQUEST_SIZE_HEADERS, */
++/*LFT_REQUEST_SIZE_BODY, */
++/*LFT_REQUEST_SIZE_BODY_NO_TE, */
++
++ LFT_REPLY_SIZE_TOTAL,
++/*LFT_REPLY_SIZE_LINE, */
++/*LFT_REPLY_SIZE_HEADERS, */
++/*LFT_REPLY_SIZE_BODY, */
++/*LFT_REPLY_SIZE_BODY_NO_TE, */
++
++#ifdef HAVE_EXTACL_LOG
++ LFT_EXT_LOG,
++#endif
++
++ LFT_PERCENT /* special string cases for escaped chars */
++} logformat_bcode_t;
++
++enum log_quote {
++ LOG_QUOTE_NONE = 0,
++ LOG_QUOTE_QUOTES,
++ LOG_QUOTE_BRAKETS,
++ LOG_QUOTE_URL,
++ LOG_QUOTE_RAW
++};
++struct _logformat_token {
++ logformat_bcode_t type;
++ union {
++ char *string;
++ struct {
++ char *header;
++ char *element;
++ char separator;
++ } header;
++ char *timespec;
++ } data;
++ unsigned char width;
++ unsigned char precision;
++ enum log_quote quote:3;
++ unsigned int left:1;
++ unsigned int space:1;
++ unsigned int zero:1;
++ int divisor;
++ logformat_token *next; /* todo: move from linked list to array */
++};
++
++struct logformat_token_table_entry {
++ const char *config;
++ logformat_bcode_t token_type;
++ int options;
++};
++
++struct logformat_token_table_entry logformat_token_table[] =
++{
++
++ {">a", LFT_CLIENT_IP_ADDRESS},
++/*{ ">p", LFT_CLIENT_PORT}, */
++ {">A", LFT_CLIENT_FQDN},
++
++/*{ "<a", LFT_SERVER_IP_ADDRESS }, */
++/*{ "<p", LFT_SERVER_PORT }, */
++ {"<A", LFT_SERVER_IP_OR_PEER_NAME},
++
++ {"la", LFT_LOCAL_IP},
++ {"lp", LFT_LOCAL_PORT},
++/*{ "lA", LFT_LOCAL_NAME }, */
++
++ {"ts", LFT_TIME_SECONDS_SINCE_EPOCH},
++ {"tu", LFT_TIME_SUBSECOND},
++ {"tl", LFT_TIME_LOCALTIME},
++ {"tg", LFT_TIME_GMT},
++ {"tr", LFT_TIME_TO_HANDLE_REQUEST},
++
++ {">h", LFT_REQUEST_HEADER},
++ {"<h", LFT_REPLY_HEADER},
++
++ {"un", LFT_USER_NAME},
++ {"ul", LFT_USER_LOGIN},
++/*{ "ur", LFT_USER_REALM }, */
++/*{ "us", LFT_USER_SCHEME }, */
++ {"ui", LFT_USER_IDENT},
++
++ {"Hs", LFT_HTTP_CODE},
++/*{ "Ht", LFT_HTTP_STATUS }, */
++
++ {"Ss", LFT_SQUID_STATUS},
++/*{ "Se", LFT_SQUID_ERROR }, */
++ {"Sh", LFT_SQUID_HIERARCHY},
++
++ {"mt", LFT_MIME_TYPE},
++
++ {"rm", LFT_REQUEST_METHOD},
++ {"ru", LFT_REQUEST_URI}, /* doesn't include the query-string */
++/* { "rq", LFT_REQUEST_QUERY }, * / / * the query-string, INCLUDING the leading ? */
++ {">v", LFT_REQUEST_VERSION},
++ {"rv", LFT_REQUEST_VERSION},
++
++/*{ ">st", LFT_REQUEST_SIZE_TOTAL }, */
++/*{ ">sl", LFT_REQUEST_SIZE_LINE }, * / / * the request line "GET ... " */
++/*{ ">sh", LFT_REQUEST_SIZE_HEADERS }, */
++/*{ ">sb", LFT_REQUEST_SIZE_BODY }, */
++/*{ ">sB", LFT_REQUEST_SIZE_BODY_NO_TE }, */
++
++ {"<st", LFT_REPLY_SIZE_TOTAL},
++/*{ "<sl", LFT_REPLY_SIZE_LINE }, * / / * the reply line (protocol, code, text) */
++/*{ "<sh", LFT_REPLY_SIZE_HEADERS }, */
++/*{ "<sb", LFT_REPLY_SIZE_BODY }, */
++/*{ "<sB", LFT_REPLY_SIZE_BODY_NO_TE }, */
++
++#ifdef HAVE_EXTACL_LOG
++ {"ea", LFT_EXT_LOG},
++#endif
++
++ {"%", LFT_PERCENT},
++
++ {NULL, LFT_NONE} /* this must be last */
++};
++
++static void
++accessLogCustom(AccessLogEntry * al, customlog * log)
++{
++ logformat *lf;
++ Logfile *logfile;
++ logformat_token *fmt;
++ static MemBuf mb = MemBufNULL;
++ char tmp[1024];
++ String sb = StringNull;
++
++ memBufReset(&mb);
++
++ lf = log->logFormat;
++ logfile = log->logfile;
++ for (fmt = lf->format; fmt != NULL; fmt = fmt->next) { /* for each token */
++ const char *out = NULL;
++ int quote = 0;
++ long int outint = 0;
++ int doint = 0;
++ int dofree = 0;
++ switch (fmt->type) {
++ case LFT_NONE:
++ out = "";
++ break;
++ case LFT_STRING:
++ out = fmt->data.string;
++ break;
++ case LFT_CLIENT_IP_ADDRESS:
++ out = inet_ntoa(al->cache.caddr);
++ break;
++
++ case LFT_CLIENT_FQDN:
++ out = fqdncache_gethostbyaddr(al->cache.caddr, FQDN_LOOKUP_IF_MISS);
++ if (!out)
++ out = inet_ntoa(al->cache.caddr);
++ break;
++
++ /* case LFT_CLIENT_PORT: */
++
++ /* case LFT_SERVER_IP_ADDRESS: */
++
++ case LFT_SERVER_IP_OR_PEER_NAME:
++ out = al->hier.host;
++ break;
++
++ /* case LFT_SERVER_PORT: */
++
++ case LFT_LOCAL_IP:
++ if (al->request)
++ out = inet_ntoa(al->request->my_addr);
++ break;
++
++ case LFT_LOCAL_PORT:
++ if (al->request) {
++ outint = al->request->my_port;
++ doint = 1;
++ }
++ break;
++
++ case LFT_TIME_SECONDS_SINCE_EPOCH:
++ outint = current_time.tv_sec;
++ doint = 1;
++ break;
++
++ case LFT_TIME_SUBSECOND:
++ outint = current_time.tv_usec / fmt->divisor;
++ doint = 1;
++ break;
++
++
++ case LFT_TIME_LOCALTIME:
++ case LFT_TIME_GMT:
++ {
++ const char *spec;
++ struct tm *t;
++ spec = fmt->data.timespec;
++ if (!spec)
++ spec = "%d/%b/%Y:%H:%M:%S %z";
++ if (fmt->type == LFT_TIME_LOCALTIME)
++ t = localtime(&squid_curtime);
++ else
++ t = gmtime(&squid_curtime);
++ strftime(tmp, sizeof(tmp), spec, t);
++ out = tmp;
++ }
++ break;
++
++ case LFT_TIME_TO_HANDLE_REQUEST:
++ outint = al->cache.msec;
++ doint = 1;
++ break;
++
++ case LFT_REQUEST_HEADER:
++ if (al->request)
++ sb = httpHeaderGetByName(&al->request->header, fmt->data.header.header);
++ out = strBuf(sb);
++ quote = 1;
++ break;
++
++ case LFT_REPLY_HEADER:
++ if (al->reply)
++ sb = httpHeaderGetByName(&al->reply->header, fmt->data.header.header);
++ out = strBuf(sb);
++ quote = 1;
++ break;
++
++ case LFT_REQUEST_HEADER_ELEM:
++ if (al->request)
++ sb = httpHeaderGetByNameListMember(&al->request->header, fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
++ out = strBuf(sb);
++ quote = 1;
++ break;
++
++ case LFT_REPLY_HEADER_ELEM:
++ if (al->reply)
++ sb = httpHeaderGetByNameListMember(&al->reply->header, fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
++ out = strBuf(sb);
++ quote = 1;
++ break;
++
++ case LFT_REQUEST_ALL_HEADERS:
++ out = al->headers.request;
++ quote = 1;
++ break;
++
++ case LFT_REPLY_ALL_HEADERS:
++ out = al->headers.reply;
++ quote = 1;
++ break;
++
++ case LFT_USER_NAME:
++ out = accessLogFormatName(al->cache.authuser ?
++ al->cache.authuser : al->cache.rfc931);
++ dofree = 1;
++ break;
++
++ case LFT_USER_LOGIN:
++ out = accessLogFormatName(al->cache.authuser);
++ dofree = 1;
++ break;
++
++ case LFT_USER_IDENT:
++ out = accessLogFormatName(al->cache.rfc931);
++ dofree = 1;
++ break;
++
++ /* case LFT_USER_REALM: */
++ /* case LFT_USER_SCHEME: */
++
++ case LFT_HTTP_CODE:
++ outint = al->http.code;
++ doint = 1;
++ break;
++
++ /* case LFT_HTTP_STATUS:
++ * out = statusline->text;
++ * quote = 1;
++ * break;
++ */
++
++ case LFT_SQUID_STATUS:
++ out = log_tags[al->cache.code];
++ break;
++
++ /* case LFT_SQUID_ERROR: */
++
++ case LFT_SQUID_HIERARCHY:
++ if (al->hier.ping.timedout)
++ memBufAppend(&mb, "TIMEOUT_", 8);
++ out = hier_strings[al->hier.code];
++ break;
++
++ case LFT_MIME_TYPE:
++ out = al->http.content_type;
++ break;
++
++ case LFT_REQUEST_METHOD:
++ out = al->private.method_str;
++ break;
++
++ case LFT_REQUEST_URI:
++ out = al->url;
++ break;
++
++ case LFT_REQUEST_VERSION:
++ snprintf(tmp, sizeof(tmp), "%d.%d", (int) al->http.version.major, (int) al->http.version.minor);
++ out = tmp;
++ break;
++
++ /*case LFT_REQUEST_SIZE_TOTAL: */
++ /*case LFT_REQUEST_SIZE_LINE: */
++ /*case LFT_REQUEST_SIZE_HEADERS: */
++ /*case LFT_REQUEST_SIZE_BODY: */
++ /*case LFT_REQUEST_SIZE_BODY_NO_TE: */
++
++ case LFT_REPLY_SIZE_TOTAL:
++ outint = al->cache.size;
++ doint = 1;
++ break;
++
++ /*case LFT_REPLY_SIZE_LINE: */
++ /*case LFT_REPLY_SIZE_HEADERS: */
++ /*case LFT_REPLY_SIZE_BODY: */
++ /*case LFT_REPLY_SIZE_BODY_NO_TE: */
++
++#ifdef HAVE_EXTACL_LOG
++ case LFT_EXT_LOG:
++ if (al->request)
++ out = strBuf(al->request->extacl_log);
++
++ quote = 1;
++ break;
++#endif
++
++ case LFT_PERCENT:
++ out = "%";
++ break;
++ }
++
++ if (doint) {
++ snprintf(tmp, sizeof(tmp), "%0*ld", fmt->zero ? (int) fmt->width : 0, outint);
++ out = tmp;
++ }
++ if (out && *out) {
++ if (quote || fmt->quote != LOG_QUOTE_NONE) {
++ char *newout = NULL;
++ int newfree = 0;
++ switch (fmt->quote) {
++ case LOG_QUOTE_NONE:
++ newout = rfc1738_escape_unescaped(out);
++ break;
++ case LOG_QUOTE_QUOTES:
++ newout = log_quoted_string(out);
++ newfree = 1;
++ break;
++ case LOG_QUOTE_BRAKETS:
++ newout = log_quote(out);
++ newfree = 1;
++ break;
++ case LOG_QUOTE_URL:
++ newout = rfc1738_escape(out);
++ break;
++ case LOG_QUOTE_RAW:
++ break;
++ }
++ if (newout) {
++ if (dofree)
++ safe_free(out);
++ out = newout;
++ dofree = newfree;
++ }
++ }
++ if (fmt->width) {
++ if (fmt->left)
++ memBufPrintf(&mb, "%-*s", (int) fmt->width, out);
++ else
++ memBufPrintf(&mb, "%*s", (int) fmt->width, out);
++ } else
++ memBufAppend(&mb, out, strlen(out));
++ } else {
++ memBufAppend(&mb, "-", 1);
++ }
++ if (fmt->space)
++ memBufAppend(&mb, " ", 1);
++ stringClean(&sb);
++ if (dofree)
++ safe_free(out);
++ }
++ logfilePrintf(logfile, "%s\n", mb.buf);
++}
++
++/* parses a single token. Returns the token length in characters,
++ * and fills in the lt item with the token information.
++ * def is for sure null-terminated
++ */
++static int
++accessLogGetNewLogFormatToken(logformat_token * lt, char *def, enum log_quote *quote)
++{
++ char *cur = def;
++ struct logformat_token_table_entry *lte;
++ int l;
++
++ memset(lt, 0, sizeof(*lt));
++ l = strcspn(cur, "%");
++ if (l > 0) {
++ char *cp;
++ /* it's a string for sure, until \0 or the next % */
++ cp = xmalloc(l + 1);
++ xstrncpy(cp, cur, l + 1);
++ lt->type = LFT_STRING;
++ lt->data.string = cp;
++ while (l > 0) {
++ switch(*cur) {
++ case '"':
++ if (*quote == LOG_QUOTE_NONE)
++ *quote = LOG_QUOTE_QUOTES;
++ else if (*quote == LOG_QUOTE_QUOTES)
++ *quote = LOG_QUOTE_NONE;
++ break;
++ case '[':
++ if (*quote == LOG_QUOTE_NONE)
++ *quote = LOG_QUOTE_BRAKETS;
++ break;
++ case ']':
++ if (*quote == LOG_QUOTE_BRAKETS)
++ *quote = LOG_QUOTE_NONE;
++ break;
++ }
++ cur++;
++ l--;
++ }
++ goto done;
++ }
++ if (!*cur)
++ goto done;
++ cur++;
++ switch (*cur) {
++ case '"':
++ lt->quote = LOG_QUOTE_QUOTES;
++ cur++;
++ break;
++ case '\'':
++ lt->quote = LOG_QUOTE_RAW;
++ cur++;
++ break;
++ case '[':
++ lt->quote = LOG_QUOTE_BRAKETS;
++ cur++;
++ break;
++ case '#':
++ lt->quote = LOG_QUOTE_URL;
++ cur++;
++ break;
++ default:
++ lt->quote = *quote;
++ break;
++ }
++ if (*cur == '-') {
++ lt->left = 1;
++ cur++;
++ }
++ if (*cur == '0') {
++ lt->zero = 1;
++ cur++;
++ }
++ if (isdigit(*cur))
++ lt->width = strtol(cur, &cur, 10);
++ if (*cur == '.')
++ lt->precision = strtol(cur + 1, &cur, 10);
++ if (*cur == '{') {
++ char *cp;
++ cur++;
++ l = strcspn(cur, "}");
++ cp = xmalloc(l + 1);
++ xstrncpy(cp, cur, l + 1);
++ lt->data.string = cp;
++ cur += l;
++ if (*cur == '}')
++ cur++;
++ }
++ lt->type = LFT_NONE;
++ for (lte = logformat_token_table; lte->config != NULL; lte++) {
++ if (strncmp(lte->config, cur, strlen(lte->config)) == 0) {
++ lt->type = lte->token_type;
++ cur += strlen(lte->config);
++ break;
++ }
++ }
++ if (lt->type == LFT_NONE) {
++ fatalf("Can't parse configuration token: '%s'\n",
++ def);
++ }
++ if (*cur == ' ') {
++ lt->space = 1;
++ cur++;
++ }
++ done:
++ switch (lt->type) {
++ case LFT_REQUEST_HEADER:
++ case LFT_REPLY_HEADER:
++ if (lt->data.string) {
++ char *header = lt->data.string;
++ char *cp = strchr(header, ':');
++ if (cp) {
++ *cp++ = '\0';
++ if (*cp == ',' || *cp == ';' || *cp == ':')
++ lt->data.header.separator = *cp++;
++ else
++ lt->data.header.separator = ',';
++ lt->data.header.element = cp;
++ lt->type = (lt->type == LFT_REQUEST_HEADER) ?
++ LFT_REQUEST_HEADER_ELEM :
++ LFT_REPLY_HEADER_ELEM;
++ }
++ lt->data.header.header = header;
++ } else {
++ lt->type = (lt->type == LFT_REQUEST_HEADER) ?
++ LFT_REQUEST_ALL_HEADERS :
++ LFT_REPLY_ALL_HEADERS;
++ Config.onoff.log_mime_hdrs = 1;
++ }
++ break;
++ case LFT_CLIENT_FQDN:
++ Config.onoff.log_fqdn = 1;
++ break;
++ case LFT_TIME_SUBSECOND:
++ lt->divisor = 1000;
++ if (lt->precision) {
++ int i;
++ lt->divisor = 1000000;
++ for (i = lt->precision; i > 1; i--)
++ lt->divisor /= 10;
++ if (!lt->divisor)
++ lt->divisor = 0;
++ }
++ break;
++ default:
++ break;
++ }
++ return (cur - def);
++}
++
++int
++accessLogParseLogFormat(logformat_token ** fmt, char *def)
++{
++ char *cur, *eos;
++ logformat_token *new_lt, *last_lt;
++ enum log_quote quote = LOG_QUOTE_NONE;
++
++ debug(46, 1) ("accessLogParseLogFormat: got definition '%s'\n", def);
++
++ /* very inefficent parser, but who cares, this needs to be simple */
++ /* First off, let's tokenize, we'll optimize in a second pass.
++ * A token can either be a %-prefixed sequence (usually a dynamic
++ * token but it can be an escaped sequence), or a string. */
++ cur = def;
++ eos = def + strlen(def);
++ *fmt = new_lt = last_lt = xmalloc(sizeof(logformat_token));
++ cur += accessLogGetNewLogFormatToken(new_lt, cur, &quote);
++ while (cur < eos) {
++ new_lt = xmalloc(sizeof(logformat_token));
++ last_lt->next = new_lt;
++ last_lt = new_lt;
++ cur += accessLogGetNewLogFormatToken(new_lt, cur, &quote);
++ }
++ return 1;
++}
++
++void
++accessLogDumpLogFormat(StoreEntry * entry, const char *name, logformat * definitions)
++{
++ logformat_token *t;
++ logformat *format;
++ struct logformat_token_table_entry *te;
++ debug(46, 0) ("accessLogDumpLogFormat called\n");
++
++ for (format = definitions; format; format = format->next) {
++ debug(46, 0) ("Dumping logformat definition for %s\n", format->name);
++ storeAppendPrintf(entry, "logformat %s ", format->name);
++ for (t = format->format; t; t = t->next) {
++ if (t->type == LFT_STRING)
++ storeAppendPrintf(entry, "%s", t->data.string);
++ else {
++ char argbuf[256];
++ char *arg = NULL;
++ logformat_bcode_t type = t->type;
++
++ switch (type) {
++ /* special cases */
++ case LFT_STRING:
++ break;
++ case LFT_REQUEST_HEADER_ELEM:
++ case LFT_REPLY_HEADER_ELEM:
++ if (t->data.header.separator != ',')
++ snprintf(argbuf, sizeof(argbuf), "%s:%c%s", t->data.header.header, t->data.header.separator, t->data.header.element);
++ else
++ snprintf(argbuf, sizeof(argbuf), "%s:%s", t->data.header.header, t->data.header.element);
++
++ arg = argbuf;
++ type = (type == LFT_REQUEST_HEADER_ELEM) ?
++ LFT_REQUEST_HEADER :
++ LFT_REPLY_HEADER;
++ break;
++
++ case LFT_REQUEST_ALL_HEADERS:
++ case LFT_REPLY_ALL_HEADERS:
++ type = (type == LFT_REQUEST_ALL_HEADERS) ?
++ LFT_REQUEST_HEADER :
++ LFT_REPLY_HEADER;
++ break;
++
++ default:
++ if (t->data.string)
++ arg = t->data.string;
++ break;
++ }
++ storeAppend(entry, "%", 1);
++ switch (t->quote) {
++ case LOG_QUOTE_QUOTES:
++ storeAppend(entry, "\"", 1);
++ break;
++ case LOG_QUOTE_BRAKETS:
++ storeAppend(entry, "[", 1);
++ break;
++ case LOG_QUOTE_URL:
++ storeAppend(entry, "#", 1);
++ break;
++ case LOG_QUOTE_RAW:
++ storeAppend(entry, "'", 1);
++ break;
++ case LOG_QUOTE_NONE:
++ break;
++ }
++ if (t->left)
++ storeAppend(entry, "-", 1);
++ if (t->zero)
++ storeAppend(entry, "0", 1);
++ if (t->width)
++ storeAppendPrintf(entry, "%d", (int) t->width);
++ if (t->precision)
++ storeAppendPrintf(entry, ".%d", (int) t->precision);
++ if (arg)
++ storeAppendPrintf(entry, "{%s}", arg);
++ for (te = logformat_token_table; te->config != NULL; te++) {
++ if (te->token_type == t->type) {
++ storeAppendPrintf(entry, "%s", te->config);
++ break;
++ }
++ }
++ if (t->space)
++ storeAppend(entry, " ", 1);
++ assert(te->config != NULL);
++ }
++ }
++ }
++ storeAppend(entry, "\n", 1);
++}
++
++void
++accessLogFreeLogFormat(logformat_token ** tokens)
++{
++ while (*tokens) {
++ logformat_token *token = *tokens;
++ *tokens = token->next;
++ safe_free(token->data.string);
++ xfree(token);
++ }
++}
++
+ static void
+-accessLogSquid(AccessLogEntry * al)
++accessLogSquid(AccessLogEntry * al, Logfile * logfile)
+ {
+ const char *client = NULL;
+ char *user = NULL;
+@@ -261,10 +1018,19 @@
+ al->hier.host,
+ al->http.content_type);
+ safe_free(user);
++ if (Config.onoff.log_mime_hdrs) {
++ char *ereq = log_quote(al->headers.request);
++ char *erep = log_quote(al->headers.reply);
++ logfilePrintf(logfile, " [%s] [%s]\n", ereq, erep);
++ safe_free(ereq);
++ safe_free(erep);
++ } else {
++ logfilePrintf(logfile, "\n");
++ }
+ }
+
+ static void
+-accessLogCommon(AccessLogEntry * al)
++accessLogCommon(AccessLogEntry * al, Logfile * logfile)
+ {
+ const char *client = NULL;
+ char *user1 = NULL, *user2 = NULL;
+@@ -288,11 +1054,21 @@
+ hier_strings[al->hier.code]);
+ safe_free(user1);
+ safe_free(user2);
++ if (Config.onoff.log_mime_hdrs) {
++ char *ereq = log_quote(al->headers.request);
++ char *erep = log_quote(al->headers.reply);
++ logfilePrintf(logfile, " [%s] [%s]\n", ereq, erep);
++ safe_free(ereq);
++ safe_free(erep);
++ } else {
++ logfilePrintf(logfile, "\n");
++ }
+ }
+
+ void
+-accessLogLog(AccessLogEntry * al)
++accessLogLog(AccessLogEntry * al, aclCheck_t * checklist)
+ {
++ customlog *log;
+ if (LogfileStatus != LOG_ENABLE)
+ return;
+ if (al->url == NULL)
+@@ -306,20 +1082,38 @@
+ if (al->hier.host[0] == '\0')
+ xstrncpy(al->hier.host, dash_str, SQUIDHOSTNAMELEN);
+
+- if (Config.onoff.common_log)
+- accessLogCommon(al);
+- else
+- accessLogSquid(al);
+- if (Config.onoff.log_mime_hdrs) {
+- char *ereq = log_quote(al->headers.request);
+- char *erep = log_quote(al->headers.reply);
+- logfilePrintf(logfile, " [%s] [%s]\n", ereq, erep);
+- safe_free(ereq);
+- safe_free(erep);
+- } else {
+- logfilePrintf(logfile, "\n");
++ for (log = Config.Log.accesslogs; log; log = log->next) {
++ if (checklist && log->aclList && aclMatchAclList(log->aclList, checklist) != 1)
++ continue;
++ switch (log->type) {
++ case CLF_AUTO:
++ if (Config.onoff.common_log)
++ accessLogCommon(al, log->logfile);
++ else
++ accessLogSquid(al, log->logfile);
++ break;
++ case CLF_SQUID:
++ accessLogSquid(al, log->logfile);
++ break;
++ case CLF_COMMON:
++ accessLogCommon(al, log->logfile);
++ break;
++ case CLF_CUSTOM:
++ accessLogCustom(al, log);
++ break;
++ case CLF_NONE:
++ goto last;
++ default:
++ fatalf("Unknown log format %d\n", log->type);
++ break;
++ }
++ logfileFlush(log->logfile);
++ if (!checklist)
++ break;
+ }
+- logfileFlush(logfile);
++ last:
++ (void)0; /* NULL statement for label */
++
+ #if MULTICAST_MISS_STREAM
+ if (al->cache.code != LOG_TCP_MISS)
+ (void) 0;
+@@ -346,12 +1140,15 @@
+ void
+ accessLogRotate(void)
+ {
++ customlog *log;
+ #if FORW_VIA_DB
+ fvdbClear();
+ #endif
+- if (NULL == logfile)
+- return;
+- logfileRotate(logfile);
++ for (log = Config.Log.accesslogs; log; log = log->next) {
++ if (log->logfile) {
++ logfileRotate(log->logfile);
++ }
++ }
+ #if HEADERS_LOG
+ logfileRotate(headerslog);
+ #endif
+@@ -360,10 +1157,13 @@
+ void
+ accessLogClose(void)
+ {
+- if (NULL == logfile)
+- return;
+- logfileClose(logfile);
+- logfile = NULL;
++ customlog *log;
++ for (log = Config.Log.accesslogs; log; log = log->next) {
++ if (log->logfile) {
++ logfileClose(log->logfile);
++ log->logfile = NULL;
++ }
++ }
+ #if HEADERS_LOG
+ logfileClose(headerslog);
+ headerslog = NULL;
+@@ -383,11 +1183,14 @@
+ void
+ accessLogInit(void)
+ {
++ customlog *log;
+ assert(sizeof(log_tags) == (LOG_TYPE_MAX + 1) * sizeof(char *));
+- if (strcasecmp(Config.Log.access, "none") == 0)
+- return;
+- logfile = logfileOpen(Config.Log.access, MAX_URL << 1, 1);
+- LogfileStatus = LOG_ENABLE;
++ for (log = Config.Log.accesslogs; log; log = log->next) {
++ if (log->type == CLF_NONE)
++ continue;
++ log->logfile = logfileOpen(log->filename, MAX_URL << 1, 1);
++ LogfileStatus = LOG_ENABLE;
++ }
+ #if HEADERS_LOG
+ headerslog = logfileOpen("/usr/local/squid/logs/headers.log", MAX_URL << 1, 0);
+ assert(NULL != headerslog);
+Index: src/cache_cf.c
+diff -u src/cache_cf.c:1.38.6.24 src/cache_cf.c:1.38.6.11.4.9
+--- src/cache_cf.c:1.38.6.24 Fri May 6 19:15:36 2005
++++ src/cache_cf.c Thu May 26 21:34:13 2005
+@@ -60,6 +60,14 @@
+ static void dump_cachedir_option_readonly(StoreEntry * e, const char *option, SwapDir * sd);
+ static void parse_cachedir_option_maxsize(SwapDir * sd, const char *option, const char *value, int reconfiguring);
+ static void dump_cachedir_option_maxsize(StoreEntry * e, const char *option, SwapDir * sd);
++static void parse_logformat(logformat ** logformat_definitions);
++static void parse_access_log(customlog ** customlog_definitions);
++static void dump_logformat(StoreEntry * entry, const char *name, logformat * definitions);
++static void dump_access_log(StoreEntry * entry, const char *name, customlog * definitions);
++static void free_logformat(logformat ** definitions);
++static void free_access_log(customlog ** definitions);
++
++
+ static struct cache_dir_option common_cachedir_options[] =
+ {
+ {"read-only", parse_cachedir_option_readonly, dump_cachedir_option_readonly},
+@@ -2631,3 +2639,144 @@
+ return t;
+ }
+ }
++
++static void
++parse_logformat(logformat ** logformat_definitions)
++{
++ logformat *nlf;
++ char *name, *def;
++
++ if ((name = strtok(NULL, w_space)) == NULL)
++ self_destruct();
++ if ((def = strtok(NULL, "\r\n")) == NULL)
++ self_destruct();
++
++ debug(3, 1) ("Logformat for '%s' is '%s'\n", name, def);
++
++ nlf = xcalloc(1, sizeof(logformat));
++ nlf->name = xstrdup(name);
++ if (!accessLogParseLogFormat(&nlf->format, def))
++ self_destruct();
++ nlf->next = *logformat_definitions;
++ *logformat_definitions = nlf;
++}
++
++static void
++parse_access_log(customlog ** logs)
++{
++ const char *filename, *logdef_name;
++ customlog *cl;
++ logformat *lf;
++
++ cl = xcalloc(1, sizeof(*cl));
++
++ if ((filename = strtok(NULL, w_space)) == NULL)
++ self_destruct();
++
++ if (strcmp(filename, "none") == 0) {
++ cl->type = CLF_NONE;
++ goto done;
++ }
++ if ((logdef_name = strtok(NULL, w_space)) == NULL)
++ logdef_name = "auto";
++
++ debug(3, 9) ("Log definition name '%s' file '%s'\n", logdef_name, filename);
++
++ cl->filename = xstrdup(filename);
++
++ /* look for the definition pointer corresponding to this name */
++ lf = Config.Log.logformats;
++ while (lf != NULL) {
++ debug(3, 9) ("Comparing against '%s'\n", lf->name);
++ if (strcmp(lf->name, logdef_name) == 0)
++ break;
++ lf = lf->next;
++ }
++ if (lf != NULL) {
++ cl->type = CLF_CUSTOM;
++ cl->logFormat = lf;
++ } else if (strcmp(logdef_name, "auto") == 0) {
++ cl->type = CLF_AUTO;
++ } else if (strcmp(logdef_name, "squid") == 0) {
++ cl->type = CLF_SQUID;
++ } else if (strcmp(logdef_name, "common") == 0) {
++ cl->type = CLF_COMMON;
++ } else {
++ debug(3, 0) ("Log format '%s' is not defined\n", logdef_name);
++ self_destruct();
++ }
++
++ done:
++ aclParseAclList(&cl->aclList);
++
++ while (*logs)
++ logs = &(*logs)->next;
++ *logs = cl;
++}
++
++static void
++dump_logformat(StoreEntry * entry, const char *name, logformat * definitions)
++{
++ accessLogDumpLogFormat(entry, name, definitions);
++}
++
++static void
++dump_access_log(StoreEntry * entry, const char *name, customlog * logs)
++{
++ customlog *log;
++ for (log = logs; log; log = log->next) {
++ storeAppendPrintf(entry, "%s ", name);
++ switch (log->type) {
++ case CLF_CUSTOM:
++ storeAppendPrintf(entry, "%s %s", log->filename, log->logFormat->name);
++ break;
++ case CLF_NONE:
++ storeAppendPrintf(entry, "none");
++ break;
++ case CLF_SQUID:
++ storeAppendPrintf(entry, "%s squid", log->filename);
++ break;
++ case CLF_COMMON:
++ storeAppendPrintf(entry, "%s squid", log->filename);
++ break;
++ case CLF_AUTO:
++ if (log->aclList)
++ storeAppendPrintf(entry, "%s auto", log->filename);
++ else
++ storeAppendPrintf(entry, "%s", log->filename);
++ break;
++ case CLF_UNKNOWN:
++ break;
++ }
++ if (log->aclList)
++ dump_acl_list(entry, log->aclList);
++ storeAppendPrintf(entry, "\n");
++ }
++}
++
++static void
++free_logformat(logformat ** definitions)
++{
++ while (*definitions) {
++ logformat *format = *definitions;
++ *definitions = format->next;
++ accessLogFreeLogFormat(&format->format);
++ xfree(format);
++ }
++}
++
++static void
++free_access_log(customlog ** definitions)
++{
++ while (*definitions) {
++ customlog *log = *definitions;
++ *definitions = log->next;
++
++ log->logFormat = NULL;
++ log->type = CLF_UNKNOWN;
++ if (log->aclList)
++ aclDestroyAclList(&log->aclList);
++ safe_free(log->filename);
++ xfree(log);
++ }
++}
+Index: src/cf.data.pre
+diff -u src/cf.data.pre:1.49.2.77 src/cf.data.pre:1.49.2.40.2.17
+--- src/cf.data.pre:1.49.2.77 Tue May 10 19:17:53 2005
++++ src/cf.data.pre Thu Sep 1 12:28:46 2005
+@@ -833,16 +833,97 @@
+ (hard coded at 1 MB).
+ DOC_END
+
+-
+-NAME: cache_access_log
+-TYPE: string
+-DEFAULT: @DEFAULT_ACCESS_LOG@
+-LOC: Config.Log.access
++NAME: logformat
++TYPE: logformat
++LOC: Config.Log.logformats
++DEFAULT: none
+ DOC_START
+- Logs the client request activity. Contains an entry for
+- every HTTP and ICP queries received. To disable, enter "none".
+-DOC_END
++ Usage:
++
++ logformat <name> <format specification>
++
++ Defines an access log format.
++
++ The <format specification> is a string with embedded % format codes
++
++ % format codes all follow the same basic structure where all but
++ the formatcode is optional. Output strings are automatically escaped
++ as required according to their context and the output format
++ modifiers are usually not needed, but can be specified if an explicit
++ output format is desired.
++
++ % ["|[|'|#] [-] [[0]width] [{argument}] formatcode
++
++ " output in quoted string format
++ [ output in squid text log format as used by log_mime_hdrs
++ # output in URL quoted format
++ ' output as-is
++
++ - left aligned
++ width field width. If starting with 0 then the
++ output is zero padded
++ {arg} argument such as header name etc
++
++ Format codes:
++
++ >a Client source IP address
++ >A Client FQDN
++ <A Server IP address or peer name
++ la Local IP address (http_port)
++ lp Local port number (http_port)
++ ts Seconds since epoch
++ tu subsecond time (milliseconds)
++ tl Local time. Optional strftime format argument
++ default %d/%b/%Y:%H:%M:%S %z
++ tg GMT time. Optional strftime format argument
++ default %d/%b/%Y:%H:%M:%S %z
++ tr Response time (milliseconds)
++ >h Request header. Optional header name argument
++ on the format header[:[separator]element]
++ <h Reply header. Optional header name argument
++ as for >h
++ un User name
++ ul User login
++ ui User ident
++ Hs HTTP status code
++ Ss Squid request status (TCP_MISS etc)
++ Sh Squid hierarchy status (DEFAULT_PARENT etc)
++ mt MIME content type
++ rm Request method (GET/POST etc)
++ ru Request URL
++ rv Request protocol version
++ ea Log string returned by external acl
++ <st Reply size including HTTP headers
++ % a literal % character
++
++logformat squid %ts.%03tu %6tr %>a %Ss/%03Hs %<st %rm %ru %un %Sh/%<A %mt
++logformat squidmime %ts.%03tu %6tr %>a %Ss/%03Hs %<st %rm %ru %un %Sh/%<A %mt [%>h] [%<h]
++logformat common %>a %ui %un [%tl] "%rm %ru HTTP/%rv" %Hs %<st %Ss:%Sh
++logformat combined %>a %ui %un [%tl] "%rm %ru HTTP/%rv" %Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
++DOC_END
++
++NAME: access_log cache_access_log
++TYPE: access_log
++LOC: Config.Log.accesslogs
++DEFAULT: none
++DOC_START
++ These files log client request activities. Has a line every HTTP or
++ ICP request. The format is:
++ access_log <filepath> [<logformat name> [acl acl ...]]
++
++ Will log to the specified file using the specified format (which
++ must be defined in a logformat directive) those entries which match
++ ALL the acl's specified (which must be defined in acl clauses).
++ If no acl is specified, all requests will be logged to this file.
++
++ To disable logging of a request use the filepath "none", in which case
++ a logformat name should not be specified.
+
++ To log the request via syslog specify a filepath of "syslog"
++NOCOMMENT_START
++access_log @DEFAULT_ACCESS_LOG@ squid
++NOCOMMENT_END
++DOC_END
+
+ NAME: cache_log
+ TYPE: string
+@@ -2429,6 +2510,17 @@
+ no limit imposed.
+ DOC_END
+
++NAME: log_access
++TYPE: acl_access
++LOC: Config.accessList.log
++DEFAULT: none
++COMMENT: allow|deny acl acl...
++DOC_START
++ This options allows you to control which requests gets logged
++ to access.log (see cache_access_log directive). Requests denied
++ for logging will also not be accounted for in performance counters.
++DOC_END
++
+ COMMENT_START
+ ADMINISTRATIVE PARAMETERS
+ -----------------------------------------------------------------------------
+Index: src/client_side.c
+diff -u src/client_side.c:1.47.2.61 src/client_side.c:1.47.2.31.2.10
+--- src/client_side.c:1.47.2.61 Wed Apr 20 19:14:36 2005
++++ src/client_side.c Thu May 26 21:34:14 2005
+@@ -850,14 +850,18 @@
+ http->al.cache.code = http->log_type;
+ http->al.cache.msec = tvSubMsec(http->start, current_time);
+ if (request) {
+- Packer p;
+- MemBuf mb;
+- memBufDefInit(&mb);
+- packerToMemInit(&p, &mb);
+- httpHeaderPackInto(&request->header, &p);
++ if (Config.onoff.log_mime_hdrs) {
++ Packer p;
++ MemBuf mb;
++ memBufDefInit(&mb);
++ packerToMemInit(&p, &mb);
++ httpHeaderPackInto(&request->header, &p);
++ http->al.headers.request = xstrdup(mb.buf);
++ packerClean(&p);
++ memBufClean(&mb);
++ }
+ http->al.http.method = request->method;
+ http->al.http.version = request->http_ver;
+- http->al.headers.request = xstrdup(mb.buf);
+ http->al.hier = request->hier;
+ if (request->auth_user_request) {
+ if (authenticateUserRequestUsername(request->auth_user_request))
+@@ -867,12 +871,15 @@
+ }
+ if (conn->rfc931[0])
+ http->al.cache.rfc931 = conn->rfc931;
+- packerClean(&p);
+- memBufClean(&mb);
+ }
+- accessLogLog(&http->al);
+- clientUpdateCounters(http);
+- clientdbUpdate(conn->peer.sin_addr, http->log_type, PROTO_HTTP, http->out.size);
++ http->al.request = request;
++ if (!http->acl_checklist)
++ http->acl_checklist = clientAclChecklistCreate(Config.accessList.http, http);
++ if (!Config.accessList.log || aclCheckFast(Config.accessList.log, http->acl_checklist)) {
++ accessLogLog(&http->al, http->acl_checklist);
++ clientUpdateCounters(http);
++ clientdbUpdate(conn->peer.sin_addr, http->log_type, PROTO_HTTP, http->out.size);
++ }
+ }
+ if (http->acl_checklist)
+ aclChecklistFree(http->acl_checklist);
+@@ -883,6 +890,11 @@
+ safe_free(http->al.headers.request);
+ safe_free(http->al.headers.reply);
+ safe_free(http->al.cache.authuser);
++ if (http->al.reply) {
++ httpReplyDestroy(http->al.reply);
++ http->al.reply = NULL;
++ }
++ http->al.request = NULL;
+ safe_free(http->redirect.location);
+ stringClean(&http->range_iter.boundary);
+ if ((e = http->entry)) {
+@@ -1981,6 +1993,7 @@
+ }
+ if (http->out.offset == 0) {
+ rep = clientBuildReply(http, buf, size);
++ http->al.reply = rep;
+ if (rep) {
+ aclCheck_t *ch;
+ int rv;
+@@ -2003,7 +2016,6 @@
+ http->entry = clientCreateStoreEntry(http, http->request->method,
+ null_request_flags);
+ errorAppendEntry(http->entry, err);
+- httpReplyDestroy(rep);
+ memFree(buf, MEM_CLIENT_SOCK_BUF);
+ return;
+ }
+@@ -2038,7 +2050,6 @@
+ http->entry = clientCreateStoreEntry(http, http->request->method,
+ null_request_flags);
+ errorAppendEntry(http->entry, err);
+- httpReplyDestroy(rep);
+ memFree(buf, MEM_CLIENT_SOCK_BUF);
+ return;
+ }
+@@ -2086,7 +2097,6 @@
+ #if HEADERS_LOG
+ headersLog(0, 0, http->request->method, rep);
+ #endif
+- httpReplyDestroy(rep);
+ rep = NULL;
+ } else {
+ memBufDefInit(&mb);
+Index: src/icp_v2.c
+diff -u src/icp_v2.c:1.5 src/icp_v2.c:1.5.60.1
+--- src/icp_v2.c:1.5 Fri May 4 06:39:12 2001
++++ src/icp_v2.c Sat Jun 21 05:45:26 2003
+@@ -63,7 +63,7 @@
+ al.cache.size = len;
+ al.cache.code = logcode;
+ al.cache.msec = delay;
+- accessLogLog(&al);
++ accessLogLog(&al, NULL);
+ }
+
+ void
+Index: src/logfile.c
+diff -u src/logfile.c:1.5.38.3 src/logfile.c:1.5.38.3.4.1
+--- src/logfile.c:1.5.38.3 Mon Jan 20 19:15:11 2003
++++ src/logfile.c Wed Mar 2 12:50:03 2005
+@@ -39,33 +39,38 @@
+ Logfile *
+ logfileOpen(const char *path, size_t bufsz, int fatal_flag)
+ {
+- int fd;
+- Logfile *lf;
+- fd = file_open(path, O_WRONLY | O_CREAT | O_TEXT);
+- if (DISK_ERROR == fd) {
+- if (ENOENT == errno && fatal_flag) {
+- fatalf("Cannot open '%s' because\n"
+- "\tthe parent directory does not exist.\n"
+- "\tPlease create the directory.\n", path);
+- } else if (EACCES == errno && fatal_flag) {
+- fatalf("Cannot open '%s' for writing.\n"
+- "\tThe parent directory must be writeable by the\n"
+- "\tuser '%s', which is the cache_effective_user\n"
+- "\tset in squid.conf.", path, Config.effectiveUser);
+- } else {
+- debug(50, 1) ("logfileOpen: %s: %s\n", path, xstrerror());
+- return NULL;
++ Logfile *lf = xcalloc(1, sizeof(*lf));
++ xstrncpy(lf->path, path, MAXPATHLEN);
++ if (strcmp(path, "syslog") == 0) {
++ lf->flags.syslog = 1;
++ lf->syslog_priority = LOG_INFO;
++ lf->fd = -1;
++ } else {
++ int fd = file_open(path, O_WRONLY | O_CREAT | O_TEXT);
++ if (DISK_ERROR == fd) {
++ if (ENOENT == errno && fatal_flag) {
++ fatalf("Cannot open '%s' because\n"
++ "\tthe parent directory does not exist.\n"
++ "\tPlease create the directory.\n", path);
++ } else if (EACCES == errno && fatal_flag) {
++ fatalf("Cannot open '%s' for writing.\n"
++ "\tThe parent directory must be writeable by the\n"
++ "\tuser '%s', which is the cache_effective_user\n"
++ "\tset in squid.conf.", path, Config.effectiveUser);
++ } else {
++ debug(50, 1) ("logfileOpen: %s: %s\n", path, xstrerror());
++ safe_free(lf);
++ return NULL;
++ }
++ }
++ lf->fd = fd;
++ if (bufsz > 0) {
++ lf->buf = xmalloc(bufsz);
++ lf->bufsz = bufsz;
+ }
+ }
+- lf = xcalloc(1, sizeof(*lf));
+- lf->fd = fd;
+ if (fatal_flag)
+ lf->flags.fatal = 1;
+- xstrncpy(lf->path, path, MAXPATHLEN);
+- if (bufsz > 0) {
+- lf->buf = xmalloc(bufsz);
+- lf->bufsz = bufsz;
+- }
+ return lf;
+ }
+
+@@ -73,7 +78,8 @@
+ logfileClose(Logfile * lf)
+ {
+ logfileFlush(lf);
+- file_close(lf->fd);
++ if (lf->fd >= 0)
++ file_close(lf->fd);
+ if (lf->buf)
+ xfree(lf->buf);
+ xfree(lf);
+@@ -89,6 +95,8 @@
+ char from[MAXPATHLEN];
+ char to[MAXPATHLEN];
+ assert(lf->path);
++ if (lf->flags.syslog)
++ return;
+ #ifdef S_ISREG
+ if (stat(lf->path, &sb) == 0)
+ if (S_ISREG(sb.st_mode) == 0)
+@@ -120,6 +128,10 @@
+ void
+ logfileWrite(Logfile * lf, void *buf, size_t len)
+ {
++ if (lf->flags.syslog) {
++ syslog(lf->syslog_priority, "%s", (char *)buf);
++ return;
++ }
+ if (0 == lf->bufsz) {
+ /* buffering disabled */
+ logfileWriteWrapper(lf, buf, len);
+Index: src/protos.h
+diff -u src/protos.h:1.41.6.30 src/protos.h:1.41.6.14.2.9
+--- src/protos.h:1.41.6.30 Wed May 18 19:14:37 2005
++++ src/protos.h Thu May 26 21:34:15 2005
+@@ -34,11 +34,14 @@
+ #ifndef SQUID_PROTOS_H
+ #define SQUID_PROTOS_H
+
+-extern void accessLogLog(AccessLogEntry *);
++extern void accessLogLog(AccessLogEntry *, aclCheck_t * checklist);
+ extern void accessLogRotate(void);
+ extern void accessLogClose(void);
+ extern void accessLogInit(void);
+ extern const char *accessLogTime(time_t);
++extern int accessLogParseLogFormat(logformat_token ** fmt, char *def);
++extern void accessLogDumpLogFormat(StoreEntry * entry, const char *name, logformat * definitions);
++extern void accessLogFreeLogFormat(logformat_token ** fmt);
+ extern void hierarchyNote(HierarchyLogEntry *, hier_code, const char *);
+ #if FORW_VIA_DB
+ extern void fvdbCountVia(const char *key);
+Index: src/structs.h
+diff -u src/structs.h:1.48.2.39 src/structs.h:1.48.2.11.2.12
+--- src/structs.h:1.48.2.39 Wed May 4 19:18:43 2005
++++ src/structs.h Thu May 26 21:34:16 2005
+@@ -465,7 +465,6 @@
+ char *as_whois_server;
+ struct {
+ char *log;
+- char *access;
+ char *store;
+ char *swap;
+ #if USE_USERAGENT_LOG
+@@ -477,6 +476,8 @@
+ #if WIP_FWD_LOG
+ char *forward;
+ #endif
++ logformat *logformats;
++ customlog *accesslogs;
+ int rotateNumber;
+ } Log;
+ char *adminEmail;
+@@ -619,6 +620,7 @@
+ acl_access *AlwaysDirect;
+ acl_access *ASlists;
+ acl_access *noCache;
++ acl_access *log;
+ #if SQUID_SNMP
+ acl_access *snmp;
+ #endif
+@@ -1057,6 +1059,8 @@
+ const char *method_str;
+ } private;
+ HierarchyLogEntry hier;
++ HttpReply *reply;
++ request_t *request;
+ };
+
+ struct _clientHttpRequest {
+@@ -2200,8 +2204,32 @@
+ size_t bufsz;
+ ssize_t offset;
+ struct {
+- unsigned int fatal:1;
++ unsigned int fatal;
++ unsigned int syslog;
+ } flags;
++ int syslog_priority;
++};
++
++struct _logformat {
++ char *name;
++ logformat_token *format;
++ logformat *next;
++};
++
++struct _customlog {
++ char *filename;
++ acl_list *aclList;
++ logformat *logFormat;
++ Logfile *logfile;
++ customlog *next;
++ enum {
++ CLF_UNKNOWN,
++ CLF_AUTO,
++ CLF_CUSTOM,
++ CLF_SQUID,
++ CLF_COMMON,
++ CLF_NONE
++ } type;
+ };
+
+ struct cache_dir_option {
+Index: src/typedefs.h
+diff -u src/typedefs.h:1.25.6.8 src/typedefs.h:1.25.6.2.2.6
+--- src/typedefs.h:1.25.6.8 Sat Mar 26 18:16:17 2005
++++ src/typedefs.h Thu May 26 21:34:16 2005
+@@ -209,6 +209,9 @@
+ typedef struct _storerepl_entry storerepl_entry_t;
+ typedef struct _diskd_queue diskd_queue;
+ typedef struct _Logfile Logfile;
++typedef struct _logformat_token logformat_token;
++typedef struct _logformat logformat;
++typedef struct _customlog customlog;
+ typedef struct _RemovalPolicy RemovalPolicy;
+ typedef struct _RemovalPolicyWalker RemovalPolicyWalker;
+ typedef struct _RemovalPurgeWalker RemovalPurgeWalker;