mod_firehose.c revision 231ca3b40df46af2a63d006ebde6b745f73c40b2
97a9a944b5887e91042b019776c41d5dd74557aferikabele/* Licensed to the Apache Software Foundation (ASF) under one or more
97a9a944b5887e91042b019776c41d5dd74557aferikabele * contributor license agreements. See the NOTICE file distributed with
97a9a944b5887e91042b019776c41d5dd74557aferikabele * this work for additional information regarding copyright ownership.
a945f35eff8b6a88009ce73de6d4c862ce58de3cslive * The ASF licenses this file to You under the Apache License, Version 2.0
a945f35eff8b6a88009ce73de6d4c862ce58de3cslive * (the "License"); you may not use this file except in compliance with
a945f35eff8b6a88009ce73de6d4c862ce58de3cslive * the License. You may obtain a copy of the License at
5a58787efeb02a1c3f06569d019ad81fd2efa06end *
5a58787efeb02a1c3f06569d019ad81fd2efa06end * http://www.apache.org/licenses/LICENSE-2.0
5a58787efeb02a1c3f06569d019ad81fd2efa06end *
5a58787efeb02a1c3f06569d019ad81fd2efa06end * Unless required by applicable law or agreed to in writing, software
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * distributed under the License is distributed on an "AS IS" BASIS,
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * See the License for the specific language governing permissions and
d29d9ab4614ff992b0e8de6e2b88d52b6f1f153erbowen * limitations under the License.
5a58787efeb02a1c3f06569d019ad81fd2efa06end */
5a58787efeb02a1c3f06569d019ad81fd2efa06end
5a58787efeb02a1c3f06569d019ad81fd2efa06end/*
d229f940abfb2490dee17979e9a5ff31b7012eb5rbowen * Originally written @ Covalent by Jim Jagielski
3f08db06526d6901aa08c110b5bc7dde6bc39905nd * Modified to support writing to non blocking pipes @ BBC by Graham Leggett
5a58787efeb02a1c3f06569d019ad81fd2efa06end * Modifications (C) 2011 British Broadcasting Corporation
5a58787efeb02a1c3f06569d019ad81fd2efa06end */
5a58787efeb02a1c3f06569d019ad81fd2efa06end
3f08db06526d6901aa08c110b5bc7dde6bc39905nd/*
5a58787efeb02a1c3f06569d019ad81fd2efa06end * mod_firehose.c:
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd * A request and response sniffer for Apache v2.x. It logs
3b3b7fc78d1f5bfc2769903375050048ff41ff26nd * all filter data right before and after it goes out on the
ad74a0524a06bfe11b7de9e3b4ce7233ab3bd3f7nd * wire (BUT right before SSL encoded or after SSL decoded).
ad74a0524a06bfe11b7de9e3b4ce7233ab3bd3f7nd * It can produce a *huge* amount of data.
e1e8390280254f7f0580d701e583f670643d4f3fnilgun */
f086b4b402fa9a2fefc7dda85de2a3cc1cd0a654rjung
3b3b7fc78d1f5bfc2769903375050048ff41ff26nd#include "httpd.h"
5a58787efeb02a1c3f06569d019ad81fd2efa06end#include "http_connection.h"
5a58787efeb02a1c3f06569d019ad81fd2efa06end#include "http_config.h"
5a58787efeb02a1c3f06569d019ad81fd2efa06end#include "http_core.h"
5a58787efeb02a1c3f06569d019ad81fd2efa06end#include "http_log.h"
5a58787efeb02a1c3f06569d019ad81fd2efa06end#include "http_request.h"
5a58787efeb02a1c3f06569d019ad81fd2efa06end#include "util_ebcdic.h"
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive#include "apr_strings.h"
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen#include "apr_portable.h"
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen#include "apr_uuid.h"
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen#include "mod_proxy.h"
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive#if APR_HAVE_SYS_SYSLIMITS_H
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive#include <sys/syslimits.h>
06ba4a61654b3763ad65f52283832ebf058fdf1cslive#endif
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive#if APR_HAVE_LINUX_LIMITS_H
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive#include <linux/limits.h>
ffb01336be79c64046b636e59fa8ddca8ec029edsf#endif
ffb01336be79c64046b636e59fa8ddca8ec029edsf#if APR_HAVE_FCNTL_H
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive#include <fcntl.h>
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive#endif
5a58787efeb02a1c3f06569d019ad81fd2efa06end#if APR_HAVE_UNISTD_H
5a58787efeb02a1c3f06569d019ad81fd2efa06end#include <unistd.h>
5a58787efeb02a1c3f06569d019ad81fd2efa06end#endif
117c1f888a14e73cdd821dc6c23eb0411144a41cnd
deeee6bb6fd94c0ba5f3730b58abd9d299c89ccdndmodule AP_MODULE_DECLARE_DATA firehose_module;
4db28ee269aa06f7c6232e11cd01f58c3349af23noodl
117c1f888a14e73cdd821dc6c23eb0411144a41cndtypedef enum proxy_enum
117c1f888a14e73cdd821dc6c23eb0411144a41cnd{
4a31db3c3a0202003c1b9f87affa7cc143e120e5sf FIREHOSE_PROXY, FIREHOSE_NORMAL
117c1f888a14e73cdd821dc6c23eb0411144a41cnd} proxy_enum;
ffb01336be79c64046b636e59fa8ddca8ec029edsf
117c1f888a14e73cdd821dc6c23eb0411144a41cndtypedef enum request_enum
117c1f888a14e73cdd821dc6c23eb0411144a41cnd{
117c1f888a14e73cdd821dc6c23eb0411144a41cnd FIREHOSE_CONNECTION, FIREHOSE_REQUEST
2bc7f1cf720973a67f8ff7a8d523e40569ae5b6cnd} request_enum;
117c1f888a14e73cdd821dc6c23eb0411144a41cnd
117c1f888a14e73cdd821dc6c23eb0411144a41cndtypedef enum direction_enum
117c1f888a14e73cdd821dc6c23eb0411144a41cnd{
117c1f888a14e73cdd821dc6c23eb0411144a41cnd FIREHOSE_IN = '<', FIREHOSE_OUT = '>'
4db28ee269aa06f7c6232e11cd01f58c3349af23noodl} direction_enum;
5a58787efeb02a1c3f06569d019ad81fd2efa06end
5a58787efeb02a1c3f06569d019ad81fd2efa06endtypedef struct firehose_conn_t
5a58787efeb02a1c3f06569d019ad81fd2efa06end{
5a58787efeb02a1c3f06569d019ad81fd2efa06end const char *filename;
5a58787efeb02a1c3f06569d019ad81fd2efa06end apr_file_t *file;
5a58787efeb02a1c3f06569d019ad81fd2efa06end proxy_enum proxy;
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen direction_enum direction;
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd request_enum request;
30471a4650391f57975f60bbb6e4a90be7b284bfhumbedooh int suppress;
5a58787efeb02a1c3f06569d019ad81fd2efa06end apr_int32_t nonblock;
5a58787efeb02a1c3f06569d019ad81fd2efa06end} firehose_conn_t;
5a58787efeb02a1c3f06569d019ad81fd2efa06end
06ba4a61654b3763ad65f52283832ebf058fdf1cslivetypedef struct firehose_conf_t
06ba4a61654b3763ad65f52283832ebf058fdf1cslive{
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen apr_array_header_t *firehoses;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive} firehose_conf_t;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive
06ba4a61654b3763ad65f52283832ebf058fdf1cslivetypedef struct firehose_ctx_t
06ba4a61654b3763ad65f52283832ebf058fdf1cslive{
06ba4a61654b3763ad65f52283832ebf058fdf1cslive firehose_conf_t *conf;
97a9a944b5887e91042b019776c41d5dd74557aferikabele firehose_conn_t *conn;
97a9a944b5887e91042b019776c41d5dd74557aferikabele apr_bucket_brigade *bb;
97a9a944b5887e91042b019776c41d5dd74557aferikabele apr_bucket_brigade *tmp;
ffb01336be79c64046b636e59fa8ddca8ec029edsf char uuid[APR_UUID_FORMATTED_LENGTH + 1];
06ba4a61654b3763ad65f52283832ebf058fdf1cslive apr_uint64_t count;
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen int direction;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive conn_rec *c;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive request_rec *r;
ffb01336be79c64046b636e59fa8ddca8ec029edsf ap_filter_t *f;
ffb01336be79c64046b636e59fa8ddca8ec029edsf} firehose_ctx_t;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive
06ba4a61654b3763ad65f52283832ebf058fdf1cslive#define HEADER_LEN (sizeof(apr_uint64_t)*6 + APR_UUID_FORMATTED_LENGTH + 7)
06ba4a61654b3763ad65f52283832ebf058fdf1cslive#define BODY_LEN (PIPE_BUF - HEADER_LEN - 2)
06ba4a61654b3763ad65f52283832ebf058fdf1cslive#define HEADER_FMT "%" APR_UINT64_T_HEX_FMT " %" APR_UINT64_T_HEX_FMT " %c %s %" APR_UINT64_T_HEX_FMT CRLF
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2ndstatic apr_status_t filter_output_cleanup(void *dummy)
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen{
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen ap_filter_t *f = (ap_filter_t *) dummy;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive ap_remove_output_filter(f);
9b6a3a558cc90ffdaa0b50bd02546ffec424ded7slive return APR_SUCCESS;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive}
4a31db3c3a0202003c1b9f87affa7cc143e120e5sf
06ba4a61654b3763ad65f52283832ebf058fdf1cslivestatic apr_status_t filter_input_cleanup(void *dummy)
06ba4a61654b3763ad65f52283832ebf058fdf1cslive{
709e3a21ba73b8433462959cd56c773454b34441trawick ap_filter_t *f = (ap_filter_t *) dummy;
709e3a21ba73b8433462959cd56c773454b34441trawick ap_remove_input_filter(f);
709e3a21ba73b8433462959cd56c773454b34441trawick return APR_SUCCESS;
709e3a21ba73b8433462959cd56c773454b34441trawick}
709e3a21ba73b8433462959cd56c773454b34441trawick
709e3a21ba73b8433462959cd56c773454b34441trawick/**
709e3a21ba73b8433462959cd56c773454b34441trawick * Add the terminating empty fragment to indicate end-of-connection.
5a58787efeb02a1c3f06569d019ad81fd2efa06end */
5a58787efeb02a1c3f06569d019ad81fd2efa06endstatic apr_status_t pumpit_cleanup(void *dummy)
5a58787efeb02a1c3f06569d019ad81fd2efa06end{
5a58787efeb02a1c3f06569d019ad81fd2efa06end firehose_ctx_t *ctx = (firehose_ctx_t *) dummy;
5a58787efeb02a1c3f06569d019ad81fd2efa06end apr_status_t rv;
9fc1345bb54ea7f68c2e59ff3a618c1237a30918yoshiki apr_size_t hdr_len;
5a58787efeb02a1c3f06569d019ad81fd2efa06end char header[HEADER_LEN + 1];
5a58787efeb02a1c3f06569d019ad81fd2efa06end apr_size_t bytes;
5a58787efeb02a1c3f06569d019ad81fd2efa06end
5a58787efeb02a1c3f06569d019ad81fd2efa06end if (!ctx->count) {
5a58787efeb02a1c3f06569d019ad81fd2efa06end return APR_SUCCESS;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive }
06ba4a61654b3763ad65f52283832ebf058fdf1cslive
06ba4a61654b3763ad65f52283832ebf058fdf1cslive hdr_len = apr_snprintf(header, sizeof(header), HEADER_FMT,
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd (apr_uint64_t) 0, (apr_uint64_t) apr_time_now(), ctx->direction,
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd ctx->uuid, ctx->count);
06ba4a61654b3763ad65f52283832ebf058fdf1cslive ap_xlate_proto_to_ascii(header, hdr_len);
06ba4a61654b3763ad65f52283832ebf058fdf1cslive
06ba4a61654b3763ad65f52283832ebf058fdf1cslive rv = apr_file_write_full(ctx->conn->file, header, hdr_len, &bytes);
f989aee1278b24f2b6e3a8e3b0935b590349de81jorton if (APR_SUCCESS != rv) {
55478342807b8fbc71ba2af7444e3a06fad7ebf1rbowen if (ctx->conn->suppress) {
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd /* ignore the error */
5a58787efeb02a1c3f06569d019ad81fd2efa06end }
5a58787efeb02a1c3f06569d019ad81fd2efa06end else if (ctx->r) {
5a58787efeb02a1c3f06569d019ad81fd2efa06end ap_log_rerror(
5a58787efeb02a1c3f06569d019ad81fd2efa06end APLOG_MARK,
5a58787efeb02a1c3f06569d019ad81fd2efa06end APLOG_WARNING,
5fe85d8662e07bb4e9dbe9c2d0274e90a3058135rbowen rv,
5a58787efeb02a1c3f06569d019ad81fd2efa06end ctx->r,
5a58787efeb02a1c3f06569d019ad81fd2efa06end "mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
5a58787efeb02a1c3f06569d019ad81fd2efa06end (apr_uint64_t)(hdr_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
5a58787efeb02a1c3f06569d019ad81fd2efa06end }
5a58787efeb02a1c3f06569d019ad81fd2efa06end else {
5a58787efeb02a1c3f06569d019ad81fd2efa06end ap_log_cerror(
5a58787efeb02a1c3f06569d019ad81fd2efa06end APLOG_MARK,
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd APLOG_WARNING,
5a58787efeb02a1c3f06569d019ad81fd2efa06end rv,
5a58787efeb02a1c3f06569d019ad81fd2efa06end ctx->c,
5a58787efeb02a1c3f06569d019ad81fd2efa06end "mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
5a58787efeb02a1c3f06569d019ad81fd2efa06end (apr_uint64_t)(hdr_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
06ba4a61654b3763ad65f52283832ebf058fdf1cslive }
06ba4a61654b3763ad65f52283832ebf058fdf1cslive ctx->conn->suppress = 1;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive }
a63f0ab647ad2ab72efc9bea7a66e24e9ebc5cc2nd else {
2bfb6b5514fae5aff3a3a56a15f3d5a545395c41igalic ctx->conn->suppress = 0;
2bfb6b5514fae5aff3a3a56a15f3d5a545395c41igalic }
2bfb6b5514fae5aff3a3a56a15f3d5a545395c41igalic
2bfb6b5514fae5aff3a3a56a15f3d5a545395c41igalic ctx->count = 0;
2bfb6b5514fae5aff3a3a56a15f3d5a545395c41igalic
06ba4a61654b3763ad65f52283832ebf058fdf1cslive return APR_SUCCESS;
06ba4a61654b3763ad65f52283832ebf058fdf1cslive}
06ba4a61654b3763ad65f52283832ebf058fdf1cslive
06ba4a61654b3763ad65f52283832ebf058fdf1cslive/*
5a58787efeb02a1c3f06569d019ad81fd2efa06end * Pump the bucket contents to the pipe.
5a58787efeb02a1c3f06569d019ad81fd2efa06end *
5a58787efeb02a1c3f06569d019ad81fd2efa06end * Writes smaller than PIPE_BUF are guaranteed to be atomic when written to
5a58787efeb02a1c3f06569d019ad81fd2efa06end * pipes. As a result, we break the buckets into packets smaller than PIPE_BUF and
5a58787efeb02a1c3f06569d019ad81fd2efa06end * send each one in turn.
5fe85d8662e07bb4e9dbe9c2d0274e90a3058135rbowen *
5a58787efeb02a1c3f06569d019ad81fd2efa06end * Each packet is marked with the UUID of the connection so that the process that
5a58787efeb02a1c3f06569d019ad81fd2efa06end * reassembles the packets can put the right packets in the right order.
5a58787efeb02a1c3f06569d019ad81fd2efa06end *
3b3b7fc78d1f5bfc2769903375050048ff41ff26nd * Each packet is numbered with an incrementing counter. If a packet cannot be
ad74a0524a06bfe11b7de9e3b4ce7233ab3bd3f7nd * written we drop the packet on the floor, and the counter will enable dropped
ad74a0524a06bfe11b7de9e3b4ce7233ab3bd3f7nd * packets to be detected.
e1e8390280254f7f0580d701e583f670643d4f3fnilgun */
f086b4b402fa9a2fefc7dda85de2a3cc1cd0a654rjungstatic apr_status_t pumpit(ap_filter_t *f, apr_bucket *b, firehose_ctx_t *ctx)
727872d18412fc021f03969b8641810d8896820bhumbedooh{
0d0ba3a410038e179b695446bb149cce6264e0abnd apr_status_t rv = APR_SUCCESS;
727872d18412fc021f03969b8641810d8896820bhumbedooh
cc7e1025de9ac63bd4db6fe7f71c158b2cf09fe4humbedooh if (!(APR_BUCKET_IS_METADATA(b))) {
0d0ba3a410038e179b695446bb149cce6264e0abnd const char *buf;
cc7e1025de9ac63bd4db6fe7f71c158b2cf09fe4humbedooh apr_size_t nbytes, offset = 0;
727872d18412fc021f03969b8641810d8896820bhumbedooh
0d0ba3a410038e179b695446bb149cce6264e0abnd rv = apr_bucket_read(b, &buf, &nbytes, APR_BLOCK_READ);
0d0ba3a410038e179b695446bb149cce6264e0abnd
0d0ba3a410038e179b695446bb149cce6264e0abnd if (rv == APR_SUCCESS) {
727872d18412fc021f03969b8641810d8896820bhumbedooh while (nbytes > 0) {
0d0ba3a410038e179b695446bb149cce6264e0abnd char header[HEADER_LEN + 1];
0d0ba3a410038e179b695446bb149cce6264e0abnd apr_size_t hdr_len;
0d0ba3a410038e179b695446bb149cce6264e0abnd apr_size_t body_len = nbytes < BODY_LEN ? nbytes : BODY_LEN;
727872d18412fc021f03969b8641810d8896820bhumbedooh apr_size_t bytes;
0d0ba3a410038e179b695446bb149cce6264e0abnd struct iovec vec[3];
0d0ba3a410038e179b695446bb149cce6264e0abnd
30471a4650391f57975f60bbb6e4a90be7b284bfhumbedooh /*
5effc8b39fae5cd169d17f342bfc265705840014rbowen * Insert the chunk header, specifying the number of bytes in
d229f940abfb2490dee17979e9a5ff31b7012eb5rbowen * the chunk.
0d0ba3a410038e179b695446bb149cce6264e0abnd */
7fec19672a491661b2fe4b29f685bc7f4efa64d4nd hdr_len = apr_snprintf(header, sizeof(header), HEADER_FMT,
7fec19672a491661b2fe4b29f685bc7f4efa64d4nd (apr_uint64_t) body_len, (apr_uint64_t) apr_time_now(),
7fec19672a491661b2fe4b29f685bc7f4efa64d4nd ctx->direction, ctx->uuid, ctx->count);
5a58787efeb02a1c3f06569d019ad81fd2efa06end ap_xlate_proto_to_ascii(header, hdr_len);
vec[0].iov_base = header;
vec[0].iov_len = hdr_len;
vec[1].iov_base = (void *) (buf + offset);
vec[1].iov_len = body_len;
vec[2].iov_base = CRLF;
vec[2].iov_len = 2;
rv = apr_file_writev_full(ctx->conn->file, vec, 3, &bytes);
if (APR_SUCCESS != rv) {
if (ctx->conn->suppress) {
/* ignore the error */
}
else if (ctx->r) {
ap_log_rerror(
APLOG_MARK,
APLOG_WARNING,
rv,
ctx->r,
"mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
(apr_uint64_t)(vec[0].iov_len + vec[1].iov_len + vec[2].iov_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
}
else {
ap_log_cerror(
APLOG_MARK,
APLOG_WARNING,
rv,
ctx->c,
"mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
(apr_uint64_t)(vec[0].iov_len + vec[1].iov_len + vec[2].iov_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
}
ctx->conn->suppress = 1;
rv = APR_SUCCESS;
}
else {
ctx->conn->suppress = 0;
}
ctx->count++;
nbytes -= vec[1].iov_len;
offset += vec[1].iov_len;
}
}
}
return rv;
}
static apr_status_t firehose_input_filter(ap_filter_t *f,
apr_bucket_brigade *bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes)
{
apr_bucket *b;
apr_status_t rv;
firehose_ctx_t *ctx = f->ctx;
/* just get out of the way of things we don't want. */
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
/* if an error was received, bail out now. If the error is
* EAGAIN and we have not yet seen an EOS, we will definitely
* be called again, at which point we will send our buffered
* data. Instead of sending EAGAIN, some filters return an
* empty brigade instead when data is not yet available. In
* this case, pass through the APR_SUCCESS and emulate the
* underlying filter.
*/
if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
return rv;
}
for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b
= APR_BUCKET_NEXT(b)) {
rv = pumpit(f, b, ctx);
if (APR_SUCCESS != rv) {
return rv;
}
}
return APR_SUCCESS;
}
static apr_status_t firehose_output_filter(ap_filter_t *f,
apr_bucket_brigade *bb)
{
apr_bucket *b;
apr_status_t rv = APR_SUCCESS;
firehose_ctx_t *ctx = f->ctx;
while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
b = APR_BRIGADE_FIRST(bb);
rv = pumpit(f, b, ctx);
if (APR_SUCCESS != rv) {
return rv;
}
/* pass each bucket down */
APR_BUCKET_REMOVE(b);
APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
/*
* If we ever see an EOS, make sure to FLUSH.
*/
if (APR_BUCKET_IS_EOS(b)) {
apr_bucket *flush = apr_bucket_flush_create(f->c->bucket_alloc);
APR_BUCKET_INSERT_BEFORE(b, flush);
}
rv = ap_pass_brigade(f->next, ctx->bb);
}
return rv;
}
/**
* Create a firehose for each main request.
*/
static int firehose_create_request(request_rec *r)
{
firehose_ctx_t *ctx;
apr_uuid_t uuid;
int set = 0;
ap_filter_t *f;
if (r->main) {
return DECLINED;
}
f = r->connection->input_filters;
while (f) {
if (f->frec->filter_func.in_func == &firehose_input_filter) {
ctx = (firehose_ctx_t *) f->ctx;
if (ctx->conn->request == FIREHOSE_REQUEST) {
pumpit_cleanup(ctx);
if (!set) {
apr_uuid_get(&uuid);
set = 1;
}
apr_uuid_format(ctx->uuid, &uuid);
}
}
f = f->next;
}
f = r->connection->output_filters;
while (f) {
if (f->frec->filter_func.out_func == &firehose_output_filter) {
ctx = (firehose_ctx_t *) f->ctx;
if (ctx->conn->request == FIREHOSE_REQUEST) {
pumpit_cleanup(ctx);
if (!set) {
apr_uuid_get(&uuid);
set = 1;
}
apr_uuid_format(ctx->uuid, &uuid);
}
}
f = f->next;
}
return OK;
}
/* Ideas for extension:
*
* TODO: An idea for configuration. Let the filename directives be per-directory,
* with a global hashtable of filename to filehandle mappings. As each directive
* is parsed, a file is opened at server start. By default, all input is buffered
* until the header_parser hook, at which point we check if we should be buffering
* at all. If not, we dump the buffer and remove the filter. If so, we start
* attempting to write the buffer to the file.
*
* TODO: Implement a buffer to allow firehose fragment writes to back up to some
* threshold before packets are dropped. Flush the buffer on cleanup, waiting a
* suitable amount of time for the downstream to catch up.
*
* TODO: For the request firehose, have an option to set aside request buckets
* until we decide whether we're going to record this request or not. Allows for
* targeted firehose by URL space.
*
* TODO: Potentially decide on firehose sending based on a variable in the notes
* table or subprocess_env. Use standard httpd SetEnvIf and friends to decide on
* whether to include the request or not. Using this, we can react to the value
* of a flagpole. Run this check in the header_parser hook.
*/
static int firehose_pre_conn(conn_rec *c, void *csd)
{
firehose_conf_t *conf;
firehose_ctx_t *ctx;
apr_uuid_t uuid;
int i;
firehose_conn_t *conn;
conf = ap_get_module_config(c->base_server->module_config,
&firehose_module);
if (conf->firehoses->nelts) {
apr_uuid_get(&uuid);
}
conn = (firehose_conn_t *) conf->firehoses->elts;
for (i = 0; i < conf->firehoses->nelts; i++) {
if (!conn->file || (conn->proxy == FIREHOSE_NORMAL
&& !c->sbh) || (conn->proxy == FIREHOSE_PROXY && c->sbh)) {
conn++;
continue;
}
ctx = apr_pcalloc(c->pool, sizeof(firehose_ctx_t));
apr_uuid_format(ctx->uuid, &uuid);
ctx->conf = conf;
ctx->conn = conn;
ctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
ctx->c = c;
apr_pool_cleanup_register(c->pool, ctx, pumpit_cleanup, pumpit_cleanup);
if (conn->direction == FIREHOSE_IN) {
ctx->direction = conn->proxy == FIREHOSE_PROXY ? '>' : '<';
ctx->f = ap_add_input_filter("FIREHOSE_IN", ctx, NULL, c);
apr_pool_cleanup_register(c->pool, ctx->f, filter_input_cleanup,
filter_input_cleanup);
}
if (conn->direction == FIREHOSE_OUT) {
ctx->direction = conn->proxy == FIREHOSE_PROXY ? '<' : '>';
ctx->f = ap_add_output_filter("FIREHOSE_OUT", ctx, NULL, c);
apr_pool_cleanup_register(c->pool, ctx->f, filter_output_cleanup,
filter_output_cleanup);
}
conn++;
}
return OK;
}
static int firehose_open_logs(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
firehose_conf_t *conf;
apr_status_t rv;
void *data;
int i;
firehose_conn_t *conn;
/* make sure we only open the files on the second pass for config */
apr_pool_userdata_get(&data, "mod_firehose", s->process->pool);
if (!data) {
apr_pool_userdata_set((const void *) 1, "mod_firehose",
apr_pool_cleanup_null, s->process->pool);
return OK;
}
while (s) {
conf = ap_get_module_config(s->module_config,
&firehose_module);
conn = (firehose_conn_t *) conf->firehoses->elts;
for (i = 0; i < conf->firehoses->nelts; i++) {
if (APR_SUCCESS != (rv = apr_file_open(&conn->file, conn->filename,
APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_APPEND
| conn->nonblock, APR_OS_DEFAULT, plog))) {
ap_log_error(APLOG_MARK,
APLOG_WARNING,
rv, s, "mod_firehose: could not open '%s' for write, disabling firehose %s%s %s filter",
conn->filename, conn->proxy == FIREHOSE_PROXY ? "proxy " : "",
conn->request == FIREHOSE_REQUEST ? " request" : "connection",
conn->direction == FIREHOSE_IN ? "input" : "output");
}
conn++;
}
s = s->next;
}
return OK;
}
static void firehose_register_hooks(apr_pool_t *p)
{
/*
* We know that SSL is CONNECTION + 5
*/
ap_register_output_filter("FIREHOSE_OUT", firehose_output_filter, NULL,
AP_FTYPE_CONNECTION + 3);
ap_register_input_filter("FIREHOSE_IN", firehose_input_filter, NULL,
AP_FTYPE_CONNECTION + 3);
ap_hook_open_logs(firehose_open_logs, NULL, NULL, APR_HOOK_LAST);
ap_hook_pre_connection(firehose_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_create_request(firehose_create_request, NULL, NULL,
APR_HOOK_REALLY_LAST + 1);
}
static void *firehose_create_sconfig(apr_pool_t *p, server_rec *s)
{
firehose_conf_t *ptr = apr_pcalloc(p, sizeof(firehose_conf_t));
ptr->firehoses = apr_array_make(p, 2, sizeof(firehose_conn_t));
return ptr;
}
static void *firehose_merge_sconfig(apr_pool_t *p, void *basev,
void *overridesv)
{
firehose_conf_t *cconf = apr_pcalloc(p, sizeof(firehose_conf_t));
firehose_conf_t *base = (firehose_conf_t *) basev;
firehose_conf_t *overrides = (firehose_conf_t *) overridesv;
cconf->firehoses = apr_array_append(p, overrides->firehoses,
base->firehoses);
return cconf;
}
static const char *firehose_enable_connection(cmd_parms *cmd, const char *arg1,
const char *arg2, proxy_enum proxy, direction_enum direction,
request_enum request)
{
const char *name = arg2 ? arg2 : arg1;
firehose_conn_t *firehose;
firehose_conf_t
*ptr =
(firehose_conf_t *) ap_get_module_config(cmd->server->module_config,
&firehose_module);
firehose = apr_array_push(ptr->firehoses);
firehose->filename = name;
firehose->proxy = proxy;
firehose->direction = direction;
firehose->request = request;
if (arg2) {
if (!strcmp(arg1, "nonblock")) {
#ifdef APR_FOPEN_NONBLOCK
firehose->nonblock = APR_FOPEN_NONBLOCK;
#else
return "The parameter 'nonblock' is not supported by APR on this platform";
#endif
}
else if (!strcmp(arg1, "block")) {
firehose->nonblock = 0;
}
else {
return apr_psprintf(cmd->pool,
"The parameter '%s' should be 'block' or 'nonblock'", arg1);
}
}
else {
firehose->nonblock = 0;
}
return NULL;
}
static const char *firehose_enable_connection_input(cmd_parms *cmd,
void *dummy, const char *arg1, const char *arg2)
{
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
| NOT_IN_LIMIT);
if (err != NULL) {
return err;
}
return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
FIREHOSE_IN, FIREHOSE_CONNECTION);
}
static const char *firehose_enable_connection_output(cmd_parms *cmd,
void *dummy, const char *arg1, const char *arg2)
{
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
| NOT_IN_LIMIT);
if (err != NULL) {
return err;
}
return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
FIREHOSE_OUT, FIREHOSE_CONNECTION);
}
static const char *firehose_enable_request_input(cmd_parms *cmd, void *dummy,
const char *arg1, const char *arg2)
{
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
| NOT_IN_LIMIT);
if (err != NULL) {
return err;
}
return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
FIREHOSE_IN, FIREHOSE_REQUEST);
}
static const char *firehose_enable_request_output(cmd_parms *cmd, void *dummy,
const char *arg1, const char *arg2)
{
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
| NOT_IN_LIMIT);
if (err != NULL) {
return err;
}
return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
FIREHOSE_OUT, FIREHOSE_REQUEST);
}
static const char *firehose_enable_proxy_connection_input(cmd_parms *cmd,
void *dummy, const char *arg1, const char *arg2)
{
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
| NOT_IN_LIMIT);
if (err != NULL) {
return err;
}
return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_PROXY,
FIREHOSE_IN, FIREHOSE_CONNECTION);
}
static const char *firehose_enable_proxy_connection_output(cmd_parms *cmd,
void *dummy, const char *arg1, const char *arg2)
{
const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
| NOT_IN_LIMIT);
if (err != NULL) {
return err;
}
return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_PROXY,
FIREHOSE_OUT, FIREHOSE_CONNECTION);
}
static const command_rec firehose_cmds[] =
{
AP_INIT_TAKE12("FirehoseConnectionInput", firehose_enable_connection_input, NULL,
RSRC_CONF, "Enable firehose on connection input data written to the given file/pipe"),
AP_INIT_TAKE12("FirehoseConnectionOutput", firehose_enable_connection_output, NULL,
RSRC_CONF, "Enable firehose on connection output data written to the given file/pipe"),
AP_INIT_TAKE12("FirehoseRequestInput", firehose_enable_request_input, NULL,
RSRC_CONF, "Enable firehose on request input data written to the given file/pipe"),
AP_INIT_TAKE12("FirehoseRequestOutput", firehose_enable_request_output, NULL,
RSRC_CONF, "Enable firehose on request output data written to the given file/pipe"),
AP_INIT_TAKE12("FirehoseProxyConnectionInput", firehose_enable_proxy_connection_input, NULL,
RSRC_CONF, "Enable firehose on proxied connection input data written to the given file/pipe"),
AP_INIT_TAKE12("FirehoseProxyConnectionOutput", firehose_enable_proxy_connection_output, NULL,
RSRC_CONF, "Enable firehose on proxied connection output data written to the given file/pipe"),
{ NULL }
};
AP_DECLARE_MODULE(firehose) =
{
STANDARD20_MODULE_STUFF,
NULL,
NULL,
firehose_create_sconfig,
firehose_merge_sconfig,
firehose_cmds,
firehose_register_hooks
};