/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mod_perl.h" int modperl_callback(pTHX_ modperl_handler_t *handler, apr_pool_t *p, request_rec *r, server_rec *s, AV *args) { CV *cv = Nullcv; I32 flags = G_EVAL|G_SCALAR; dSP; int count, status = OK; /* handler callbacks shouldn't affect each other's taintedness * state, so start every callback with a clear tainted status * before and after the callback one of the main problems we are * trying to solve is that when modperl_croak called (which calls * perl's croak(Nullch) to throw an error object) it leaves the * interpreter in the tainted state which later affects other * callbacks that call eval, etc., which triggers perl crash with: * Insecure dependency in eval while running setgid. Callback * called exit. */ TAINT_NOT; if ((status = modperl_handler_resolve(aTHX_ &handler, p, s)) != OK) { TAINT_NOT; return status; } ENTER;SAVETMPS; PUSHMARK(SP); if (MpHandlerMETHOD(handler)) { GV *gv; if (!handler->mgv_obj) { Perl_croak(aTHX_ "panic: %s method handler object is NULL!", modperl_handler_name(handler)); } gv = modperl_mgv_lookup(aTHX_ handler->mgv_obj); XPUSHs(modperl_mgv_sv(gv)); } if (args) { I32 items = AvFILLp(args) + 1; EXTEND(SP, items); Copy(AvARRAY(args), SP + 1, items, SV*); SP += items; } PUTBACK; if (MpHandlerANON(handler)) { #ifdef USE_ITHREADS cv = modperl_handler_anon_get(aTHX_ handler->mgv_obj); #else cv = handler->cv; #endif } else { GV *gv = modperl_mgv_lookup_autoload(aTHX_ handler->mgv_cv, s, p); if (gv) { cv = modperl_mgv_cv(gv); } else { const char *name; modperl_mgv_t *symbol = handler->mgv_cv; /* XXX: need to validate *symbol */ if (symbol && symbol->name) { name = modperl_mgv_as_string(aTHX_ symbol, p, 0); } else { name = handler->name; } MP_TRACE_h(MP_FUNC, "[%s %s] lookup of %s failed", modperl_pid_tid(p), modperl_server_desc(s, p), name); ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "lookup of '%s' failed", name); status = HTTP_INTERNAL_SERVER_ERROR; } } if (status == OK) { count = call_sv((SV*)cv, flags); SPAGAIN; if (count != 1) { /* XXX can this really happen with G_EVAL|G_SCALAR? */ status = OK; } else { SV *status_sv = POPs; if (status_sv == &PL_sv_undef) { /* ModPerl::Util::exit() and Perl_croak internally * arrange to return PL_sv_undef with G_EVAL|G_SCALAR */ status = OK; } else { status = SvIVx(status_sv); } } PUTBACK; } FREETMPS;LEAVE; if (SvTRUE(ERRSV)) { MP_TRACE_h(MP_FUNC, "$@ = %s", SvPV_nolen(ERRSV)); status = HTTP_INTERNAL_SERVER_ERROR; } if (status == HTTP_INTERNAL_SERVER_ERROR) { if (r && r->notes) { apr_table_merge(r->notes, "error-notes", SvPV_nolen(ERRSV)); } } TAINT_NOT; return status; } int modperl_callback_run_handlers(int idx, int type, request_rec *r, conn_rec *c, server_rec *s, apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, modperl_hook_run_mode_e run_mode) { #ifdef USE_ITHREADS pTHX; modperl_interp_t *interp = NULL; #endif MP_dSCFG(s); MP_dDCFG; MP_dRCFG; modperl_handler_t **handlers; apr_pool_t *p = NULL; MpAV *av, **avp; int i, status = OK; const char *desc = NULL; AV *av_args = Nullav; if (!MpSrvENABLE(scfg)) { MP_TRACE_h(MP_FUNC, "PerlOff for server %s:%u", s->server_hostname, s->port); return DECLINED; } if (r || c) { p = c ? c->pool : r->pool; } else { p = pconf; } avp = modperl_handler_lookup_handlers(dcfg, scfg, rcfg, p, type, idx, FALSE, &desc); if (!(avp && (av = *avp))) { MP_TRACE_h(MP_FUNC, "no %s handlers configured (%s)", desc, r ? r->uri : ""); return DECLINED; } #ifdef USE_ITHREADS if (r && !c && modperl_interp_scope_connection(scfg)) { c = r->connection; } if (r || c) { interp = modperl_interp_select(r, c, s); aTHX = interp->perl; } else { /* Child{Init,Exit}, OpenLogs */ aTHX = scfg->mip->parent->perl; PERL_SET_CONTEXT(aTHX); } #endif /* XXX: would like to do this in modperl_hook_create_request() * but modperl_interp_select() is what figures out if * PerlInterpScope eq handler, in which case we do not register * a cleanup. modperl_hook_create_request() is also currently always * run even if modperl isn't handling any part of the request */ modperl_config_req_cleanup_register(r, rcfg); switch (type) { case MP_HANDLER_TYPE_PER_SRV: modperl_handler_make_args(aTHX_ &av_args, "Apache2::RequestRec", r, NULL); /* per-server PerlSetEnv and PerlPassEnv - only once per-request */ if (! MpReqPERL_SET_ENV_SRV(rcfg)) { modperl_env_configure_request_srv(aTHX_ r); } break; case MP_HANDLER_TYPE_PER_DIR: modperl_handler_make_args(aTHX_ &av_args, "Apache2::RequestRec", r, NULL); /* per-server PerlSetEnv and PerlPassEnv - only once per-request */ if (! MpReqPERL_SET_ENV_SRV(rcfg)) { modperl_env_configure_request_srv(aTHX_ r); } /* per-directory PerlSetEnv - only once per-request */ if (! MpReqPERL_SET_ENV_DIR(rcfg)) { modperl_env_configure_request_dir(aTHX_ r); } break; case MP_HANDLER_TYPE_PRE_CONNECTION: case MP_HANDLER_TYPE_CONNECTION: modperl_handler_make_args(aTHX_ &av_args, "Apache2::Connection", c, NULL); break; case MP_HANDLER_TYPE_FILES: modperl_handler_make_args(aTHX_ &av_args, "APR::Pool", pconf, "APR::Pool", plog, "APR::Pool", ptemp, "Apache2::ServerRec", s, NULL); break; case MP_HANDLER_TYPE_PROCESS: modperl_handler_make_args(aTHX_ &av_args, "APR::Pool", pconf, "Apache2::ServerRec", s, NULL); break; }; modperl_callback_current_callback_set(desc); MP_TRACE_h(MP_FUNC, "[%s] running %d %s handlers", modperl_pid_tid(p), av->nelts, desc); handlers = (modperl_handler_t **)av->elts; for (i=0; inelts; i++) { status = modperl_callback(aTHX_ handlers[i], p, r, s, av_args); MP_TRACE_h(MP_FUNC, "callback '%s' returned %d", modperl_handler_name(handlers[i]), status); /* follow Apache's lead and let OK terminate the phase for * MP_HOOK_RUN_FIRST handlers. MP_HOOK_RUN_ALL handlers keep * going on OK. MP_HOOK_VOID handlers ignore all errors. */ if (run_mode == MP_HOOK_RUN_ALL) { /* special case */ if (type == MP_HANDLER_TYPE_FILES && status != OK) { /* open_logs and post_config require OK return code or * the server aborts, so we need to log an error in * case the handler didn't fail but returned something * different from OK */ if (SvTRUE(ERRSV)) { status = modperl_errsv(aTHX_ status, r, s); } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "Callback '%s' returned %d, whereas " "Apache2::Const::OK (%d) is the only valid " "return value for %s handlers", modperl_handler_name(handlers[i]), status, OK, desc); } break; } /* the normal case: * OK and DECLINED continue * errors end the phase */ else if ((status != OK) && (status != DECLINED)) { status = modperl_errsv(aTHX_ status, r, s); #ifdef MP_TRACE if (i+1 != av->nelts) { MP_TRACE_h(MP_FUNC, "error status %d leaves %d " "uncalled %s handlers", status, av->nelts-i-1, desc); } #endif break; } } else if (run_mode == MP_HOOK_RUN_FIRST) { /* the exceptional case: * OK and errors end the phase * DECLINED continues */ if (status == OK) { #ifdef MP_TRACE if (i+1 != av->nelts) { MP_TRACE_h(MP_FUNC, "OK ends the %s stack, " "leaving %d uncalled %s handlers", desc, av->nelts-i-1, desc); } #endif break; } if (status != DECLINED) { status = modperl_errsv(aTHX_ status, r, s); #ifdef MP_TRACE if (i+1 != av->nelts) { MP_TRACE_h(MP_FUNC, "error status %d leaves %d " "uncalled %s handlers", status, av->nelts-i-1, desc); } #endif break; } } else { /* the rare case. * MP_HOOK_VOID handlers completely ignore the return status * Apache should handle whatever mod_perl returns, * so there is no need to mess with the status */ } /* it's possible that during the last callback a new handler * was pushed onto the same phase it's running from. av needs * to be updated. * * XXX: would be nice to somehow optimize that */ avp = modperl_handler_lookup_handlers(dcfg, scfg, rcfg, p, type, idx, FALSE, NULL); if (avp && (av = *avp)) { handlers = (modperl_handler_t **)av->elts; } } SvREFCNT_dec((SV*)av_args); /* PerlInterpScope handler */ MP_INTERP_PUTBACK(interp); return status; } int modperl_callback_per_dir(int idx, request_rec *r, modperl_hook_run_mode_e run_mode) { return modperl_callback_run_handlers(idx, MP_HANDLER_TYPE_PER_DIR, r, NULL, r->server, NULL, NULL, NULL, run_mode); } int modperl_callback_per_srv(int idx, request_rec *r, modperl_hook_run_mode_e run_mode) { return modperl_callback_run_handlers(idx, MP_HANDLER_TYPE_PER_SRV, r, NULL, r->server, NULL, NULL, NULL, run_mode); } int modperl_callback_connection(int idx, conn_rec *c, modperl_hook_run_mode_e run_mode) { return modperl_callback_run_handlers(idx, MP_HANDLER_TYPE_CONNECTION, NULL, c, c->base_server, NULL, NULL, NULL, run_mode); } int modperl_callback_pre_connection(int idx, conn_rec *c, void *csd, modperl_hook_run_mode_e run_mode) { return modperl_callback_run_handlers(idx, MP_HANDLER_TYPE_PRE_CONNECTION, NULL, c, c->base_server, NULL, NULL, NULL, run_mode); } void modperl_callback_process(int idx, apr_pool_t *p, server_rec *s, modperl_hook_run_mode_e run_mode) { modperl_callback_run_handlers(idx, MP_HANDLER_TYPE_PROCESS, NULL, NULL, s, p, NULL, NULL, run_mode); } int modperl_callback_files(int idx, apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s, modperl_hook_run_mode_e run_mode) { return modperl_callback_run_handlers(idx, MP_HANDLER_TYPE_FILES, NULL, NULL, s, pconf, plog, ptemp, run_mode); }