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