]> granicus.if.org Git - apache/blob - modules/debugging/mod_firehose.c
mod_ssl: Ensure that the SSL close notify alert is flushed to the client.
[apache] / modules / debugging / mod_firehose.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * Originally written @ Covalent by Jim Jagielski
19  * Modified to support writing to non blocking pipes @ BBC by Graham Leggett
20  * Modifications (C) 2011 British Broadcasting Corporation
21  */
22
23 /*
24  * mod_firehose.c:
25  *  A request and response sniffer for Apache v2.x. It logs
26  *  all filter data right before and after it goes out on the
27  *  wire (BUT right before SSL encoded or after SSL decoded).
28  *  It can produce a *huge* amount of data.
29  */
30
31 #include "httpd.h"
32 #include "http_connection.h"
33 #include "http_config.h"
34 #include "http_core.h"
35 #include "http_log.h"
36 #include "http_request.h"
37 #include "util_ebcdic.h"
38 #include "apr_strings.h"
39 #include "apr_portable.h"
40 #include "apr_uuid.h"
41 #include "mod_proxy.h"
42
43 #if APR_HAVE_SYS_SYSLIMITS_H
44 #include <sys/syslimits.h>
45 #endif
46 #if APR_HAVE_LINUX_LIMITS_H
47 #include <linux/limits.h>
48 #endif
49 #if APR_HAVE_FCNTL_H
50 #include <fcntl.h>
51 #endif
52 #if APR_HAVE_UNISTD_H
53 #include <unistd.h>
54 #endif
55
56 module AP_MODULE_DECLARE_DATA firehose_module;
57
58 typedef enum proxy_enum
59 {
60     FIREHOSE_PROXY, FIREHOSE_NORMAL
61 } proxy_enum;
62
63 typedef enum request_enum
64 {
65     FIREHOSE_CONNECTION, FIREHOSE_REQUEST
66 } request_enum;
67
68 typedef enum direction_enum
69 {
70     FIREHOSE_IN = '<', FIREHOSE_OUT = '>'
71 } direction_enum;
72
73 typedef struct firehose_conn_t
74 {
75     const char *filename;
76     apr_file_t *file;
77     proxy_enum proxy;
78     direction_enum direction;
79     request_enum request;
80     int suppress;
81     apr_int32_t nonblock;
82 } firehose_conn_t;
83
84 typedef struct firehose_conf_t
85 {
86     apr_array_header_t *firehoses;
87 } firehose_conf_t;
88
89 typedef struct firehose_ctx_t
90 {
91     firehose_conf_t *conf;
92     firehose_conn_t *conn;
93     apr_bucket_brigade *bb;
94     apr_bucket_brigade *tmp;
95     char uuid[APR_UUID_FORMATTED_LENGTH + 1];
96     apr_uint64_t count;
97     int direction;
98     conn_rec *c;
99     request_rec *r;
100     ap_filter_t *f;
101 } firehose_ctx_t;
102
103 #define HEADER_LEN (sizeof(apr_uint64_t)*6 + APR_UUID_FORMATTED_LENGTH + 7)
104 #define BODY_LEN (PIPE_BUF - HEADER_LEN - 2)
105 #define HEADER_FMT "%" APR_UINT64_T_HEX_FMT " %" APR_UINT64_T_HEX_FMT " %c %s %" APR_UINT64_T_HEX_FMT CRLF
106
107 static apr_status_t filter_output_cleanup(void *dummy)
108 {
109     ap_filter_t *f = (ap_filter_t *) dummy;
110     ap_remove_output_filter(f);
111     return APR_SUCCESS;
112 }
113
114 static apr_status_t filter_input_cleanup(void *dummy)
115 {
116     ap_filter_t *f = (ap_filter_t *) dummy;
117     ap_remove_input_filter(f);
118     return APR_SUCCESS;
119 }
120
121 /**
122  * Add the terminating empty fragment to indicate end-of-connection.
123  */
124 static apr_status_t pumpit_cleanup(void *dummy)
125 {
126     firehose_ctx_t *ctx = (firehose_ctx_t *) dummy;
127     apr_status_t rv;
128     apr_size_t hdr_len;
129     char header[HEADER_LEN + 1];
130
131     if (!ctx->count) {
132         return APR_SUCCESS;
133     }
134
135     hdr_len = apr_snprintf(header, sizeof(header), HEADER_FMT,
136             (apr_uint64_t) 0, (apr_uint64_t) apr_time_now(), ctx->direction,
137             ctx->uuid, ctx->count);
138     ap_xlate_proto_to_ascii(header, hdr_len);
139
140     rv = apr_file_write_full(ctx->conn->file, header, hdr_len, NULL);
141     if (APR_SUCCESS != rv) {
142         if (ctx->conn->suppress) {
143             /* ignore the error */
144         }
145         else if (ctx->r) {
146             ap_log_rerror(
147                     APLOG_MARK,
148                     APLOG_WARNING,
149                     rv,
150                     ctx->r,
151                     "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)",
152                     (apr_uint64_t)(hdr_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
153         }
154         else {
155             ap_log_cerror(
156                     APLOG_MARK,
157                     APLOG_WARNING,
158                     rv,
159                     ctx->c,
160                     "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)",
161                     (apr_uint64_t)(hdr_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
162         }
163         ctx->conn->suppress = 1;
164     }
165     else {
166         ctx->conn->suppress = 0;
167     }
168
169     ctx->count = 0;
170
171     return APR_SUCCESS;
172 }
173
174 /*
175  * Pump the bucket contents to the pipe.
176  *
177  * Writes smaller than PIPE_BUF are guaranteed to be atomic when written to
178  * pipes. As a result, we break the buckets into packets smaller than PIPE_BUF and
179  * send each one in turn.
180  *
181  * Each packet is marked with the UUID of the connection so that the process that
182  * reassembles the packets can put the right packets in the right order.
183  *
184  * Each packet is numbered with an incrementing counter. If a packet cannot be
185  * written we drop the packet on the floor, and the counter will enable dropped
186  * packets to be detected.
187  */
188 static apr_status_t pumpit(ap_filter_t *f, apr_bucket *b, firehose_ctx_t *ctx)
189 {
190     apr_status_t rv = APR_SUCCESS;
191
192     if (!(APR_BUCKET_IS_METADATA(b))) {
193         const char *buf;
194         apr_size_t nbytes, offset = 0;
195
196         rv = apr_bucket_read(b, &buf, &nbytes, APR_BLOCK_READ);
197
198         if (rv == APR_SUCCESS) {
199             while (nbytes > 0) {
200                 char header[HEADER_LEN + 1];
201                 apr_size_t hdr_len;
202                 apr_size_t body_len = nbytes < BODY_LEN ? nbytes : BODY_LEN;
203                 apr_size_t bytes;
204                 struct iovec vec[3];
205
206                 /*
207                  * Insert the chunk header, specifying the number of bytes in
208                  * the chunk.
209                  */
210                 hdr_len = apr_snprintf(header, sizeof(header), HEADER_FMT,
211                         (apr_uint64_t) body_len, (apr_uint64_t) apr_time_now(),
212                         ctx->direction, ctx->uuid, ctx->count);
213                 ap_xlate_proto_to_ascii(header, hdr_len);
214
215                 vec[0].iov_base = header;
216                 vec[0].iov_len = hdr_len;
217                 vec[1].iov_base = (void *) (buf + offset);
218                 vec[1].iov_len = body_len;
219                 vec[2].iov_base = CRLF;
220                 vec[2].iov_len = 2;
221
222                 rv = apr_file_writev_full(ctx->conn->file, vec, 3, &bytes);
223                 if (APR_SUCCESS != rv) {
224                     if (ctx->conn->suppress) {
225                         /* ignore the error */
226                     }
227                     else if (ctx->r) {
228                         ap_log_rerror(
229                                 APLOG_MARK,
230                                 APLOG_WARNING,
231                                 rv,
232                                 ctx->r,
233                                 "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)",
234                                 (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);
235                     }
236                     else {
237                         ap_log_cerror(
238                                 APLOG_MARK,
239                                 APLOG_WARNING,
240                                 rv,
241                                 ctx->c,
242                                 "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)",
243                                 (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);
244                     }
245                     ctx->conn->suppress = 1;
246                     rv = APR_SUCCESS;
247                 }
248                 else {
249                     ctx->conn->suppress = 0;
250                 }
251
252                 ctx->count++;
253                 nbytes -= vec[1].iov_len;
254                 offset += vec[1].iov_len;
255             }
256         }
257
258     }
259     return rv;
260 }
261
262 static apr_status_t firehose_input_filter(ap_filter_t *f,
263                                           apr_bucket_brigade *bb,
264                                           ap_input_mode_t mode,
265                                           apr_read_type_e block,
266                                           apr_off_t readbytes)
267 {
268     apr_bucket *b;
269     apr_status_t rv;
270     firehose_ctx_t *ctx = f->ctx;
271
272     /* just get out of the way of things we don't want. */
273     if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
274         return ap_get_brigade(f->next, bb, mode, block, readbytes);
275     }
276
277     rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
278
279     /* if an error was received, bail out now. If the error is
280      * EAGAIN and we have not yet seen an EOS, we will definitely
281      * be called again, at which point we will send our buffered
282      * data. Instead of sending EAGAIN, some filters return an
283      * empty brigade instead when data is not yet available. In
284      * this case, pass through the APR_SUCCESS and emulate the
285      * underlying filter.
286      */
287     if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
288         return rv;
289     }
290
291     for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b
292             = APR_BUCKET_NEXT(b)) {
293         rv = pumpit(f, b, ctx);
294         if (APR_SUCCESS != rv) {
295             return rv;
296         }
297     }
298
299     return APR_SUCCESS;
300 }
301
302 static apr_status_t firehose_output_filter(ap_filter_t *f,
303                                            apr_bucket_brigade *bb)
304 {
305     apr_bucket *b;
306     apr_status_t rv = APR_SUCCESS;
307     firehose_ctx_t *ctx = f->ctx;
308
309     while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
310
311         b = APR_BRIGADE_FIRST(bb);
312
313         rv = pumpit(f, b, ctx);
314         if (APR_SUCCESS != rv) {
315             return rv;
316         }
317
318         /* pass each bucket down */
319         APR_BUCKET_REMOVE(b);
320         APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
321
322         /*
323          * If we ever see an EOS, make sure to FLUSH.
324          */
325         if (APR_BUCKET_IS_EOS(b)) {
326             apr_bucket *flush = apr_bucket_flush_create(f->c->bucket_alloc);
327             APR_BUCKET_INSERT_BEFORE(b, flush);
328         }
329
330         rv = ap_pass_brigade(f->next, ctx->bb);
331
332     }
333
334     return rv;
335 }
336
337 /**
338  * Create a firehose for each main request.
339  */
340 static int firehose_create_request(request_rec *r)
341 {
342     firehose_ctx_t *ctx;
343     apr_uuid_t uuid;
344     int set = 0;
345     ap_filter_t *f;
346
347     if (r->main) {
348         return DECLINED;
349     }
350
351     f = r->connection->input_filters;
352     while (f) {
353         if (f->frec->filter_func.in_func == &firehose_input_filter) {
354             ctx = (firehose_ctx_t *) f->ctx;
355             if (ctx->conn->request == FIREHOSE_REQUEST) {
356                 pumpit_cleanup(ctx);
357                 if (!set) {
358                     apr_uuid_get(&uuid);
359                     set = 1;
360                 }
361                 apr_uuid_format(ctx->uuid, &uuid);
362             }
363         }
364         f = f->next;
365     }
366
367     f = r->connection->output_filters;
368     while (f) {
369         if (f->frec->filter_func.out_func == &firehose_output_filter) {
370             ctx = (firehose_ctx_t *) f->ctx;
371             if (ctx->conn->request == FIREHOSE_REQUEST) {
372                 pumpit_cleanup(ctx);
373                 if (!set) {
374                     apr_uuid_get(&uuid);
375                     set = 1;
376                 }
377                 apr_uuid_format(ctx->uuid, &uuid);
378             }
379         }
380         f = f->next;
381     }
382
383     return OK;
384 }
385
386 /* Ideas for extension:
387  *
388  * TODO: An idea for configuration. Let the filename directives be per-directory,
389  * with a global hashtable of filename to filehandle mappings. As each directive
390  * is parsed, a file is opened at server start. By default, all input is buffered
391  * until the header_parser hook, at which point we check if we should be buffering
392  * at all. If not, we dump the buffer and remove the filter. If so, we start
393  * attempting to write the buffer to the file.
394  *
395  * TODO: Implement a buffer to allow firehose fragment writes to back up to some
396  * threshold before packets are dropped. Flush the buffer on cleanup, waiting a
397  * suitable amount of time for the downstream to catch up.
398  *
399  * TODO: For the request firehose, have an option to set aside request buckets
400  * until we decide whether we're going to record this request or not. Allows for
401  * targeted firehose by URL space.
402  *
403  * TODO: Potentially decide on firehose sending based on a variable in the notes
404  * table or subprocess_env. Use standard httpd SetEnvIf and friends to decide on
405  * whether to include the request or not. Using this, we can react to the value
406  * of a flagpole. Run this check in the header_parser hook.
407  */
408
409 static int firehose_pre_conn(conn_rec *c, void *csd)
410 {
411     firehose_conf_t *conf;
412     firehose_ctx_t *ctx;
413     apr_uuid_t uuid;
414     int i;
415     firehose_conn_t *conn;
416
417     conf = ap_get_module_config(c->base_server->module_config,
418             &firehose_module);
419
420     if (conf->firehoses->nelts) {
421         apr_uuid_get(&uuid);
422     }
423
424     conn = (firehose_conn_t *) conf->firehoses->elts;
425     for (i = 0; i < conf->firehoses->nelts; i++) {
426
427         if (!conn->file || (conn->proxy == FIREHOSE_NORMAL
428                 && !c->sbh) || (conn->proxy == FIREHOSE_PROXY && c->sbh)) {
429             conn++;
430             continue;
431         }
432
433         ctx = apr_pcalloc(c->pool, sizeof(firehose_ctx_t));
434         apr_uuid_format(ctx->uuid, &uuid);
435         ctx->conf = conf;
436         ctx->conn = conn;
437         ctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
438         ctx->c = c;
439         apr_pool_cleanup_register(c->pool, ctx, pumpit_cleanup, pumpit_cleanup);
440         if (conn->direction == FIREHOSE_IN) {
441             ctx->direction = conn->proxy == FIREHOSE_PROXY ? '>' : '<';
442             ctx->f = ap_add_input_filter("FIREHOSE_IN", ctx, NULL, c);
443             apr_pool_cleanup_register(c->pool, ctx->f, filter_input_cleanup,
444                     filter_input_cleanup);
445         }
446         if (conn->direction == FIREHOSE_OUT) {
447             ctx->direction = conn->proxy == FIREHOSE_PROXY ? '<' : '>';
448             ctx->f = ap_add_output_filter("FIREHOSE_OUT", ctx, NULL, c);
449             apr_pool_cleanup_register(c->pool, ctx->f, filter_output_cleanup,
450                     filter_output_cleanup);
451         }
452
453         conn++;
454     }
455
456     return OK;
457 }
458
459 static int firehose_open_logs(apr_pool_t *p, apr_pool_t *plog,
460         apr_pool_t *ptemp, server_rec *s)
461 {
462     firehose_conf_t *conf;
463     apr_status_t rv;
464     void *data;
465     int i;
466     firehose_conn_t *conn;
467
468     /* make sure we only open the files on the second pass for config */
469     apr_pool_userdata_get(&data, "mod_firehose", s->process->pool);
470     if (!data) {
471         apr_pool_userdata_set((const void *) 1, "mod_firehose",
472                 apr_pool_cleanup_null, s->process->pool);
473         return OK;
474     }
475
476     while (s) {
477
478         conf = ap_get_module_config(s->module_config,
479                 &firehose_module);
480
481         conn = (firehose_conn_t *) conf->firehoses->elts;
482         for (i = 0; i < conf->firehoses->nelts; i++) {
483             if (APR_SUCCESS != (rv = apr_file_open(&conn->file, conn->filename,
484                     APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_APPEND
485                             | conn->nonblock, APR_OS_DEFAULT, plog))) {
486                 ap_log_error(APLOG_MARK,
487                         APLOG_WARNING,
488                         rv, s, "mod_firehose: could not open '%s' for write, disabling firehose %s%s %s filter",
489                         conn->filename, conn->proxy == FIREHOSE_PROXY ? "proxy " : "",
490                         conn->request == FIREHOSE_REQUEST ? " request" : "connection",
491                         conn->direction == FIREHOSE_IN ? "input" : "output");
492             }
493             conn++;
494         }
495
496         s = s->next;
497     }
498
499     return OK;
500 }
501
502 static void firehose_register_hooks(apr_pool_t *p)
503 {
504     /*
505      * We know that SSL is CONNECTION + 5
506      */
507     ap_register_output_filter("FIREHOSE_OUT", firehose_output_filter, NULL,
508             AP_FTYPE_CONNECTION + 3);
509
510     ap_register_input_filter("FIREHOSE_IN", firehose_input_filter, NULL,
511             AP_FTYPE_CONNECTION + 3);
512
513     ap_hook_open_logs(firehose_open_logs, NULL, NULL, APR_HOOK_LAST);
514     ap_hook_pre_connection(firehose_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);
515     ap_hook_create_request(firehose_create_request, NULL, NULL,
516             APR_HOOK_REALLY_LAST + 1);
517 }
518
519 static void *firehose_create_sconfig(apr_pool_t *p, server_rec *s)
520 {
521     firehose_conf_t *ptr = apr_pcalloc(p, sizeof(firehose_conf_t));
522
523     ptr->firehoses = apr_array_make(p, 2, sizeof(firehose_conn_t));
524
525     return ptr;
526 }
527
528 static void *firehose_merge_sconfig(apr_pool_t *p, void *basev,
529         void *overridesv)
530 {
531     firehose_conf_t *cconf = apr_pcalloc(p, sizeof(firehose_conf_t));
532     firehose_conf_t *base = (firehose_conf_t *) basev;
533     firehose_conf_t *overrides = (firehose_conf_t *) overridesv;
534
535     cconf->firehoses = apr_array_append(p, overrides->firehoses,
536             base->firehoses);
537
538     return cconf;
539 }
540
541 static const char *firehose_enable_connection(cmd_parms *cmd, const char *arg1,
542         const char *arg2, proxy_enum proxy, direction_enum direction,
543         request_enum request)
544 {
545     const char *name = arg2 ? arg2 : arg1;
546
547     firehose_conn_t *firehose;
548     firehose_conf_t
549             *ptr =
550                     (firehose_conf_t *) ap_get_module_config(cmd->server->module_config,
551                             &firehose_module);
552
553     firehose = apr_array_push(ptr->firehoses);
554
555     firehose->filename = name;
556     firehose->proxy = proxy;
557     firehose->direction = direction;
558     firehose->request = request;
559
560     if (arg2) {
561         if (!strcmp(arg1, "nonblock")) {
562 #ifdef APR_FOPEN_NONBLOCK
563             firehose->nonblock = APR_FOPEN_NONBLOCK;
564 #else
565             return "The parameter 'nonblock' is not supported by APR on this platform";
566 #endif
567         }
568         else if (!strcmp(arg1, "block")) {
569             firehose->nonblock = 0;
570         }
571         else {
572             return apr_psprintf(cmd->pool,
573                     "The parameter '%s' should be 'block' or 'nonblock'", arg1);
574         }
575     }
576     else {
577         firehose->nonblock = 0;
578     }
579
580     return NULL;
581 }
582
583 static const char *firehose_enable_connection_input(cmd_parms *cmd,
584         void *dummy, const char *arg1, const char *arg2)
585 {
586
587     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
588             | NOT_IN_LIMIT);
589     if (err != NULL) {
590         return err;
591     }
592
593     return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
594             FIREHOSE_IN, FIREHOSE_CONNECTION);
595
596 }
597
598 static const char *firehose_enable_connection_output(cmd_parms *cmd,
599         void *dummy, const char *arg1, const char *arg2)
600 {
601
602     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
603             | NOT_IN_LIMIT);
604     if (err != NULL) {
605         return err;
606     }
607
608     return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
609             FIREHOSE_OUT, FIREHOSE_CONNECTION);
610
611 }
612
613 static const char *firehose_enable_request_input(cmd_parms *cmd, void *dummy,
614         const char *arg1, const char *arg2)
615 {
616
617     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
618             | NOT_IN_LIMIT);
619     if (err != NULL) {
620         return err;
621     }
622
623     return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
624             FIREHOSE_IN, FIREHOSE_REQUEST);
625
626 }
627
628 static const char *firehose_enable_request_output(cmd_parms *cmd, void *dummy,
629         const char *arg1, const char *arg2)
630 {
631
632     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
633             | NOT_IN_LIMIT);
634     if (err != NULL) {
635         return err;
636     }
637
638     return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
639             FIREHOSE_OUT, FIREHOSE_REQUEST);
640
641 }
642
643 static const char *firehose_enable_proxy_connection_input(cmd_parms *cmd,
644         void *dummy, const char *arg1, const char *arg2)
645 {
646
647     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
648             | NOT_IN_LIMIT);
649     if (err != NULL) {
650         return err;
651     }
652
653     return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_PROXY,
654             FIREHOSE_IN, FIREHOSE_CONNECTION);
655
656 }
657
658 static const char *firehose_enable_proxy_connection_output(cmd_parms *cmd,
659         void *dummy, const char *arg1, const char *arg2)
660 {
661
662     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
663             | NOT_IN_LIMIT);
664     if (err != NULL) {
665         return err;
666     }
667
668     return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_PROXY,
669             FIREHOSE_OUT, FIREHOSE_CONNECTION);
670
671 }
672
673 static const command_rec firehose_cmds[] =
674 {
675         AP_INIT_TAKE12("FirehoseConnectionInput", firehose_enable_connection_input, NULL,
676                 RSRC_CONF, "Enable firehose on connection input data written to the given file/pipe"),
677         AP_INIT_TAKE12("FirehoseConnectionOutput", firehose_enable_connection_output, NULL,
678                 RSRC_CONF, "Enable firehose on connection output data written to the given file/pipe"),
679         AP_INIT_TAKE12("FirehoseRequestInput", firehose_enable_request_input, NULL,
680                 RSRC_CONF, "Enable firehose on request input data written to the given file/pipe"),
681         AP_INIT_TAKE12("FirehoseRequestOutput", firehose_enable_request_output, NULL,
682                 RSRC_CONF, "Enable firehose on request output data written to the given file/pipe"),
683         AP_INIT_TAKE12("FirehoseProxyConnectionInput", firehose_enable_proxy_connection_input, NULL,
684                 RSRC_CONF, "Enable firehose on proxied connection input data written to the given file/pipe"),
685         AP_INIT_TAKE12("FirehoseProxyConnectionOutput", firehose_enable_proxy_connection_output, NULL,
686                 RSRC_CONF, "Enable firehose on proxied connection output data written to the given file/pipe"),
687         { NULL }
688 };
689
690 AP_DECLARE_MODULE(firehose) =
691 {
692         STANDARD20_MODULE_STUFF,
693         NULL,
694         NULL,
695         firehose_create_sconfig,
696         firehose_merge_sconfig,
697         firehose_cmds,
698         firehose_register_hooks
699 };