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