blob: 04f43171f6660fdda1253698134ac03cb6819384 [file] [log] [blame]
/* 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 = (CV *)NULL;
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((char *)NULL) 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] lookup of %s failed",
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)
{
MP_dINTERP;
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 = (AV *)NULL;
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;
}
MP_INTERPa(r, c, s);
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, "running %d %s handlers", av->nelts, desc);
handlers = (modperl_handler_t **)av->elts;
for (i=0; i<av->nelts; 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);
MP_INTERP_PUTBACK(interp, aTHX);
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);
}
/*
* Local Variables:
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/