]> granicus.if.org Git - apache/blob - modules/filters/mod_ext_filter.c
mod_ext_filter: fix test for onfail behaviour
[apache] / modules / filters / mod_ext_filter.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  * mod_ext_filter allows Unix-style filters to filter http content.
19  */
20
21 #include "httpd.h"
22 #include "http_config.h"
23 #include "http_log.h"
24 #include "http_protocol.h"
25
26 #include "http_core.h"
27 #include "apr_buckets.h"
28 #include "util_filter.h"
29 #include "util_script.h"
30 #include "util_time.h"
31 #include "apr_strings.h"
32 #include "apr_hash.h"
33 #include "apr_lib.h"
34 #include "apr_poll.h"
35 #define APR_WANT_STRFUNC
36 #include "apr_want.h"
37
38 typedef struct ef_server_t {
39     apr_pool_t *p;
40     apr_hash_t *h;
41 } ef_server_t;
42
43 typedef struct ef_filter_t {
44     const char *name;
45     enum {INPUT_FILTER=1, OUTPUT_FILTER} mode;
46     ap_filter_type ftype;
47     const char *command;
48     const char *enable_env;
49     const char *disable_env;
50     char **args;
51     const char *intype;             /* list of IMTs we process (well, just one for now) */
52 #define INTYPE_ALL (char *)1
53     const char *outtype;            /* IMT of filtered output */
54 #define OUTTYPE_UNCHANGED (char *)1
55     int preserves_content_length;
56 } ef_filter_t;
57
58 typedef struct ef_dir_t {
59     int debug;
60     int log_stderr;
61     int onfail;
62 } ef_dir_t;
63
64 typedef struct ef_ctx_t {
65     apr_pool_t *p;
66     apr_proc_t *proc;
67     apr_procattr_t *procattr;
68     ef_dir_t *dc;
69     ef_filter_t *filter;
70     int noop;
71 #if APR_FILES_AS_SOCKETS
72     apr_pollset_t *pollset;
73 #endif
74 } ef_ctx_t;
75
76 module AP_MODULE_DECLARE_DATA ext_filter_module;
77 static const server_rec *main_server;
78
79 static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
80 static apr_status_t ef_input_filter(ap_filter_t *, apr_bucket_brigade *,
81                                     ap_input_mode_t, apr_read_type_e,
82                                     apr_off_t);
83
84 #define DBGLVL_SHOWOPTIONS         1
85 #define DBGLVL_GORY                9
86
87 #define ERRFN_USERDATA_KEY         "EXTFILTCHILDERRFN"
88
89 static void *create_ef_dir_conf(apr_pool_t *p, char *dummy)
90 {
91     ef_dir_t *dc = (ef_dir_t *)apr_pcalloc(p, sizeof(ef_dir_t));
92
93     dc->debug = -1;
94     dc->log_stderr = -1;
95     dc->onfail = -1;
96
97     return dc;
98 }
99
100 static void *create_ef_server_conf(apr_pool_t *p, server_rec *s)
101 {
102     ef_server_t *conf;
103
104     conf = (ef_server_t *)apr_pcalloc(p, sizeof(ef_server_t));
105     conf->p = p;
106     conf->h = apr_hash_make(conf->p);
107     return conf;
108 }
109
110 static void *merge_ef_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
111 {
112     ef_dir_t *a = (ef_dir_t *)apr_pcalloc (p, sizeof(ef_dir_t));
113     ef_dir_t *base = (ef_dir_t *)basev, *over = (ef_dir_t *)overridesv;
114
115     if (over->debug != -1) {        /* if admin coded something... */
116         a->debug = over->debug;
117     }
118     else {
119         a->debug = base->debug;
120     }
121
122     if (over->log_stderr != -1) {   /* if admin coded something... */
123         a->log_stderr = over->log_stderr;
124     }
125     else {
126         a->log_stderr = base->log_stderr;
127     }
128
129     if (over->onfail != -1) {   /* if admin coded something... */
130         a->onfail = over->onfail;
131     }
132     else {
133         a->onfail = base->onfail;
134     }
135
136     return a;
137 }
138
139 static const char *add_options(cmd_parms *cmd, void *in_dc,
140                                const char *arg)
141 {
142     ef_dir_t *dc = in_dc;
143
144     if (!strncasecmp(arg, "DebugLevel=", 11)) {
145         dc->debug = atoi(arg + 11);
146     }
147     else if (!strcasecmp(arg, "LogStderr")) {
148         dc->log_stderr = 1;
149     }
150     else if (!strcasecmp(arg, "NoLogStderr")) {
151         dc->log_stderr = 0;
152     }
153     else if (!strcasecmp(arg, "Onfail=remove")) {
154         dc->onfail = 1;
155     }
156     else if (!strcasecmp(arg, "Onfail=abort")) {
157         dc->onfail = 0;
158     }
159     else {
160         return apr_pstrcat(cmd->temp_pool,
161                            "Invalid ExtFilterOptions option: ",
162                            arg,
163                            NULL);
164     }
165
166     return NULL;
167 }
168
169 static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filter)
170 {
171     if (**args == '"') {
172         const char *start = *args + 1;
173         char *parms;
174         int escaping = 0;
175         apr_status_t rv;
176
177         ++*args; /* move past leading " */
178         /* find true end of args string (accounting for escaped quotes) */
179         while (**args && (**args != '"' || (**args == '"' && escaping))) {
180             if (escaping) {
181                 escaping = 0;
182             }
183             else if (**args == '\\') {
184                 escaping = 1;
185             }
186             ++*args;
187         }
188         if (**args != '"') {
189             return "Expected cmd= delimiter";
190         }
191         /* copy *just* the arg string for parsing, */
192         parms = apr_pstrndup(p, start, *args - start);
193         ++*args; /* move past trailing " */
194
195         /* parse and tokenize the args. */
196         rv = apr_tokenize_to_argv(parms, &(filter->args), p);
197         if (rv != APR_SUCCESS) {
198             return "cmd= parse error";
199         }
200     }
201     else
202     {
203         /* simple path */
204         /* Allocate space for two argv pointers and parse the args. */
205         filter->args = (char **)apr_palloc(p, 2 * sizeof(char *));
206         filter->args[0] = ap_getword_white(p, args);
207         filter->args[1] = NULL; /* end of args */
208     }
209     if (!filter->args[0]) {
210         return "Invalid cmd= parameter";
211     }
212     filter->command = filter->args[0];
213
214     return NULL;
215 }
216
217 static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
218 {
219     ef_server_t *conf = ap_get_module_config(cmd->server->module_config,
220                                              &ext_filter_module);
221     const char *token;
222     const char *name;
223     char *normalized_name;
224     ef_filter_t *filter;
225
226     name = ap_getword_white(cmd->pool, &args);
227     if (!name) {
228         return "Filter name not found";
229     }
230
231     /* During request processing, we find information about the filter
232      * by looking up the filter name provided by core server in our
233      * hash table.  But the core server has normalized the filter
234      * name by converting it to lower case.  Thus, when adding the
235      * filter to our hash table we have to use lower case as well.
236      */
237     normalized_name = apr_pstrdup(cmd->pool, name);
238     ap_str_tolower(normalized_name);
239
240     if (apr_hash_get(conf->h, normalized_name, APR_HASH_KEY_STRING)) {
241         return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
242                             name);
243     }
244
245     filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t));
246     filter->name = name;
247     filter->mode = OUTPUT_FILTER;
248     filter->ftype = AP_FTYPE_RESOURCE;
249     apr_hash_set(conf->h, normalized_name, APR_HASH_KEY_STRING, filter);
250
251     while (*args) {
252         while (apr_isspace(*args)) {
253             ++args;
254         }
255
256         /* Nasty parsing...  I wish I could simply use ap_getword_white()
257          * here and then look at the token, but ap_getword_white() doesn't
258          * do the right thing when we have cmd="word word word"
259          */
260         if (!strncasecmp(args, "preservescontentlength", 22)) {
261             token = ap_getword_white(cmd->pool, &args);
262             if (!strcasecmp(token, "preservescontentlength")) {
263                 filter->preserves_content_length = 1;
264             }
265             else {
266                 return apr_psprintf(cmd->pool,
267                                     "mangled argument `%s'",
268                                     token);
269             }
270             continue;
271         }
272
273         if (!strncasecmp(args, "mode=", 5)) {
274             args += 5;
275             token = ap_getword_white(cmd->pool, &args);
276             if (!strcasecmp(token, "output")) {
277                 filter->mode = OUTPUT_FILTER;
278             }
279             else if (!strcasecmp(token, "input")) {
280                 filter->mode = INPUT_FILTER;
281             }
282             else {
283                 return apr_psprintf(cmd->pool, "Invalid mode: `%s'",
284                                     token);
285             }
286             continue;
287         }
288
289         if (!strncasecmp(args, "ftype=", 6)) {
290             args += 6;
291             token = ap_getword_white(cmd->pool, &args);
292             filter->ftype = atoi(token);
293             continue;
294         }
295
296         if (!strncasecmp(args, "enableenv=", 10)) {
297             args += 10;
298             token = ap_getword_white(cmd->pool, &args);
299             filter->enable_env = token;
300             continue;
301         }
302
303         if (!strncasecmp(args, "disableenv=", 11)) {
304             args += 11;
305             token = ap_getword_white(cmd->pool, &args);
306             filter->disable_env = token;
307             continue;
308         }
309
310         if (!strncasecmp(args, "intype=", 7)) {
311             args += 7;
312             filter->intype = ap_getword_white(cmd->pool, &args);
313             continue;
314         }
315
316         if (!strncasecmp(args, "outtype=", 8)) {
317             args += 8;
318             filter->outtype = ap_getword_white(cmd->pool, &args);
319             continue;
320         }
321
322         if (!strncasecmp(args, "cmd=", 4)) {
323             args += 4;
324             if ((token = parse_cmd(cmd->pool, &args, filter))) {
325                 return token;
326             }
327             continue;
328         }
329
330         return apr_psprintf(cmd->pool, "Unexpected parameter: `%s'",
331                             args);
332     }
333
334     /* parsing is done...  register the filter
335      */
336     if (filter->mode == OUTPUT_FILTER) {
337         /* XXX need a way to ensure uniqueness among all filters */
338         ap_register_output_filter(filter->name, ef_output_filter, NULL, filter->ftype);
339     }
340     else if (filter->mode == INPUT_FILTER) {
341         /* XXX need a way to ensure uniqueness among all filters */
342         ap_register_input_filter(filter->name, ef_input_filter, NULL, filter->ftype);
343     }
344     else {
345         ap_assert(1 != 1); /* we set the field wrong somehow */
346     }
347
348     return NULL;
349 }
350
351 static const command_rec cmds[] =
352 {
353     AP_INIT_ITERATE("ExtFilterOptions",
354                     add_options,
355                     NULL,
356                     ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
357                     "valid options: DebugLevel=n, LogStderr, NoLogStderr"),
358     AP_INIT_RAW_ARGS("ExtFilterDefine",
359                      define_filter,
360                      NULL,
361                      RSRC_CONF,
362                      "Define an external filter"),
363     {NULL}
364 };
365
366 static int ef_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_s)
367 {
368     main_server = main_s;
369     return OK;
370 }
371
372 static void register_hooks(apr_pool_t *p)
373 {
374     ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
375 }
376
377 static apr_status_t set_resource_limits(request_rec *r,
378                                         apr_procattr_t *procattr)
379 {
380 #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
381     defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
382     core_dir_config *conf =
383         (core_dir_config *)ap_get_module_config(r->per_dir_config,
384                                                 &core_module);
385     apr_status_t rv;
386
387 #ifdef RLIMIT_CPU
388     rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, conf->limit_cpu);
389     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
390 #endif
391 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
392     rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, conf->limit_mem);
393     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
394 #endif
395 #ifdef RLIMIT_NPROC
396     rv = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, conf->limit_nproc);
397     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
398 #endif
399
400 #endif /* if at least one limit defined */
401
402     return APR_SUCCESS;
403 }
404
405 static apr_status_t ef_close_file(void *vfile)
406 {
407     return apr_file_close(vfile);
408 }
409
410 static void child_errfn(apr_pool_t *pool, apr_status_t err, const char *description)
411 {
412     request_rec *r;
413     void *vr;
414     apr_file_t *stderr_log;
415     char errbuf[200];
416     char time_str[APR_CTIME_LEN];
417
418     apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
419     r = vr;
420     apr_file_open_stderr(&stderr_log, pool);
421     ap_recent_ctime(time_str, apr_time_now());
422     apr_file_printf(stderr_log,
423                     "[%s] [client %s] mod_ext_filter (%d)%s: %s\n",
424                     time_str,
425                     r->connection->remote_ip,
426                     err,
427                     apr_strerror(err, errbuf, sizeof(errbuf)),
428                     description);
429 }
430
431 /* init_ext_filter_process: get the external filter process going
432  * This is per-filter-instance (i.e., per-request) initialization.
433  */
434 static apr_status_t init_ext_filter_process(ap_filter_t *f)
435 {
436     ef_ctx_t *ctx = f->ctx;
437     apr_status_t rc;
438     ef_dir_t *dc = ctx->dc;
439     const char * const *env;
440
441     ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc));
442
443     rc = apr_procattr_create(&ctx->procattr, ctx->p);
444     ap_assert(rc == APR_SUCCESS);
445
446     rc = apr_procattr_io_set(ctx->procattr,
447                             APR_CHILD_BLOCK,
448                             APR_CHILD_BLOCK,
449                             APR_CHILD_BLOCK);
450     ap_assert(rc == APR_SUCCESS);
451
452     rc = set_resource_limits(f->r, ctx->procattr);
453     ap_assert(rc == APR_SUCCESS);
454
455     if (dc->log_stderr > 0) {
456         rc = apr_procattr_child_err_set(ctx->procattr,
457                                       f->r->server->error_log, /* stderr in child */
458                                       NULL);
459         ap_assert(rc == APR_SUCCESS);
460     }
461
462     rc = apr_procattr_child_errfn_set(ctx->procattr, child_errfn);
463     ap_assert(rc == APR_SUCCESS);
464     apr_pool_userdata_set(f->r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ctx->p);
465
466     rc = apr_procattr_error_check_set(ctx->procattr, 1);
467     if (rc != APR_SUCCESS) {
468         return rc;
469     }
470
471     /* add standard CGI variables as well as DOCUMENT_URI, DOCUMENT_PATH_INFO,
472      * and QUERY_STRING_UNESCAPED
473      */
474     ap_add_cgi_vars(f->r);
475     ap_add_common_vars(f->r);
476     apr_table_setn(f->r->subprocess_env, "DOCUMENT_URI", f->r->uri);
477     apr_table_setn(f->r->subprocess_env, "DOCUMENT_PATH_INFO", f->r->path_info);
478     if (f->r->args) {
479             /* QUERY_STRING is added by ap_add_cgi_vars */
480         char *arg_copy = apr_pstrdup(f->r->pool, f->r->args);
481         ap_unescape_url(arg_copy);
482         apr_table_setn(f->r->subprocess_env, "QUERY_STRING_UNESCAPED",
483                        ap_escape_shell_cmd(f->r->pool, arg_copy));
484     }
485
486     env = (const char * const *) ap_create_environment(ctx->p,
487                                                        f->r->subprocess_env);
488
489     rc = apr_proc_create(ctx->proc,
490                             ctx->filter->command,
491                             (const char * const *)ctx->filter->args,
492                             env, /* environment */
493                             ctx->procattr,
494                             ctx->p);
495     if (rc != APR_SUCCESS) {
496         ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
497                       "couldn't create child process to run `%s'",
498                       ctx->filter->command);
499         return rc;
500     }
501
502     apr_pool_note_subprocess(ctx->p, ctx->proc, APR_KILL_AFTER_TIMEOUT);
503
504     /* We don't want the handle to the child's stdin inherited by any
505      * other processes created by httpd.  Otherwise, when we close our
506      * handle, the child won't see EOF because another handle will still
507      * be open.
508      */
509
510     apr_pool_cleanup_register(ctx->p, ctx->proc->in,
511                          apr_pool_cleanup_null, /* other mechanism */
512                          ef_close_file);
513
514 #if APR_FILES_AS_SOCKETS
515     {
516         apr_pollfd_t pfd = { 0 };
517
518         rc = apr_pollset_create(&ctx->pollset, 2, ctx->p, 0);
519         ap_assert(rc == APR_SUCCESS);
520
521         pfd.p         = ctx->p;
522         pfd.desc_type = APR_POLL_FILE;
523         pfd.reqevents = APR_POLLOUT;
524         pfd.desc.f    = ctx->proc->in;
525         rc = apr_pollset_add(ctx->pollset, &pfd);
526         ap_assert(rc == APR_SUCCESS);
527
528         pfd.reqevents = APR_POLLIN;
529         pfd.desc.f    = ctx->proc->out;
530         rc = apr_pollset_add(ctx->pollset, &pfd);
531         ap_assert(rc == APR_SUCCESS);
532     }
533 #endif
534
535     return APR_SUCCESS;
536 }
537
538 static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t *p)
539 {
540     const char *debug_str = dc->debug == -1 ?
541         "DebugLevel=0" : apr_psprintf(p, "DebugLevel=%d", dc->debug);
542     const char *log_stderr_str = dc->log_stderr < 1 ?
543         "NoLogStderr" : "LogStderr";
544     const char *preserve_content_length_str = filter->preserves_content_length ?
545         "PreservesContentLength" : "!PreserveContentLength";
546     const char *intype_str = !filter->intype ?
547         "*/*" : filter->intype;
548     const char *outtype_str = !filter->outtype ?
549         "(unchanged)" : filter->outtype;
550
551     return apr_psprintf(p,
552                         "ExtFilterOptions %s %s %s ExtFilterInType %s "
553                         "ExtFilterOuttype %s",
554                         debug_str, log_stderr_str, preserve_content_length_str,
555                         intype_str, outtype_str);
556 }
557
558 static ef_filter_t *find_filter_def(const server_rec *s, const char *fname)
559 {
560     ef_server_t *sc;
561     ef_filter_t *f;
562
563     sc = ap_get_module_config(s->module_config, &ext_filter_module);
564     f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
565     if (!f && s != main_server) {
566         s = main_server;
567         sc = ap_get_module_config(s->module_config, &ext_filter_module);
568         f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
569     }
570     return f;
571 }
572
573 static apr_status_t init_filter_instance(ap_filter_t *f)
574 {
575     ef_ctx_t *ctx;
576     ef_dir_t *dc;
577     apr_status_t rv;
578
579     f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(ef_ctx_t));
580     dc = ap_get_module_config(f->r->per_dir_config,
581                               &ext_filter_module);
582     ctx->dc = dc;
583     /* look for the user-defined filter */
584     ctx->filter = find_filter_def(f->r->server, f->frec->name);
585     if (!ctx->filter) {
586         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
587                       "couldn't find definition of filter '%s'",
588                       f->frec->name);
589         return APR_EINVAL;
590     }
591     ctx->p = f->r->pool;
592     if (ctx->filter->intype &&
593         ctx->filter->intype != INTYPE_ALL) {
594         const char *ctypes;
595
596         if (ctx->filter->mode == INPUT_FILTER) {
597             ctypes = apr_table_get(f->r->headers_in, "Content-Type");
598         }
599         else {
600             ctypes = f->r->content_type;
601         }
602
603         if (ctypes) {
604             const char *ctype = ap_getword(f->r->pool, &ctypes, ';');
605
606             if (strcasecmp(ctx->filter->intype, ctype)) {
607                 /* wrong IMT for us; don't mess with the output */
608                 ctx->noop = 1;
609             }
610         }
611         else {
612             ctx->noop = 1;
613         }
614     }
615     if (ctx->filter->enable_env &&
616         !apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) {
617         /* an environment variable that enables the filter isn't set; bail */
618         ctx->noop = 1;
619     }
620     if (ctx->filter->disable_env &&
621         apr_table_get(f->r->subprocess_env, ctx->filter->disable_env)) {
622         /* an environment variable that disables the filter is set; bail */
623         ctx->noop = 1;
624     }
625     if (!ctx->noop) {
626         rv = init_ext_filter_process(f);
627         if (rv != APR_SUCCESS) {
628             return rv;
629         }
630         if (ctx->filter->outtype &&
631             ctx->filter->outtype != OUTTYPE_UNCHANGED) {
632             ap_set_content_type(f->r, ctx->filter->outtype);
633         }
634         if (ctx->filter->preserves_content_length != 1) {
635             /* nasty, but needed to avoid confusing the browser
636              */
637             apr_table_unset(f->r->headers_out, "Content-Length");
638         }
639     }
640
641     if (dc->debug >= DBGLVL_SHOWOPTIONS) {
642         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
643                       "%sfiltering `%s' of type `%s' through `%s', cfg %s",
644                       ctx->noop ? "NOT " : "",
645                       f->r->uri ? f->r->uri : f->r->filename,
646                       f->r->content_type ? f->r->content_type : "(unspecified)",
647                       ctx->filter->command,
648                       get_cfg_string(dc, ctx->filter, f->r->pool));
649     }
650
651     return APR_SUCCESS;
652 }
653
654 /* drain_available_output():
655  *
656  * if any data is available from the filter, read it and append it
657  * to the the bucket brigade
658  */
659 static apr_status_t drain_available_output(ap_filter_t *f,
660                                            apr_bucket_brigade *bb)
661 {
662     request_rec *r = f->r;
663     conn_rec *c = r->connection;
664     ef_ctx_t *ctx = f->ctx;
665     ef_dir_t *dc = ctx->dc;
666     apr_size_t len;
667     char buf[4096];
668     apr_status_t rv;
669     apr_bucket *b;
670
671     while (1) {
672         len = sizeof(buf);
673         rv = apr_file_read(ctx->proc->out,
674                       buf,
675                       &len);
676         if ((rv && !APR_STATUS_IS_EAGAIN(rv)) ||
677             dc->debug >= DBGLVL_GORY) {
678             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
679                           "apr_file_read(child output), len %" APR_SIZE_T_FMT,
680                           !rv ? len : -1);
681         }
682         if (rv != APR_SUCCESS) {
683             return rv;
684         }
685         b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
686         APR_BRIGADE_INSERT_TAIL(bb, b);
687         return APR_SUCCESS;
688     }
689     /* we should never get here; if we do, a bogus error message would be
690      * the least of our problems
691      */
692     return APR_ANONYMOUS;
693 }
694
695 static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
696                                         apr_size_t len, apr_bucket_brigade *bb)
697 {
698     ef_ctx_t *ctx = f->ctx;
699     ef_dir_t *dc = ctx->dc;
700     apr_status_t rv;
701     apr_size_t bytes_written = 0;
702     apr_size_t tmplen;
703
704     do {
705         tmplen = len - bytes_written;
706         rv = apr_file_write(ctx->proc->in,
707                        (const char *)data + bytes_written,
708                        &tmplen);
709         bytes_written += tmplen;
710         if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
711             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
712                           "apr_file_write(child input), len %" APR_SIZE_T_FMT,
713                           tmplen);
714             return rv;
715         }
716         if (APR_STATUS_IS_EAGAIN(rv)) {
717             /* XXX handle blocking conditions here...  if we block, we need
718              * to read data from the child process and pass it down to the
719              * next filter!
720              */
721             rv = drain_available_output(f, bb);
722             if (APR_STATUS_IS_EAGAIN(rv)) {
723 #if APR_FILES_AS_SOCKETS
724                 int num_events;
725                 const apr_pollfd_t *pdesc;
726
727                 rv = apr_pollset_poll(ctx->pollset, f->r->server->timeout,
728                                       &num_events, &pdesc);
729                 if (rv || dc->debug >= DBGLVL_GORY) {
730                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
731                                   rv, f->r, "apr_pollset_poll()");
732                 }
733                 if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
734                     /* some error such as APR_TIMEUP */
735                     return rv;
736                 }
737 #else /* APR_FILES_AS_SOCKETS */
738                 /* Yuck... I'd really like to wait until I can read
739                  * or write, but instead I have to sleep and try again
740                  */
741                 apr_sleep(100000); /* 100 milliseconds */
742                 if (dc->debug >= DBGLVL_GORY) {
743                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
744                                   0, f->r, "apr_sleep()");
745                 }
746 #endif /* APR_FILES_AS_SOCKETS */
747             }
748             else if (rv != APR_SUCCESS) {
749                 return rv;
750             }
751         }
752     } while (bytes_written < len);
753     return rv;
754 }
755
756 /* ef_unified_filter:
757  *
758  * runs the bucket brigade bb through the filter and puts the result into
759  * bb, dropping the previous content of bb (the input)
760  */
761
762 static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
763 {
764     request_rec *r = f->r;
765     conn_rec *c = r->connection;
766     ef_ctx_t *ctx = f->ctx;
767     apr_bucket *b;
768     ef_dir_t *dc;
769     apr_size_t len;
770     const char *data;
771     apr_status_t rv;
772     char buf[4096];
773     apr_bucket *eos = NULL;
774     apr_bucket_brigade *bb_tmp;
775
776     dc = ctx->dc;
777     bb_tmp = apr_brigade_create(r->pool, c->bucket_alloc);
778
779     for (b = APR_BRIGADE_FIRST(bb);
780          b != APR_BRIGADE_SENTINEL(bb);
781          b = APR_BUCKET_NEXT(b))
782     {
783         if (APR_BUCKET_IS_EOS(b)) {
784             eos = b;
785             break;
786         }
787
788         rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
789         if (rv != APR_SUCCESS) {
790             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "apr_bucket_read()");
791             return rv;
792         }
793
794         /* Good cast, we just tested len isn't negative */
795         if (len > 0 &&
796             (rv = pass_data_to_filter(f, data, (apr_size_t)len, bb_tmp))
797                 != APR_SUCCESS) {
798             return rv;
799         }
800     }
801
802     apr_brigade_cleanup(bb);
803     APR_BRIGADE_CONCAT(bb, bb_tmp);
804     apr_brigade_destroy(bb_tmp);
805
806     if (eos) {
807         /* close the child's stdin to signal that no more data is coming;
808          * that will cause the child to finish generating output
809          */
810         if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
811             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
812                           "apr_file_close(child input)");
813             return rv;
814         }
815         /* since we've seen eos and closed the child's stdin, set the proper pipe
816          * timeout; we don't care if we don't return from apr_file_read() for a while...
817          */
818         rv = apr_file_pipe_timeout_set(ctx->proc->out,
819                                        r->server->timeout);
820         if (rv) {
821             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
822                           "apr_file_pipe_timeout_set(child output)");
823             return rv;
824         }
825     }
826
827     do {
828         len = sizeof(buf);
829         rv = apr_file_read(ctx->proc->out,
830                       buf,
831                       &len);
832         if ((rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv)) ||
833             dc->debug >= DBGLVL_GORY) {
834             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
835                           "apr_file_read(child output), len %" APR_SIZE_T_FMT,
836                           !rv ? len : -1);
837         }
838         if (APR_STATUS_IS_EAGAIN(rv)) {
839             if (eos) {
840                 /* should not occur, because we have an APR timeout in place */
841                 AP_DEBUG_ASSERT(1 != 1);
842             }
843             return APR_SUCCESS;
844         }
845
846         if (rv == APR_SUCCESS) {
847             b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
848             APR_BRIGADE_INSERT_TAIL(bb, b);
849         }
850     } while (rv == APR_SUCCESS);
851
852     if (!APR_STATUS_IS_EOF(rv)) {
853         return rv;
854     }
855
856     if (eos) {
857         b = apr_bucket_eos_create(c->bucket_alloc);
858         APR_BRIGADE_INSERT_TAIL(bb, b);
859     }
860
861     return APR_SUCCESS;
862 }
863
864 static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
865 {
866     request_rec *r = f->r;
867     ef_ctx_t *ctx = f->ctx;
868     apr_status_t rv;
869
870     if (!ctx) {
871         if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
872             ctx = f->ctx;
873             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
874                           "can't initialise output filter %s: %s",
875                           f->frec->name,
876                           (ctx->dc->onfail == 1) ? "removing" : "aborting");
877             ap_remove_output_filter(f);
878             if (ctx->dc->onfail == 1) {
879                 return ap_pass_brigade(f->next, bb);
880             }
881             else {
882                 apr_bucket *e;
883                 f->r->status_line = "500 Internal Server Error";
884
885                 apr_brigade_cleanup(bb);
886                 e = ap_bucket_error_create(HTTP_INTERNAL_SERVER_ERROR,
887                                            NULL, r->pool,
888                                            f->c->bucket_alloc);
889                 APR_BRIGADE_INSERT_TAIL(bb, e);
890                 e = apr_bucket_eos_create(f->c->bucket_alloc);
891                 APR_BRIGADE_INSERT_TAIL(bb, e);
892                 ap_pass_brigade(f->next, bb);
893                 return AP_FILTER_ERROR;
894             }
895         }
896         ctx = f->ctx;
897     }
898     if (ctx->noop) {
899         ap_remove_output_filter(f);
900         return ap_pass_brigade(f->next, bb);
901     }
902
903     rv = ef_unified_filter(f, bb);
904     if (rv != APR_SUCCESS) {
905         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
906                       "ef_unified_filter() failed");
907     }
908
909     if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
910         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
911                       "ap_pass_brigade() failed");
912     }
913     return rv;
914 }
915
916 static int ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
917                            ap_input_mode_t mode, apr_read_type_e block,
918                            apr_off_t readbytes)
919 {
920     ef_ctx_t *ctx = f->ctx;
921     apr_status_t rv;
922
923     if (!ctx) {
924         if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
925             ctx = f->ctx;
926             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
927                           "can't initialise input filter %s: %s",
928                           f->frec->name,
929                           (ctx->dc->onfail == 1) ? "removing" : "aborting");
930             ap_remove_input_filter(f);
931             if (ctx->dc->onfail == 1) {
932                 return ap_get_brigade(f->next, bb, mode, block, readbytes);
933             }
934             else {
935                 f->r->status = HTTP_INTERNAL_SERVER_ERROR;
936                 return HTTP_INTERNAL_SERVER_ERROR;
937             }
938         }
939         ctx = f->ctx;
940     }
941
942     if (ctx->noop) {
943         ap_remove_input_filter(f);
944         return ap_get_brigade(f->next, bb, mode, block, readbytes);
945     }
946
947     rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
948     if (rv != APR_SUCCESS) {
949         return rv;
950     }
951
952     rv = ef_unified_filter(f, bb);
953     return rv;
954 }
955
956 module AP_MODULE_DECLARE_DATA ext_filter_module =
957 {
958     STANDARD20_MODULE_STUFF,
959     create_ef_dir_conf,
960     merge_ef_dir_conf,
961     create_ef_server_conf,
962     NULL,
963     cmds,
964     register_hooks
965 };