fix: renforcer la robustesse du module et étendre les tests/CI

Co-authored-by: aider (openrouter/openai/gpt-5.3-codex) <aider@aider.chat>
This commit is contained in:
Jacquin Antoine
2026-02-28 20:28:40 +01:00
parent a935ed1641
commit 46291898e1
10 changed files with 735 additions and 447 deletions

View File

@ -10,6 +10,7 @@
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "mod_reqin_log.h"
#include "apr_strings.h"
#include "apr_time.h"
#include "apr_lib.h"
@ -24,12 +25,15 @@
#include <fcntl.h>
#include <time.h>
#include <string.h>
#define MOD_REQIN_LOG_NAME "mod_reqin_log"
#include <stdlib.h>
#include <limits.h>
/* Maximum Unix socket path length (sun_path is typically 108 bytes) */
#define MAX_SOCKET_PATH_LEN (sizeof(((struct sockaddr_un *)0)->sun_path) - 1)
/* Maximum JSON log line size (64KB) - prevents memory exhaustion DoS */
#define MAX_JSON_SIZE (64 * 1024)
/* Default sensitive headers blacklist - prevents accidental logging of credentials */
static const char *const DEFAULT_SENSITIVE_HEADERS[] = {
"Authorization",
@ -42,26 +46,6 @@ static const char *const DEFAULT_SENSITIVE_HEADERS[] = {
NULL
};
/* Maximum JSON log line size (64KB) - prevents memory exhaustion DoS */
#define MAX_JSON_SIZE (64 * 1024)
/* Default configuration values */
#define DEFAULT_MAX_HEADERS 10
#define DEFAULT_MAX_HEADER_VALUE_LEN 256
#define DEFAULT_RECONNECT_INTERVAL 10
#define DEFAULT_ERROR_REPORT_INTERVAL 10
/* Module configuration structure */
typedef struct {
int enabled;
const char *socket_path;
apr_array_header_t *headers;
int max_headers;
int max_header_value_len;
int reconnect_interval;
int error_report_interval;
} reqin_log_config_t;
/* Dynamic string buffer */
typedef struct {
char *data;
@ -90,6 +74,7 @@ typedef struct {
static void dynbuf_append(dynbuf_t *db, const char *str, apr_size_t len);
static void append_json_string(dynbuf_t *db, const char *str);
static void format_iso8601(dynbuf_t *db, apr_time_t t);
static int parse_int_strict(const char *arg, int *out);
/* Forward declarations for server config */
static void *reqin_log_create_server_conf(apr_pool_t *pool, server_rec *s);
@ -106,6 +91,7 @@ static const char *cmd_set_error_report_interval(cmd_parms *cmd, void *dummy, co
/* Forward declarations for hooks */
static int reqin_log_post_read_request(request_rec *r);
static void reqin_log_child_init(apr_pool_t *p, server_rec *s);
static int reqin_log_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s);
static void reqin_log_register_hooks(apr_pool_t *p);
/* Command table */
@ -141,8 +127,7 @@ module AP_MODULE_DECLARE_DATA reqin_log_module = {
/* Get module configuration */
static reqin_log_server_conf_t *get_server_conf(server_rec *s)
{
reqin_log_server_conf_t *srv_conf = (reqin_log_server_conf_t *)ap_get_module_config(s->module_config, &reqin_log_module);
return srv_conf;
return (reqin_log_server_conf_t *)ap_get_module_config(s->module_config, &reqin_log_module);
}
/* Create server configuration */
@ -164,12 +149,6 @@ static void *reqin_log_create_server_conf(apr_pool_t *pool, server_rec *s)
srv_conf->child_state.last_error_report = 0;
srv_conf->child_state.connect_failed = 0;
/* Create mutex for thread-safe socket FD access in worker/event MPMs */
if (apr_thread_mutex_create(&srv_conf->child_state.fd_mutex,
APR_THREAD_MUTEX_DEFAULT, pool) != APR_SUCCESS) {
srv_conf->child_state.fd_mutex = NULL;
}
return srv_conf;
}
@ -187,11 +166,11 @@ static void dynbuf_init(dynbuf_t *db, apr_pool_t *pool, apr_size_t initial_capac
static void dynbuf_append(dynbuf_t *db, const char *str, apr_size_t len)
{
if (str == NULL) return;
if (len == (apr_size_t)-1) {
len = strlen(str);
}
if (db->len + len >= db->capacity) {
apr_size_t new_capacity = (db->len + len + 1) * 2;
char *new_data = apr_palloc(db->pool, new_capacity);
@ -199,7 +178,7 @@ static void dynbuf_append(dynbuf_t *db, const char *str, apr_size_t len)
db->data = new_data;
db->capacity = new_capacity;
}
memcpy(db->data + db->len, str, len);
db->len += len;
db->data[db->len] = '\0';
@ -225,7 +204,7 @@ static void append_json_string(dynbuf_t *db, const char *str)
if (str == NULL) {
return;
}
for (const char *p = str; *p; p++) {
char c = *p;
switch (c) {
@ -253,7 +232,7 @@ static void format_iso8601(dynbuf_t *db, apr_time_t t)
{
apr_time_exp_t tm;
apr_time_exp_gmt(&tm, t);
char time_str[32];
snprintf(time_str, sizeof(time_str), "%04d-%02d-%02dT%02d:%02d:%02dZ",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
@ -261,6 +240,18 @@ static void format_iso8601(dynbuf_t *db, apr_time_t t)
dynbuf_append(db, time_str, -1);
}
static int parse_int_strict(const char *arg, int *out)
{
char *end = NULL;
long v;
if (arg == NULL || *arg == '\0' || out == NULL) return -1;
errno = 0;
v = strtol(arg, &end, 10);
if (errno != 0 || end == arg || *end != '\0' || v < INT_MIN || v > INT_MAX) return -1;
*out = (int)v;
return 0;
}
/* ============== Configuration Command Handlers ============== */
static const char *cmd_set_enabled(cmd_parms *cmd, void *dummy, int flag)
@ -281,6 +272,12 @@ static const char *cmd_set_socket(cmd_parms *cmd, void *dummy, const char *arg)
if (srv_conf == NULL) {
return "Internal error: server configuration not available";
}
if (arg == NULL || *arg == '\0') {
return "JsonSockLogSocket must be a non-empty Unix socket path";
}
if (strlen(arg) >= MAX_SOCKET_PATH_LEN) {
return "JsonSockLogSocket path is too long for Unix domain socket";
}
srv_conf->config->socket_path = apr_pstrdup(cmd->pool, arg);
return NULL;
}
@ -298,12 +295,15 @@ static const char *cmd_set_headers(cmd_parms *cmd, void *dummy, const char *arg)
static const char *cmd_set_max_headers(cmd_parms *cmd, void *dummy, const char *arg)
{
int val;
(void)dummy;
reqin_log_server_conf_t *srv_conf = get_server_conf(cmd->server);
if (srv_conf == NULL) {
return "Internal error: server configuration not available";
}
int val = atoi(arg);
if (parse_int_strict(arg, &val) != 0) {
return "JsonSockLogMaxHeaders must be a valid integer";
}
if (val < 0) {
return "JsonSockLogMaxHeaders must be >= 0";
}
@ -313,12 +313,15 @@ static const char *cmd_set_max_headers(cmd_parms *cmd, void *dummy, const char *
static const char *cmd_set_max_header_value_len(cmd_parms *cmd, void *dummy, const char *arg)
{
int val;
(void)dummy;
reqin_log_server_conf_t *srv_conf = get_server_conf(cmd->server);
if (srv_conf == NULL) {
return "Internal error: server configuration not available";
}
int val = atoi(arg);
if (parse_int_strict(arg, &val) != 0) {
return "JsonSockLogMaxHeaderValueLen must be a valid integer";
}
if (val < 1) {
return "JsonSockLogMaxHeaderValueLen must be >= 1";
}
@ -328,12 +331,15 @@ static const char *cmd_set_max_header_value_len(cmd_parms *cmd, void *dummy, con
static const char *cmd_set_reconnect_interval(cmd_parms *cmd, void *dummy, const char *arg)
{
int val;
(void)dummy;
reqin_log_server_conf_t *srv_conf = get_server_conf(cmd->server);
if (srv_conf == NULL) {
return "Internal error: server configuration not available";
}
int val = atoi(arg);
if (parse_int_strict(arg, &val) != 0) {
return "JsonSockLogReconnectInterval must be a valid integer";
}
if (val < 0) {
return "JsonSockLogReconnectInterval must be >= 0";
}
@ -343,12 +349,15 @@ static const char *cmd_set_reconnect_interval(cmd_parms *cmd, void *dummy, const
static const char *cmd_set_error_report_interval(cmd_parms *cmd, void *dummy, const char *arg)
{
int val;
(void)dummy;
reqin_log_server_conf_t *srv_conf = get_server_conf(cmd->server);
if (srv_conf == NULL) {
return "Internal error: server configuration not available";
}
int val = atoi(arg);
if (parse_int_strict(arg, &val) != 0) {
return "JsonSockLogErrorReportInterval must be a valid integer";
}
if (val < 0) {
return "JsonSockLogErrorReportInterval must be >= 0";
}
@ -367,7 +376,7 @@ static int is_sensitive_header(const char *name)
if (name == NULL) {
return 0;
}
for (int i = 0; DEFAULT_SENSITIVE_HEADERS[i] != NULL; i++) {
if (strcasecmp(name, DEFAULT_SENSITIVE_HEADERS[i]) == 0) {
return 1;
@ -391,65 +400,93 @@ static int is_sensitive_header(const char *name)
static int try_connect(reqin_log_config_t *cfg, reqin_log_child_state_t *state, server_rec *s)
{
apr_time_t now = apr_time_now();
apr_time_t interval = apr_time_from_sec(cfg->reconnect_interval);
apr_time_t now;
apr_time_t reconnect_interval;
apr_time_t error_interval;
int err = 0;
int should_report = 0;
int fd;
int flags;
struct sockaddr_un addr;
int rc;
if (cfg == NULL || state == NULL || s == NULL || cfg->socket_path == NULL || cfg->socket_path[0] == '\0') {
return -1;
}
if (strlen(cfg->socket_path) >= MAX_SOCKET_PATH_LEN) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
MOD_REQIN_LOG_NAME ": Unix socket path too long, cannot connect");
return -1;
}
now = apr_time_now();
reconnect_interval = apr_time_from_sec(cfg->reconnect_interval);
error_interval = apr_time_from_sec(cfg->error_report_interval);
FD_MUTEX_LOCK(state);
if (state->connect_failed &&
(now - state->last_connect_attempt) < interval) {
(now - state->last_connect_attempt) < reconnect_interval) {
FD_MUTEX_UNLOCK(state);
return -1;
}
state->last_connect_attempt = now;
/* Validate socket path length before use */
if (strlen(cfg->socket_path) >= MAX_SOCKET_PATH_LEN) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
MOD_REQIN_LOG_NAME ": Configuration error - socket path too long");
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
MOD_REQIN_LOG_NAME ": Path length %zu exceeds maximum %zu",
strlen(cfg->socket_path), MAX_SOCKET_PATH_LEN);
return -1;
}
FD_MUTEX_LOCK(state);
if (state->socket_fd < 0) {
state->socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (state->socket_fd < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
MOD_REQIN_LOG_NAME ": Failed to create socket");
ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, s,
MOD_REQIN_LOG_NAME ": Socket creation error detail");
err = errno;
state->connect_failed = 1;
if ((now - state->last_error_report) >= error_interval) {
state->last_error_report = now;
should_report = 1;
}
FD_MUTEX_UNLOCK(state);
if (should_report) {
ap_log_error(APLOG_MARK, APLOG_ERR, err, s,
MOD_REQIN_LOG_NAME ": Unix socket connect failed: cannot create socket");
}
return -1;
}
int flags = fcntl(state->socket_fd, F_GETFL, 0);
flags = fcntl(state->socket_fd, F_GETFL, 0);
if (flags < 0) {
flags = 0;
}
fcntl(state->socket_fd, F_SETFL, flags | O_NONBLOCK);
}
struct sockaddr_un addr;
fd = state->socket_fd;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", cfg->socket_path);
int rc = connect(state->socket_fd, (struct sockaddr *)&addr, sizeof(addr));
rc = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (rc < 0) {
int err = errno;
if (err != EINPROGRESS && err != EAGAIN && err != EWOULDBLOCK) {
close(state->socket_fd);
state->socket_fd = -1;
state->connect_failed = 1;
err = errno;
if (err == EINPROGRESS || err == EAGAIN || err == EWOULDBLOCK || err == EALREADY || err == EISCONN) {
state->connect_failed = 0;
FD_MUTEX_UNLOCK(state);
if ((now - state->last_error_report) >= apr_time_from_sec(cfg->error_report_interval)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
MOD_REQIN_LOG_NAME ": Unix socket connection failed");
ap_log_error(APLOG_MARK, APLOG_DEBUG, err, s,
MOD_REQIN_LOG_NAME ": Connect failed at %s: %s", cfg->socket_path, strerror(err));
state->last_error_report = now;
}
return -1;
return 0;
}
close(fd);
state->socket_fd = -1;
state->connect_failed = 1;
if ((now - state->last_error_report) >= error_interval) {
state->last_error_report = now;
should_report = 1;
}
FD_MUTEX_UNLOCK(state);
if (should_report) {
ap_log_error(APLOG_MARK, APLOG_ERR, err, s,
MOD_REQIN_LOG_NAME ": Unix socket connect failed: %s", cfg->socket_path);
}
return -1;
}
state->connect_failed = 0;
@ -459,9 +496,16 @@ static int try_connect(reqin_log_config_t *cfg, reqin_log_child_state_t *state,
static int ensure_connected(reqin_log_config_t *cfg, reqin_log_child_state_t *state, server_rec *s)
{
if (state->socket_fd >= 0 && !state->connect_failed) {
int connected;
FD_MUTEX_LOCK(state);
connected = (state->socket_fd >= 0 && !state->connect_failed);
FD_MUTEX_UNLOCK(state);
if (connected) {
return 0;
}
return try_connect(cfg, state, s);
}
@ -469,49 +513,57 @@ static int write_to_socket(const char *data, apr_size_t len, server_rec *s,
reqin_log_config_t *cfg, reqin_log_child_state_t *state)
{
int fd;
int result = -1;
apr_size_t total_written = 0;
apr_time_t error_interval = apr_time_from_sec(cfg->error_report_interval);
FD_MUTEX_LOCK(state);
fd = state->socket_fd;
if (fd < 0) {
FD_MUTEX_UNLOCK(state);
return -1;
}
apr_size_t total_written = 0;
while (total_written < len) {
ssize_t n = write(fd, data + total_written, len - total_written);
if (n < 0) {
int err = errno;
if (err == EAGAIN || err == EWOULDBLOCK) {
FD_MUTEX_UNLOCK(state);
return -1;
}
if (err == EPIPE || err == ECONNRESET) {
apr_time_t now = apr_time_now();
int should_report = 0;
int conn_lost = (err == EPIPE || err == ECONNRESET || err == ENOTCONN);
if (conn_lost) {
close(fd);
state->socket_fd = -1;
state->connect_failed = 1;
FD_MUTEX_UNLOCK(state);
apr_time_t now = apr_time_now();
if ((now - state->last_error_report) >= apr_time_from_sec(cfg->error_report_interval)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
MOD_REQIN_LOG_NAME ": Unix socket write failed, connection lost");
ap_log_error(APLOG_MARK, APLOG_DEBUG, err, s,
MOD_REQIN_LOG_NAME ": Write error detail: %s", strerror(err));
state->last_error_report = now;
}
return -1;
}
if (err != EAGAIN && err != EWOULDBLOCK &&
(now - state->last_error_report) >= error_interval) {
state->last_error_report = now;
should_report = 1;
}
FD_MUTEX_UNLOCK(state);
if (should_report) {
if (conn_lost) {
ap_log_error(APLOG_MARK, APLOG_ERR, err, s,
MOD_REQIN_LOG_NAME ": Unix socket write failed: connection lost");
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, err, s,
MOD_REQIN_LOG_NAME ": Unix socket write failed");
}
}
return -1;
}
total_written += n;
total_written += (apr_size_t)n;
}
result = 0;
FD_MUTEX_UNLOCK(state);
return result;
return 0;
}
/* ============== Request Logging Functions ============== */
@ -519,11 +571,12 @@ static int write_to_socket(const char *data, apr_size_t len, server_rec *s,
static const char *get_header(request_rec *r, const char *name)
{
const apr_table_t *headers = r->headers_in;
apr_table_entry_t *elts = (apr_table_entry_t *)apr_table_elts(headers)->elts;
int nelts = apr_table_elts(headers)->nelts;
apr_array_header_t *arr = apr_table_elts(headers);
apr_table_entry_t *elts = (apr_table_entry_t *)arr->elts;
int nelts = arr->nelts;
for (int i = 0; i < nelts; i++) {
if (strcasecmp(elts[i].key, name) == 0) {
if (elts[i].key != NULL && strcasecmp(elts[i].key, name) == 0) {
return elts[i].val;
}
}
@ -532,15 +585,39 @@ static const char *get_header(request_rec *r, const char *name)
static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child_state_t *state)
{
apr_pool_t *pool = r->pool;
server_rec *s = r->server;
apr_pool_t *pool;
server_rec *s;
dynbuf_t buf;
char port_buf[16];
const char *src_ip;
const char *dst_ip;
const char *method;
const char *path;
const char *host;
const char *http_version;
if (!r || !r->server || !r->pool || !r->connection) {
return;
}
pool = r->pool;
s = r->server;
if (ensure_connected(cfg, state, s) < 0) {
return;
}
dynbuf_t buf;
src_ip = r->useragent_ip ? r->useragent_ip :
(r->connection->client_ip ? r->connection->client_ip : "");
dst_ip = r->connection->local_ip ? r->connection->local_ip : "";
method = r->method ? r->method : "UNKNOWN";
path = r->parsed_uri.path ? r->parsed_uri.path : "/";
host = apr_table_get(r->headers_in, "Host");
if (host == NULL) {
host = "";
}
http_version = r->protocol ? r->protocol : "UNKNOWN";
dynbuf_init(&buf, pool, 4096);
dynbuf_append(&buf, "{", 1);
@ -550,30 +627,27 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
format_iso8601(&buf, r->request_time);
dynbuf_append(&buf, "\",", 2);
/* timestamp (nanoseconds since epoch)
* Note: apr_time_now() returns microseconds. Multiplying by 1000 gives nanoseconds.
* LIMITATION: apr_time_t is 64-bit microseconds. Max value ~9.22e18 microseconds.
* In nanoseconds, this overflows around year 2262. However, practical overflow of
* the JSON integer field (uint64) occurs at ~1.84e19 nanoseconds = year 2262.
* Current implementation is safe until ~2262.
*/
apr_time_t now = apr_time_now();
apr_uint64_t ns = (apr_uint64_t)now * 1000;
char ts_buf[32];
snprintf(ts_buf, sizeof(ts_buf), "%" APR_UINT64_T_FMT, ns);
dynbuf_append(&buf, "\"timestamp\":", 12);
dynbuf_append(&buf, ts_buf, -1);
dynbuf_append(&buf, ",", 1);
/* timestamp (nanoseconds since epoch) */
{
apr_time_t now = apr_time_now();
apr_uint64_t ns = (apr_uint64_t)now * 1000;
char ts_buf[32];
snprintf(ts_buf, sizeof(ts_buf), "%" APR_UINT64_T_FMT, ns);
dynbuf_append(&buf, "\"timestamp\":", 12);
dynbuf_append(&buf, ts_buf, -1);
dynbuf_append(&buf, ",", 1);
}
/* src_ip */
dynbuf_append(&buf, "\"src_ip\":\"", 10);
dynbuf_append(&buf, r->useragent_ip ? r->useragent_ip : r->connection->client_ip, -1);
append_json_string(&buf, src_ip);
dynbuf_append(&buf, "\",", 2);
/* src_port */
port_buf[0] = '\0';
if (r->connection->client_addr != NULL) {
snprintf(port_buf, sizeof(port_buf), "%u", r->connection->client_addr->port);
snprintf(port_buf, sizeof(port_buf), "%u", (unsigned int)r->connection->client_addr->port);
} else {
snprintf(port_buf, sizeof(port_buf), "0");
}
dynbuf_append(&buf, "\"src_port\":", 11);
dynbuf_append(&buf, port_buf, -1);
@ -581,15 +655,14 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
/* dst_ip */
dynbuf_append(&buf, "\"dst_ip\":\"", 10);
if (r->connection->local_ip) {
dynbuf_append(&buf, r->connection->local_ip, -1);
}
append_json_string(&buf, dst_ip);
dynbuf_append(&buf, "\",", 2);
/* dst_port */
port_buf[0] = '\0';
if (r->connection->local_addr != NULL) {
snprintf(port_buf, sizeof(port_buf), "%u", r->connection->local_addr->port);
snprintf(port_buf, sizeof(port_buf), "%u", (unsigned int)r->connection->local_addr->port);
} else {
snprintf(port_buf, sizeof(port_buf), "0");
}
dynbuf_append(&buf, "\"dst_port\":", 11);
dynbuf_append(&buf, port_buf, -1);
@ -597,23 +670,22 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
/* method */
dynbuf_append(&buf, "\"method\":\"", 10);
append_json_string(&buf, r->method);
append_json_string(&buf, method);
dynbuf_append(&buf, "\",", 2);
/* path */
dynbuf_append(&buf, "\"path\":\"", 8);
append_json_string(&buf, r->parsed_uri.path ? r->parsed_uri.path : "/");
append_json_string(&buf, path);
dynbuf_append(&buf, "\",", 2);
/* host */
const char *host = apr_table_get(r->headers_in, "Host");
dynbuf_append(&buf, "\"host\":\"", 8);
append_json_string(&buf, host ? host : "");
append_json_string(&buf, host);
dynbuf_append(&buf, "\",", 2);
/* http_version */
dynbuf_append(&buf, "\"http_version\":\"", 16);
dynbuf_append(&buf, r->protocol ? r->protocol : "UNKNOWN", -1);
append_json_string(&buf, http_version);
dynbuf_append(&buf, "\"", 1);
/* Check buffer size before adding headers to prevent memory exhaustion */
@ -631,36 +703,38 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
for (int i = 0; i < cfg->headers->nelts && header_count < max_to_log; i++) {
const char *header_name = header_names[i];
const char *header_value;
/* Skip sensitive headers to prevent credential leakage */
if (is_sensitive_header(header_name)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
MOD_REQIN_LOG_NAME ": Skipping sensitive header: %s", header_name);
continue;
}
const char *header_value = get_header(r, header_name);
header_value = get_header(r, header_name);
if (header_value != NULL) {
/* Check if adding this header would exceed size limit */
apr_size_t header_contrib = 9 + strlen(header_name) + 3 +
strlen(header_value) + 1;
apr_size_t header_contrib = 9 + strlen(header_name) + 3 + strlen(header_value) + 1;
apr_size_t val_len;
char *truncated;
if (buf.len + header_contrib >= MAX_JSON_SIZE) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
MOD_REQIN_LOG_NAME ": JSON size limit reached, truncating headers");
break;
}
dynbuf_append(&buf, ",\"header_", 9);
append_json_string(&buf, header_name);
dynbuf_append(&buf, "\":\"", 3);
apr_size_t val_len = strlen(header_value);
val_len = strlen(header_value);
if ((int)val_len > cfg->max_header_value_len) {
val_len = cfg->max_header_value_len;
val_len = (apr_size_t)cfg->max_header_value_len;
}
char *truncated = apr_pstrmemdup(pool, header_value, val_len);
truncated = apr_pstrmemdup(pool, header_value, val_len);
append_json_string(&buf, truncated);
dynbuf_append(&buf, "\"", 1);
@ -671,6 +745,25 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
dynbuf_append(&buf, "}\n", 2);
if (buf.len > MAX_JSON_SIZE) {
apr_time_t now = apr_time_now();
apr_time_t error_interval = apr_time_from_sec(cfg->error_report_interval);
int should_report = 0;
FD_MUTEX_LOCK(state);
if ((now - state->last_error_report) >= error_interval) {
state->last_error_report = now;
should_report = 1;
}
FD_MUTEX_UNLOCK(state);
if (should_report) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
MOD_REQIN_LOG_NAME ": JSON log line exceeds maximum size, dropping");
}
return;
}
write_to_socket(buf.data, buf.len, s, cfg, state);
}
@ -680,7 +773,7 @@ static int reqin_log_post_read_request(request_rec *r)
{
reqin_log_server_conf_t *srv_conf = get_server_conf(r->server);
if (srv_conf == NULL || srv_conf->config == NULL ||
if (srv_conf == NULL || srv_conf->config == NULL ||
!srv_conf->config->enabled || srv_conf->config->socket_path == NULL) {
return DECLINED;
}
@ -691,20 +784,26 @@ static int reqin_log_post_read_request(request_rec *r)
static void reqin_log_child_init(apr_pool_t *p, server_rec *s)
{
(void)p;
reqin_log_server_conf_t *srv_conf = get_server_conf(s);
if (srv_conf == NULL) {
return;
}
/* Initialize child state for this process */
srv_conf->child_state.socket_fd = -1;
srv_conf->child_state.last_connect_attempt = 0;
srv_conf->child_state.last_error_report = 0;
srv_conf->child_state.connect_failed = 0;
if (srv_conf->config == NULL || !srv_conf->config->enabled ||
srv_conf->child_state.fd_mutex = NULL;
if (apr_thread_mutex_create(&srv_conf->child_state.fd_mutex,
APR_THREAD_MUTEX_DEFAULT, p) != APR_SUCCESS) {
srv_conf->child_state.fd_mutex = NULL;
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
MOD_REQIN_LOG_NAME ": Failed to create mutex, continuing in degraded mode");
}
if (srv_conf->config == NULL || !srv_conf->config->enabled ||
srv_conf->config->socket_path == NULL) {
return;
}
@ -712,9 +811,47 @@ static void reqin_log_child_init(apr_pool_t *p, server_rec *s)
try_connect(srv_conf->config, &srv_conf->child_state, s);
}
static int reqin_log_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
server_rec *cur;
(void)pconf;
(void)plog;
(void)ptemp;
for (cur = s; cur != NULL; cur = cur->next) {
reqin_log_server_conf_t *srv_conf = get_server_conf(cur);
reqin_log_config_t *cfg;
if (srv_conf == NULL || srv_conf->config == NULL) {
continue;
}
cfg = srv_conf->config;
if (!cfg->enabled) {
continue;
}
if (cfg->socket_path == NULL || cfg->socket_path[0] == '\0') {
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, cur,
MOD_REQIN_LOG_NAME ": JsonSockLogEnabled is On but JsonSockLogSocket is missing");
return HTTP_INTERNAL_SERVER_ERROR;
}
if (strlen(cfg->socket_path) >= MAX_SOCKET_PATH_LEN) {
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, cur,
MOD_REQIN_LOG_NAME ": JsonSockLogSocket path too long (max %d)", (int)MAX_SOCKET_PATH_LEN - 1);
return HTTP_INTERNAL_SERVER_ERROR;
}
}
return OK;
}
static void reqin_log_register_hooks(apr_pool_t *p)
{
(void)p;
ap_hook_post_config(reqin_log_post_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_read_request(reqin_log_post_read_request, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_child_init(reqin_log_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}