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 log_stderr;
60     int onfail;
61 } ef_dir_t;
62 
63 typedef 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 
75 module AP_MODULE_DECLARE_DATA ext_filter_module;
76 static const server_rec *main_server;
77 
78 static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
79 static 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 
create_ef_dir_conf(apr_pool_t * p,char * dummy)85 static 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 
create_ef_server_conf(apr_pool_t * p,server_rec * s)95 static 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 
merge_ef_dir_conf(apr_pool_t * p,void * basev,void * overridesv)105 static 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 
add_options(cmd_parms * cmd,void * in_dc,const char * arg)127 static 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 
parse_cmd(apr_pool_t * p,const char ** args,ef_filter_t * filter)154 static 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 
define_filter(cmd_parms * cmd,void * dummy,const char * args)202 static 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 
336 static 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 
ef_init(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * main_s)351 static 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 
register_hooks(apr_pool_t * p)357 static void register_hooks(apr_pool_t *p)
358 {
359     ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
360 }
361 
set_resource_limits(request_rec * r,apr_procattr_t * procattr)362 static 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 
ef_close_file(void * vfile)389 static apr_status_t ef_close_file(void *vfile)
390 {
391     return apr_file_close(vfile);
392 }
393 
child_errfn(apr_pool_t * pool,apr_status_t err,const char * description)394 static 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  */
init_ext_filter_process(ap_filter_t * f)417 static 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 
get_cfg_string(ef_dir_t * dc,ef_filter_t * filter,apr_pool_t * p)521 static 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 
find_filter_def(const server_rec * s,const char * fname)539 static 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 
init_filter_instance(ap_filter_t * f)554 static 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 bucket brigade
639  */
drain_available_output(ap_filter_t * f,apr_bucket_brigade * bb)640 static 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, len);
659         if (rv != APR_SUCCESS) {
660             return rv;
661         }
662         b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
663         APR_BRIGADE_INSERT_TAIL(bb, b);
664         return APR_SUCCESS;
665     }
666     /* we should never get here; if we do, a bogus error message would be
667      * the least of our problems
668      */
669     return APR_ANONYMOUS;
670 }
671 
pass_data_to_filter(ap_filter_t * f,const char * data,apr_size_t len,apr_bucket_brigade * bb)672 static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
673                                         apr_size_t len, apr_bucket_brigade *bb)
674 {
675     ef_ctx_t *ctx = f->ctx;
676     apr_status_t rv;
677     apr_size_t bytes_written = 0;
678     apr_size_t tmplen;
679 
680     do {
681         tmplen = len - bytes_written;
682         rv = apr_file_write_full(ctx->proc->in,
683                        (const char *)data + bytes_written,
684                        tmplen, &tmplen);
685         bytes_written += tmplen;
686         if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
687             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01461)
688                           "apr_file_write(child input), len %" APR_SIZE_T_FMT,
689                           tmplen);
690             return rv;
691         }
692         if (APR_STATUS_IS_EAGAIN(rv)) {
693             /* XXX handle blocking conditions here...  if we block, we need
694              * to read data from the child process and pass it down to the
695              * next filter!
696              */
697             rv = drain_available_output(f, bb);
698             if (APR_STATUS_IS_EAGAIN(rv)) {
699 #if APR_FILES_AS_SOCKETS
700                 int num_events;
701                 const apr_pollfd_t *pdesc;
702 
703                 rv = apr_pollset_poll(ctx->pollset, f->r->server->timeout,
704                                       &num_events, &pdesc);
705                 if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
706                     ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(01462)
707                                   "apr_pollset_poll()");
708                     /* some error such as APR_TIMEUP */
709                     return rv;
710                 }
711                 ap_log_rerror(APLOG_MARK, APLOG_TRACE6, rv, f->r,
712                               "apr_pollset_poll()");
713 #else /* APR_FILES_AS_SOCKETS */
714                 /* Yuck... I'd really like to wait until I can read
715                  * or write, but instead I have to sleep and try again
716                  */
717                 apr_sleep(apr_time_from_msec(100));
718                 ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r, "apr_sleep()");
719 #endif /* APR_FILES_AS_SOCKETS */
720             }
721             else if (rv != APR_SUCCESS) {
722                 return rv;
723             }
724         }
725     } while (bytes_written < len);
726     return rv;
727 }
728 
729 /* ef_unified_filter:
730  *
731  * runs the bucket brigade bb through the filter and puts the result into
732  * bb, dropping the previous content of bb (the input)
733  */
734 
ef_unified_filter(ap_filter_t * f,apr_bucket_brigade * bb)735 static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
736 {
737     request_rec *r = f->r;
738     conn_rec *c = r->connection;
739     ef_ctx_t *ctx = f->ctx;
740     apr_bucket *b;
741     apr_size_t len;
742     const char *data;
743     apr_status_t rv;
744     char buf[4096];
745     apr_bucket *eos = NULL;
746     apr_bucket_brigade *bb_tmp;
747 
748     bb_tmp = apr_brigade_create(r->pool, c->bucket_alloc);
749 
750     for (b = APR_BRIGADE_FIRST(bb);
751          b != APR_BRIGADE_SENTINEL(bb);
752          b = APR_BUCKET_NEXT(b))
753     {
754         if (APR_BUCKET_IS_EOS(b)) {
755             eos = b;
756             break;
757         }
758 
759         if (AP_BUCKET_IS_ERROR(b)) {
760             apr_bucket *cpy;
761             apr_bucket_copy(b, &cpy);
762             APR_BRIGADE_INSERT_TAIL(bb_tmp, cpy);
763             break;
764         }
765 
766         rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
767         if (rv != APR_SUCCESS) {
768             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01463) "apr_bucket_read()");
769             return rv;
770         }
771 
772         /* Good cast, we just tested len isn't negative */
773         if (len > 0 &&
774             (rv = pass_data_to_filter(f, data, (apr_size_t)len, bb_tmp))
775                 != APR_SUCCESS) {
776             return rv;
777         }
778     }
779 
780     apr_brigade_cleanup(bb);
781     APR_BRIGADE_CONCAT(bb, bb_tmp);
782     apr_brigade_destroy(bb_tmp);
783 
784     if (eos) {
785         /* close the child's stdin to signal that no more data is coming;
786          * that will cause the child to finish generating output
787          */
788         if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
789             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01464)
790                           "apr_file_close(child input)");
791             return rv;
792         }
793         /* since we've seen eos and closed the child's stdin, set the proper pipe
794          * timeout; we don't care if we don't return from apr_file_read() for a while...
795          */
796         rv = apr_file_pipe_timeout_set(ctx->proc->out,
797                                        r->server->timeout);
798         if (rv) {
799             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01465)
800                           "apr_file_pipe_timeout_set(child output)");
801             return rv;
802         }
803     }
804 
805     do {
806         int lvl = APLOG_TRACE6;
807         len = sizeof(buf);
808         rv = apr_file_read(ctx->proc->out, buf, &len);
809         if (rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv))
810             lvl = APLOG_ERR;
811         ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01466)
812                       "apr_file_read(child output), len %" APR_SIZE_T_FMT, len);
813         if (APR_STATUS_IS_EAGAIN(rv)) {
814             if (eos) {
815                 /* should not occur, because we have an APR timeout in place */
816                 AP_DEBUG_ASSERT(1 != 1);
817             }
818             return APR_SUCCESS;
819         }
820 
821         if (rv == APR_SUCCESS) {
822             b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
823             APR_BRIGADE_INSERT_TAIL(bb, b);
824         }
825     } while (rv == APR_SUCCESS);
826 
827     if (!APR_STATUS_IS_EOF(rv)) {
828         return rv;
829     }
830 
831     if (eos) {
832         b = apr_bucket_eos_create(c->bucket_alloc);
833         APR_BRIGADE_INSERT_TAIL(bb, b);
834         ctx->hit_eos = 1;
835     }
836 
837     return APR_SUCCESS;
838 }
839 
ef_output_filter(ap_filter_t * f,apr_bucket_brigade * bb)840 static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
841 {
842     request_rec *r = f->r;
843     ef_ctx_t *ctx = f->ctx;
844     apr_status_t rv;
845 
846     if (!ctx) {
847         if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
848             ctx = f->ctx;
849             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01467)
850                           "can't initialise output filter %s: %s",
851                           f->frec->name,
852                           (ctx->dc->onfail == 1) ? "removing" : "aborting");
853             ap_remove_output_filter(f);
854             if (ctx->dc->onfail == 1) {
855                 return ap_pass_brigade(f->next, bb);
856             }
857             else {
858                 apr_bucket *e;
859                 f->r->status_line = "500 Internal Server Error";
860 
861                 apr_brigade_cleanup(bb);
862                 e = ap_bucket_error_create(HTTP_INTERNAL_SERVER_ERROR,
863                                            NULL, r->pool,
864                                            f->c->bucket_alloc);
865                 APR_BRIGADE_INSERT_TAIL(bb, e);
866                 e = apr_bucket_eos_create(f->c->bucket_alloc);
867                 APR_BRIGADE_INSERT_TAIL(bb, e);
868                 ap_pass_brigade(f->next, bb);
869                 return AP_FILTER_ERROR;
870             }
871         }
872         ctx = f->ctx;
873     }
874     if (ctx->noop) {
875         ap_remove_output_filter(f);
876         return ap_pass_brigade(f->next, bb);
877     }
878 
879     rv = ef_unified_filter(f, bb);
880     if (rv != APR_SUCCESS) {
881         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01468)
882                       "ef_unified_filter() failed");
883     }
884 
885     if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
886         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01469)
887                       "ap_pass_brigade() failed");
888     }
889     return rv;
890 }
891 
ef_input_filter(ap_filter_t * f,apr_bucket_brigade * bb,ap_input_mode_t mode,apr_read_type_e block,apr_off_t readbytes)892 static apr_status_t ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
893                                     ap_input_mode_t mode, apr_read_type_e block,
894                                     apr_off_t readbytes)
895 {
896     ef_ctx_t *ctx = f->ctx;
897     apr_status_t rv;
898 
899     /* just get out of the way of things we don't want. */
900     if (mode != AP_MODE_READBYTES) {
901         return ap_get_brigade(f->next, bb, mode, block, readbytes);
902     }
903 
904     if (!ctx) {
905         if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
906             ctx = f->ctx;
907             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01470)
908                           "can't initialise input filter %s: %s",
909                           f->frec->name,
910                           (ctx->dc->onfail == 1) ? "removing" : "aborting");
911             ap_remove_input_filter(f);
912             if (ctx->dc->onfail == 1) {
913                 return ap_get_brigade(f->next, bb, mode, block, readbytes);
914             }
915             else {
916                 f->r->status = HTTP_INTERNAL_SERVER_ERROR;
917                 return HTTP_INTERNAL_SERVER_ERROR;
918             }
919         }
920         ctx = f->ctx;
921     }
922 
923     if (ctx->hit_eos) {
924         /* Match behaviour of HTTP_IN if filter is re-invoked after
925          * hitting EOS: give back another EOS. */
926         apr_bucket *e = apr_bucket_eos_create(f->c->bucket_alloc);
927         APR_BRIGADE_INSERT_TAIL(bb, e);
928         return APR_SUCCESS;
929     }
930 
931     if (ctx->noop) {
932         ap_remove_input_filter(f);
933         return ap_get_brigade(f->next, bb, mode, block, readbytes);
934     }
935 
936     rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
937     if (rv != APR_SUCCESS) {
938         return rv;
939     }
940 
941     rv = ef_unified_filter(f, bb);
942     return rv;
943 }
944 
945 AP_DECLARE_MODULE(ext_filter) =
946 {
947     STANDARD20_MODULE_STUFF,
948     create_ef_dir_conf,
949     merge_ef_dir_conf,
950     create_ef_server_conf,
951     NULL,
952     cmds,
953     register_hooks
954 };
955