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#define CORE_PRIVATE
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
38typedef struct ef_server_t {
39    apr_pool_t *p;
40    apr_hash_t *h;
41} ef_server_t;
42
43typedef 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
58typedef struct ef_dir_t {
59    int debug;
60    int log_stderr;
61    int onfail;
62} ef_dir_t;
63
64typedef 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
76module AP_MODULE_DECLARE_DATA ext_filter_module;
77static const server_rec *main_server;
78
79static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
80static 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
89static 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
100static 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
110static 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
139static 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
169static 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
217static 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
351static 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
366static 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
372static void register_hooks(apr_pool_t *p)
373{
374    ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
375}
376
377static 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
405static apr_status_t ef_close_file(void *vfile)
406{
407    return apr_file_close(vfile);
408}
409
410static 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 */
434static 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
538static 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
558static 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
573static 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 */
659static 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
695static 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
762static 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
864static 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
916static 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
956module 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};
966