#include "base.h"
#include "log.h"
#include "buffer.h"
#include "plugin.h"
#include "inet_ntop_cache.h"
#include "sys-socket.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
#ifdef HAVE_SYSLOG_H
# include <syslog.h>
#endif
typedef struct {
char key;
enum {
FORMAT_UNSET,
FORMAT_UNSUPPORTED,
FORMAT_PERCENT,
FORMAT_REMOTE_HOST,
FORMAT_REMOTE_IDENT,
FORMAT_REMOTE_USER,
FORMAT_TIMESTAMP,
FORMAT_REQUEST_LINE,
FORMAT_STATUS,
FORMAT_BYTES_OUT_NO_HEADER,
FORMAT_HEADER,
FORMAT_REMOTE_ADDR,
FORMAT_LOCAL_ADDR,
FORMAT_COOKIE,
FORMAT_TIME_USED_MS,
FORMAT_ENV,
FORMAT_FILENAME,
FORMAT_REQUEST_PROTOCOL,
FORMAT_REQUEST_METHOD,
FORMAT_SERVER_PORT,
FORMAT_QUERY_STRING,
FORMAT_TIME_USED,
FORMAT_URL,
FORMAT_SERVER_NAME,
FORMAT_HTTP_HOST,
FORMAT_CONNECTION_STATUS,
FORMAT_BYTES_IN,
FORMAT_BYTES_OUT,
FORMAT_RESPONSE_HEADER
} type;
} format_mapping;
/**
*
*
* "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
*
*/
static const format_mapping fmap[] =
{
{ '%', FORMAT_PERCENT },
{ 'h', FORMAT_REMOTE_HOST },
{ 'l', FORMAT_REMOTE_IDENT },
{ 'u', FORMAT_REMOTE_USER },
{ 't', FORMAT_TIMESTAMP },
{ 'r', FORMAT_REQUEST_LINE },
{ 's', FORMAT_STATUS },
{ 'b', FORMAT_BYTES_OUT_NO_HEADER },
{ 'i', FORMAT_HEADER },
{ 'a', FORMAT_REMOTE_ADDR },
{ 'A', FORMAT_LOCAL_ADDR },
{ 'B', FORMAT_BYTES_OUT_NO_HEADER },
{ 'C', FORMAT_COOKIE },
{ 'D', FORMAT_TIME_USED_MS },
{ 'e', FORMAT_ENV },
{ 'f', FORMAT_FILENAME },
{ 'H', FORMAT_REQUEST_PROTOCOL },
{ 'm', FORMAT_REQUEST_METHOD },
{ 'n', FORMAT_UNSUPPORTED }, /* we have no notes */
{ 'p', FORMAT_SERVER_PORT },
{ 'P', FORMAT_UNSUPPORTED }, /* we are only one process */
{ 'q', FORMAT_QUERY_STRING },
{ 'T', FORMAT_TIME_USED },
{ 'U', FORMAT_URL }, /* w/o querystring */
{ 'v', FORMAT_SERVER_NAME },
{ 'V', FORMAT_HTTP_HOST },
{ 'X', FORMAT_CONNECTION_STATUS },
{ 'I', FORMAT_BYTES_IN },
{ 'O', FORMAT_BYTES_OUT },
{ 'o', FORMAT_RESPONSE_HEADER },
{ '\0', FORMAT_UNSET }
};
typedef struct {
enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type;
buffer *string;
int field;
} format_field;
typedef struct {
format_field **ptr;
size_t used;
size_t size;
} format_fields;
typedef struct {
buffer *access_logfile;
int log_access_fd;
buffer *access_logbuffer; /* each logfile has a separate buffer */
unsigned short use_syslog; /* syslog has global buffer */
unsigned short syslog_level;
buffer *format;
time_t last_generated_accesslog_ts;
time_t *last_generated_accesslog_ts_ptr;
buffer *ts_accesslog_str;
buffer *ts_accesslog_fmt_str;
unsigned short append_tz_offset;
format_fields *parsed_format;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config **config_storage;
plugin_config conf;
buffer *syslog_logbuffer; /* syslog has global buffer. no caching, always written directly */
} plugin_data;
INIT_FUNC(mod_accesslog_init) {
plugin_data *p;
p = calloc(1, sizeof(*p));
p->syslog_logbuffer = buffer_init();
return p;
}
static void accesslog_append_escaped(buffer *dest, buffer *str) {
char *ptr, *start, *end;
/* replaces non-printable chars with \xHH where HH is the hex representation of the byte */
/* exceptions: " => \", \ => \\, whitespace chars => \n \t etc. */
if (str->used == 0) return;
buffer_prepare_append(dest, str->used - 1);
for (ptr = start = str->ptr, end = str->ptr + str->used - 1; ptr < end; ptr++) {
char const c = *ptr;
if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
/* nothing to change, add later as one block */
} else {
/* copy previous part */
if (start < ptr) {
buffer_append_string_len(dest, start, ptr - start);
}
start = ptr + 1;
switch (c) {
case '"':
BUFFER_APPEND_STRING_CONST(dest, "\\\"");
break;
case '\\':
BUFFER_APPEND_STRING_CONST(dest, "\\\\");
break;
case '\b':
BUFFER_APPEND_STRING_CONST(dest, "\\b");
break;
case '\n':
BUFFER_APPEND_STRING_CONST(dest, "\\n");
break;
case '\r':
BUFFER_APPEND_STRING_CONST(dest, "\\r");
break;
case '\t':
BUFFER_APPEND_STRING_CONST(dest, "\\t");
break;
case '\v':
BUFFER_APPEND_STRING_CONST(dest, "\\v");
break;
default: {
/* non printable char => \xHH */
char hh[5] = {'\\','x',0,0,0};
char h = c / 16;
hh[2] = (h > 9) ? (h - 10 + 'A') : (h + '0');
h = c % 16;
hh[3] = (h > 9) ? (h - 10 + 'A') : (h + '0');
buffer_append_string_len(dest, &hh[0], 4);
}
break;
}
}
}
if (start < end) {
buffer_append_string_len(dest, start, end - start);
}
}
static int accesslog_parse_format(server *srv, format_fields *fields, buffer *format) {
size_t i, j, k = 0, start = 0;
if (format->used == 0) return -1;
for (i = 0; i < format->used - 1; i++) {
switch(format->ptr[i]) {
case '%':
if (i > 0 && start != i) {
/* copy the string before this % */
if (fields->size == 0) {
fields->size = 16;
fields->used = 0;
fields->ptr = malloc(fields->size * sizeof(format_field * ));
} else if (fields->used == fields->size) {
fields->size += 16;
fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
}
fields->ptr[fields->used] = malloc(sizeof(format_field));
fields->ptr[fields->used]->type = FIELD_STRING;
fields->ptr[fields->used]->string = buffer_init();
buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
fields->used++;
}
/* we need a new field */
if (fields->size == 0) {
fields->size = 16;
fields->used = 0;
fields->ptr = malloc(fields->size * sizeof(format_field * ));
} else if (fields->used == fields->size) {
fields->size += 16;
fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
}
/* search for the terminating command */
switch (format->ptr[i+1]) {
case '>':
case '<':
/* after the } has to be a character */
if (format->ptr[i+2] == '\0') {
log_error_write(srv, __FILE__, __LINE__, "s", "%< and %> have to be followed by a format-specifier");
return -1;
}
for (j = 0; fmap[j].key != '\0'; j++) {
if (fmap[j].key != format->ptr[i+2]) continue;
/* found key */
fields->ptr[fields->used] = malloc(sizeof(format_field));
fields->ptr[fields->used]->type = FIELD_FORMAT;
fields->ptr[fields->used]->field = fmap[j].type;
fields->ptr[fields->used]->string = NULL;
fields->used++;
break;
}
if (fmap[j].key == '\0') {
log_error_write(srv, __FILE__, __LINE__, "s", "%< and %> have to be followed by a valid format-specifier");
return -1;
}
start = i + 3;
i = start - 1; /* skip the string */
break;
case '{':
/* go forward to } */
for (k = i+2; k < format->used - 1; k++) {
if (format->ptr[k] == '}') break;
}
if (k == format->used - 1) {
log_error_write(srv, __FILE__, __LINE__, "s", "%{ has to be terminated by a }");
return -1;
}
/* after the } has to be a character */
if (format->ptr[k+1] == '\0') {
log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be followed by a format-specifier");
return -1;
}
if (k == i + 2) {
log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be contain a string");
return -1;
}
for (j = 0; fmap[j].key != '\0'; j++) {
if (fmap[j].key != format->ptr[k+1]) continue;
/* found key */
fields->ptr[fields->used] = malloc(sizeof(format_field));
fields->ptr[fields->used]->type = FIELD_FORMAT;
fields->ptr[fields->used]->field = fmap[j].type;
fields->ptr[fields->used]->string = buffer_init();
buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + i + 2, k - (i + 2));
fields->used++;
break;
}
if (fmap[j].key == '\0') {
log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be followed by a valid format-specifier");
return -1;
}
start = k + 2;
i = start - 1; /* skip the string */
break;
default:
/* after the % has to be a character */
if (format->ptr[i+1] == '\0') {
log_error_write(srv, __FILE__, __LINE__, "s", "% has to be followed by a format-specifier");
return -1;
}
for (j = 0; fmap[j].key != '\0'; j++) {
if (fmap[j].key != format->ptr[i+1]) continue;
/* found key */
fields->ptr[fields->used] = malloc(sizeof(format_field));
fields->ptr[fields->used]->type = FIELD_FORMAT;
fields->ptr[fields->used]->field = fmap[j].type;
fields->ptr[fields->used]->string = NULL;
fields->used++;
break;
}
if (fmap[j].key == '\0') {
log_error_write(srv, __FILE__, __LINE__, "s", "% has to be followed by a valid format-specifier");
return -1;
}
start = i + 2;
i = start - 1; /* skip the string */
break;
}
break;
}
}
if (start < i) {
/* copy the string */
if (fields->size == 0) {
fields->size = 16;
fields->used = 0;
fields->ptr = malloc(fields->size * sizeof(format_field * ));
} else if (fields->used == fields->size) {
fields->size += 16;
fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
}
fields->ptr[fields->used] = malloc(sizeof(format_field));
fields->ptr[fields->used]->type = FIELD_STRING;
fields->ptr[fields->used]->string = buffer_init();
buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
fields->used++;
}
return 0;
}
FREE_FUNC(mod_accesslog_free) {
plugin_data *p = p_d;
size_t i;
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (!s) continue;
if (s->access_logbuffer->used) {
if (s->log_access_fd != -1) {
write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
}
}
if (s->log_access_fd != -1) close(s->log_access_fd);
buffer_free(s->ts_accesslog_str);
buffer_free(s->ts_accesslog_fmt_str);
buffer_free(s->access_logbuffer);
buffer_free(s->format);
buffer_free(s->access_logfile);
if (s->parsed_format) {
size_t j;
for (j = 0; j < s->parsed_format->used; j++) {
if (s->parsed_format->ptr[j]->string) buffer_free(s->parsed_format->ptr[j]->string);
free(s->parsed_format->ptr[j]);
}
free(s->parsed_format->ptr);
free(s->parsed_format);
}
free(s);
}
free(p->config_storage);
}
if (p->syslog_logbuffer) buffer_free(p->syslog_logbuffer);
free(p);
return HANDLER_GO_ON;
}
SETDEFAULTS_FUNC(log_access_open) {
plugin_data *p = p_d;
size_t i = 0;
config_values_t cv[] = {
{ "accesslog.filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
{ "accesslog.use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
{ "accesslog.format", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
{ "accesslog.syslog-level", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
if (!p) return HANDLER_ERROR;
p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s;
s = calloc(1, sizeof(plugin_config));
s->access_logfile = buffer_init();
s->format = buffer_init();
s->access_logbuffer = buffer_init();
s->ts_accesslog_str = buffer_init();
s->ts_accesslog_fmt_str = buffer_init();
s->log_access_fd = -1;
s->last_generated_accesslog_ts = 0;
s->last_generated_accesslog_ts_ptr = &(s->last_generated_accesslog_ts);
s->syslog_level = LOG_INFO;
cv[0].destination = s->access_logfile;
cv[1].destination = &(s->use_syslog);
cv[2].destination = s->format;
cv[3].destination = &(s->syslog_level);
p->config_storage[i] = s;
if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
return HANDLER_ERROR;
}
if (i == 0 && buffer_is_empty(s->format)) {
/* set a default logfile string */
buffer_copy_string_len(s->format, CONST_STR_LEN("%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""));
}
/* parse */
if (s->format->used) {
size_t j, count;
s->parsed_format = calloc(1, sizeof(*(s->parsed_format)));
if (-1 == accesslog_parse_format(srv, s->parsed_format, s->format)) {
log_error_write(srv, __FILE__, __LINE__, "sb",
"parsing accesslog-definition failed:", s->format);
return HANDLER_ERROR;
}
/* make sure they didn't try to send the timestamp in twice...
* also, save the format string in a different variable (this
* will save a few conditionals later)
*/
count = 0;
for (j = 0; j < s->parsed_format->used; j++) {
if (FIELD_FORMAT == s->parsed_format->ptr[j]->type) {
if (FORMAT_TIMESTAMP == s->parsed_format->ptr[j]->field) {
if (!buffer_is_empty(s->parsed_format->ptr[j]->string)) {
buffer_copy_string(s->ts_accesslog_fmt_str, s->parsed_format->ptr[j]->string->ptr);
}
if (++count > 1) {
log_error_write(srv, __FILE__, __LINE__, "sb",
"you may not use the timestamp twice in the same access log:", s->format);
return HANDLER_ERROR;
}
}
}
}
#if 0
/* debugging */
for (j = 0; j < s->parsed_format->used; j++) {
switch (s->parsed_format->ptr[j]->type) {
case FIELD_FORMAT:
log_error_write(srv, __FILE__, __LINE__, "ssds",
"config:", "format", s->parsed_format->ptr[j]->field,
s->parsed_format->ptr[j]->string ?
s->parsed_format->ptr[j]->string->ptr : "" );
break;
case FIELD_STRING:
log_error_write(srv, __FILE__, __LINE__, "ssbs", "config:", "string '", s->parsed_format->ptr[j]->string, "'");
break;
default:
break;
}
}
#endif
}
s->append_tz_offset = 0;
if (buffer_is_empty(s->ts_accesslog_fmt_str)) {
#if defined(HAVE_STRUCT_TM_GMTOFF)
BUFFER_COPY_STRING_CONST(s->ts_accesslog_fmt_str, "[%d/%b/%Y:%H:%M:%S ");
s->append_tz_offset = 1;
#else
BUFFER_COPY_STRING_CONST(s->ts_accesslog_fmt_str, "[%d/%b/%Y:%H:%M:%S +0000]");
#endif
}
if (s->use_syslog) {
/* ignore the next checks */
continue;
}
if (s->access_logfile->used < 2) continue;
if (-1 == (s->log_access_fd = open_logfile_or_pipe(srv, s->access_logfile->ptr)))
return HANDLER_ERROR;
}
return HANDLER_GO_ON;
}
SIGHUP_FUNC(log_access_cycle) {
plugin_data *p = p_d;
size_t i;
if (!p->config_storage) return HANDLER_GO_ON;
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (s->access_logbuffer->used) {
if (s->log_access_fd != -1) {
write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
}
buffer_reset(s->access_logbuffer);
}
if (s->use_syslog == 0 &&
s->access_logfile->used > 1 &&
s->access_logfile->ptr[0] != '|') {
if (-1 != s->log_access_fd) close(s->log_access_fd);
if (-1 == (s->log_access_fd =
open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
log_error_write(srv, __FILE__, __LINE__, "ss", "cycling access-log failed:", strerror(errno));
return HANDLER_ERROR;
}
fd_close_on_exec(s->log_access_fd);
}
}
return HANDLER_GO_ON;
}
#define PATCH(x) \
p->conf.x = s->x;
static int mod_accesslog_patch_connection(server *srv, connection *con, plugin_data *p) {
size_t i, j;
plugin_config *s = p->config_storage[0];
PATCH(access_logfile);
PATCH(format);
PATCH(log_access_fd);
PATCH(last_generated_accesslog_ts_ptr);
PATCH(access_logbuffer);
PATCH(ts_accesslog_str);
PATCH(ts_accesslog_fmt_str);
PATCH(append_tz_offset);
PATCH(parsed_format);
PATCH(use_syslog);
PATCH(syslog_level);
/* skip the first, the global context */
for (i = 1; i < srv->config_context->used; i++) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];
/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;
/* merge config */
for (j = 0; j < dc->value->used; j++) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.filename"))) {
PATCH(access_logfile);
PATCH(log_access_fd);
PATCH(access_logbuffer);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.format"))) {
PATCH(format);
PATCH(parsed_format);
PATCH(last_generated_accesslog_ts_ptr);
PATCH(ts_accesslog_str);
PATCH(ts_accesslog_fmt_str);
PATCH(append_tz_offset);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.use-syslog"))) {
PATCH(use_syslog);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.syslog-level"))) {
PATCH(syslog_level);
}
}
}
return 0;
}
#undef PATCH
REQUESTDONE_FUNC(log_access_write) {
plugin_data *p = p_d;
buffer *b;
size_t j;
int newts = 0;
data_string *ds;
mod_accesslog_patch_connection(srv, con, p);
/* No output device, nothing to do */
if (!p->conf.use_syslog && p->conf.log_access_fd == -1) return HANDLER_GO_ON;
if (p->conf.use_syslog) {
b = p->syslog_logbuffer;
} else {
b = p->conf.access_logbuffer;
}
if (b->used == 0) {
buffer_copy_string_len(b, CONST_STR_LEN(""));
}
for (j = 0; j < p->conf.parsed_format->used; j++) {
switch(p->conf.parsed_format->ptr[j]->type) {
case FIELD_STRING:
buffer_append_string_buffer(b, p->conf.parsed_format->ptr[j]->string);
break;
case FIELD_FORMAT:
switch(p->conf.parsed_format->ptr[j]->field) {
case FORMAT_TIMESTAMP:
/* cache the generated timestamp */
if (srv->cur_ts != *(p->conf.last_generated_accesslog_ts_ptr)) {
struct tm tm;
#if defined(HAVE_STRUCT_TM_GMTOFF)
long scd, hrs, min;
#endif
buffer_prepare_copy(p->conf.ts_accesslog_str, 255);
#if defined(HAVE_STRUCT_TM_GMTOFF)
# ifdef HAVE_LOCALTIME_R
localtime_r(&(srv->cur_ts), &tm);
strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, &tm);
# else /* HAVE_LOCALTIME_R */
strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, localtime_r(&(srv->cur_ts)));
# endif /* HAVE_LOCALTIME_R */
p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
if (p->conf.append_tz_offset) {
buffer_append_string_len(p->conf.ts_accesslog_str, tm.tm_gmtoff >= 0 ? "+" : "-", 1);
scd = abs(tm.tm_gmtoff);
hrs = scd / 3600;
min = (scd % 3600) / 60;
/* hours */
if (hrs < 10) buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("0"));
buffer_append_long(p->conf.ts_accesslog_str, hrs);
if (min < 10) buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("0"));
buffer_append_long(p->conf.ts_accesslog_str, min);
buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("]"));
}
#else /* HAVE_STRUCT_TM_GMTOFF */
# ifdef HAVE_GMTIME_R
gmtime_r(&(srv->cur_ts), &tm);
strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, &tm);
# else /* HAVE_GMTIME_R */
strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, p->conf.ts_accesslog_fmt_str->ptr, gmtime(&(srv->cur_ts)));
# endif /* HAVE_GMTIME_R */
p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
#endif /* HAVE_STRUCT_TM_GMTOFF */
*(p->conf.last_generated_accesslog_ts_ptr) = srv->cur_ts;
newts = 1;
}
buffer_append_string_buffer(b, p->conf.ts_accesslog_str);
break;
case FORMAT_REMOTE_HOST:
/* handle inet_ntop cache */
buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
break;
case FORMAT_REMOTE_IDENT:
/* ident */
buffer_append_string_len(b, CONST_STR_LEN("-"));
break;
case FORMAT_REMOTE_USER:
if (NULL != (ds = (data_string *)array_get_element(con->environment, "REMOTE_USER")) && ds->value->used > 1) {
accesslog_append_escaped(b, ds->value);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_REQUEST_LINE:
if (con->request.request_line->used) {
accesslog_append_escaped(b, con->request.request_line);
}
break;
case FORMAT_STATUS:
buffer_append_long(b, con->http_status);
break;
case FORMAT_BYTES_OUT_NO_HEADER:
if (con->bytes_written > 0) {
buffer_append_off_t(b,
con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_HEADER:
if (NULL != (ds = (data_string *)array_get_element(con->request.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
accesslog_append_escaped(b, ds->value);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_RESPONSE_HEADER:
if (NULL != (ds = (data_string *)array_get_element(con->response.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
accesslog_append_escaped(b, ds->value);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_ENV:
if (NULL != (ds = (data_string *)array_get_element(con->environment, p->conf.parsed_format->ptr[j]->string->ptr))) {
accesslog_append_escaped(b, ds->value);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_FILENAME:
if (con->physical.path->used > 1) {
buffer_append_string_buffer(b, con->physical.path);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_BYTES_OUT:
if (con->bytes_written > 0) {
buffer_append_off_t(b, con->bytes_written);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_BYTES_IN:
if (con->bytes_read > 0) {
buffer_append_off_t(b, con->bytes_read);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_TIME_USED:
buffer_append_long(b, srv->cur_ts - con->request_start);
break;
case FORMAT_SERVER_NAME:
if (con->server_name->used > 1) {
buffer_append_string_buffer(b, con->server_name);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_HTTP_HOST:
if (con->uri.authority->used > 1) {
accesslog_append_escaped(b, con->uri.authority);
} else {
buffer_append_string_len(b, CONST_STR_LEN("-"));
}
break;
case FORMAT_REQUEST_PROTOCOL:
buffer_append_string_len(b,
con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0", 8);
break;
case FORMAT_REQUEST_METHOD:
buffer_append_string(b, get_http_method_name(con->request.http_method));
break;
case FORMAT_PERCENT:
buffer_append_string_len(b, CONST_STR_LEN("%"));
break;
case FORMAT_SERVER_PORT:
{
const char *colon;
buffer *srvtoken = ((server_socket*)(con->srv_socket))->srv_token;
if (srvtoken->ptr[0] == '[') {
colon = strstr(srvtoken->ptr, "]:");
} else {
colon = strchr(srvtoken->ptr, ':');
}
if (colon) {
buffer_append_string(b, colon+1);
} else {
buffer_append_long(b, srv->srvconf.port);
}
}
break;
case FORMAT_QUERY_STRING:
accesslog_append_escaped(b, con->uri.query);
break;
case FORMAT_URL:
accesslog_append_escaped(b, con->uri.path_raw);
break;
case FORMAT_CONNECTION_STATUS:
switch(con->keep_alive) {
case 0: buffer_append_string_len(b, CONST_STR_LEN("-")); break;
default: buffer_append_string_len(b, CONST_STR_LEN("+")); break;
}
break;
default:
/*
{ 'a', FORMAT_REMOTE_ADDR },
{ 'A', FORMAT_LOCAL_ADDR },
{ 'C', FORMAT_COOKIE },
{ 'D', FORMAT_TIME_USED_MS },
*/
break;
}
break;
default:
break;
}
}
buffer_append_string_len(b, CONST_STR_LEN("\n"));
if (p->conf.use_syslog || /* syslog doesn't cache */
(p->conf.access_logfile->used && p->conf.access_logfile->ptr[0] == '|') || /* pipes don't cache */
newts ||
b->used > BUFFER_MAX_REUSE_SIZE) {
if (p->conf.use_syslog) {
#ifdef HAVE_SYSLOG_H
if (b->used > 2) {
/* syslog appends a \n on its own */
syslog(p->conf.syslog_level, "%*s", (int) b->used - 2, b->ptr);
}
#endif
} else if (p->conf.log_access_fd != -1) {
force_assert(b->used > 0);
write(p->conf.log_access_fd, b->ptr, b->used - 1);
}
buffer_reset(b);
}
return HANDLER_GO_ON;
}
int mod_accesslog_plugin_init(plugin *p);
int mod_accesslog_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("accesslog");
p->init = mod_accesslog_init;
p->set_defaults= log_access_open;
p->cleanup = mod_accesslog_free;
p->handle_request_done = log_access_write;
p->handle_sighup = log_access_cycle;
p->data = NULL;
return 0;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>