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