/* 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" #define EnvMgOK ((SV*)ENVHV && SvMAGIC((SV*)ENVHV)) #define EnvMgObj (EnvMgOK ? SvMAGIC((SV*)ENVHV)->mg_ptr : NULL) #define EnvMgLen (EnvMgOK ? SvMAGIC((SV*)ENVHV)->mg_len : 0) #define EnvMgObjSet(val){ \ if (EnvMgOK) SvMAGIC((SV*)ENVHV)->mg_ptr = (char *)val;} #define EnvMgLenSet(val) {\ if (EnvMgOK) SvMAGIC((SV*)ENVHV)->mg_len = val;} /* XXX: move to utils? */ static unsigned long modperl_interp_address(pTHX) { #ifdef USE_ITHREADS return (unsigned long)aTHX; #else return (unsigned long)0; /* just one interpreter */ #endif } #define MP_ENV_HV_STORE(hv, key, val) STMT_START { \ I32 klen = strlen(key); \ SV **svp = hv_fetch(hv, key, klen, FALSE); \ \ if (svp) { \ sv_setpv(*svp, val); \ } \ else { \ SV *sv = newSVpv(val, 0); \ hv_store(hv, key, klen, sv, FALSE); \ modperl_envelem_tie(sv, key, klen); \ svp = &sv; \ } \ MP_TRACE_e(MP_FUNC, "$ENV{%s} = \"%s\";", key, val); \ \ SvTAINTED_on(*svp); \ } STMT_END void modperl_env_hv_store(pTHX_ const char *key, const char *val) { MP_ENV_HV_STORE(ENVHV, key, val); } static MP_INLINE void modperl_env_hv_delete(pTHX_ HV *hv, char *key) { I32 klen = strlen(key); if (hv_exists(hv, key, klen)) { hv_delete(hv, key, strlen(key), G_DISCARD); } } typedef struct { const char *key; I32 klen; const char *val; I32 vlen; U32 hash; } modperl_env_ent_t; #define MP_ENV_ENT(k,v) \ { k, MP_SSTRLEN(k), v, MP_SSTRLEN(v), 0 } static modperl_env_ent_t MP_env_const_vars[] = { MP_ENV_ENT("MOD_PERL", MP_VERSION_STRING), MP_ENV_ENT("MOD_PERL_API_VERSION", MP_API_VERSION), { NULL } }; void modperl_env_hash_keys(pTHX) { modperl_env_ent_t *ent = MP_env_const_vars; while (ent->key) { PERL_HASH(ent->hash, ent->key, ent->klen); MP_TRACE_e(MP_FUNC, "[0x%lx] PERL_HASH: %s (len: %d)", modperl_interp_address(aTHX), ent->key, ent->klen); ent++; } } void modperl_env_clear(pTHX) { HV *hv = ENVHV; U32 mg_flags; modperl_env_untie(mg_flags); MP_TRACE_e(MP_FUNC, "[0x%lx] %%ENV = ();", modperl_interp_address(aTHX)); hv_clear(hv); modperl_env_tie(mg_flags); } #define MP_ENV_HV_STORE_TABLE_ENTRY(hv, elt) \ MP_ENV_HV_STORE(hv, elt.key, elt.val); static void modperl_env_table_populate(pTHX_ apr_table_t *table) { HV *hv = ENVHV; U32 mg_flags; int i; const apr_array_header_t *array; apr_table_entry_t *elts; modperl_env_untie(mg_flags); array = apr_table_elts(table); elts = (apr_table_entry_t *)array->elts; for (i = 0; i < array->nelts; i++) { if (!elts[i].key || !elts[i].val) { continue; } MP_ENV_HV_STORE_TABLE_ENTRY(hv, elts[i]); } modperl_env_tie(mg_flags); } static void modperl_env_table_unpopulate(pTHX_ apr_table_t *table) { HV *hv = ENVHV; U32 mg_flags; int i; const apr_array_header_t *array; apr_table_entry_t *elts; modperl_env_untie(mg_flags); array = apr_table_elts(table); elts = (apr_table_entry_t *)array->elts; for (i = 0; i < array->nelts; i++) { if (!elts[i].key) { continue; } modperl_env_hv_delete(aTHX_ hv, elts[i].key); MP_TRACE_e(MP_FUNC, "delete $ENV{%s};", elts[i].key); } modperl_env_tie(mg_flags); } /* see the comment in modperl_env_sync_env_hash2table */ static void modperl_env_sync_table(pTHX_ apr_table_t *table) { int i; const apr_array_header_t *array; apr_table_entry_t *elts; HV *hv = ENVHV; SV **svp; array = apr_table_elts(table); elts = (apr_table_entry_t *)array->elts; for (i = 0; i < array->nelts; i++) { if (!elts[i].key) { continue; } svp = hv_fetch(hv, elts[i].key, strlen(elts[i].key), FALSE); if (svp) { apr_table_set(table, elts[i].key, SvPV_nolen(*svp)); MP_TRACE_e(MP_FUNC, "(Set|Pass)Env '%s' '%s'", elts[i].key, SvPV_nolen(*svp)); } } TAINT_NOT; /* SvPV_* causes the taint issue */ } /* Make per-server PerlSetEnv and PerlPassEnv in sync with %ENV at * config time (if perl is running), by copying %ENV values to the * PerlSetEnv and PerlPassEnv tables (only for keys which are already * in those tables) */ void modperl_env_sync_srv_env_hash2table(pTHX_ apr_pool_t *p, modperl_config_srv_t *scfg) { modperl_env_sync_table(aTHX_ scfg->SetEnv); modperl_env_sync_table(aTHX_ scfg->PassEnv); } void modperl_env_sync_dir_env_hash2table(pTHX_ apr_pool_t *p, modperl_config_dir_t *dcfg) { modperl_env_sync_table(aTHX_ dcfg->SetEnv); } /* list of environment variables to pass by default */ static const char *MP_env_pass_defaults[] = { "PATH", "TZ", NULL }; void modperl_env_configure_server(pTHX_ apr_pool_t *p, server_rec *s) { MP_dSCFG(s); int i = 0; /* make per-server PerlSetEnv and PerlPassEnv entries visible * to %ENV at config time */ for (i=0; MP_env_pass_defaults[i]; i++) { const char *key = MP_env_pass_defaults[i]; char *val; if (apr_table_get(scfg->SetEnv, key) || apr_table_get(scfg->PassEnv, key)) { continue; /* already configured */ } if ((val = getenv(key))) { apr_table_set(scfg->PassEnv, key, val); } } MP_TRACE_e(MP_FUNC, "\t[%s/0x%lx/%s]" "\n\t@ENV{keys scfg->SetEnv} = values scfg->SetEnv;", modperl_pid_tid(p), modperl_interp_address(aTHX), modperl_server_desc(s, p)); modperl_env_table_populate(aTHX_ scfg->SetEnv); MP_TRACE_e(MP_FUNC, "\t[%s/0x%lx/%s]" "\n\t@ENV{keys scfg->PassEnv} = values scfg->PassEnv;", modperl_pid_tid(p), modperl_interp_address(aTHX), modperl_server_desc(s, p)); modperl_env_table_populate(aTHX_ scfg->PassEnv); } #define overlay_subprocess_env(r, tab) \ r->subprocess_env = apr_table_overlay(r->pool, \ r->subprocess_env, \ tab) void modperl_env_configure_request_dir(pTHX_ request_rec *r) { MP_dRCFG; MP_dDCFG; /* populate %ENV and r->subprocess_env with per-directory * PerlSetEnv entries. * * note that per-server PerlSetEnv entries, as well as * PerlPassEnv entries (which are only per-server), are added * to %ENV and r->subprocess_env via modperl_env_configure_request_srv */ if (!apr_is_empty_table(dcfg->SetEnv)) { apr_table_t *setenv_copy; /* add per-directory PerlSetEnv entries to %ENV * collisions with per-server PerlSetEnv entries are * resolved via the nature of a Perl hash */ MP_TRACE_e(MP_FUNC, "\t[%s/0x%lx/%s]" "\n\t@ENV{keys dcfg->SetEnv} = values dcfg->SetEnv;", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), modperl_server_desc(r->server, r->pool)); modperl_env_table_populate(aTHX_ dcfg->SetEnv); /* make sure the entries are in the subprocess_env table as well. * we need to use apr_table_overlap (not apr_table_overlay) because * r->subprocess_env might have per-server PerlSetEnv entries in it * and using apr_table_overlay would generate duplicate entries. * in order to use apr_table_overlap, though, we need to copy the * the dcfg table so that pool requirements are satisfied */ setenv_copy = apr_table_copy(r->pool, dcfg->SetEnv); apr_table_overlap(r->subprocess_env, setenv_copy, APR_OVERLAP_TABLES_SET); } MpReqPERL_SET_ENV_DIR_On(rcfg); } void modperl_env_configure_request_srv(pTHX_ request_rec *r) { MP_dRCFG; MP_dSCFG(r->server); /* populate %ENV and r->subprocess_env with per-server PerlSetEnv * and PerlPassEnv entries. * * although both are setup in %ENV in modperl_request_configure_server * %ENV will be reset via modperl_env_request_unpopulate. */ if (!apr_is_empty_table(scfg->SetEnv)) { MP_TRACE_e(MP_FUNC, "\t[%s/0x%lx/%s]" "\n\t@ENV{keys scfg->SetEnv} = values scfg->SetEnv;", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), modperl_server_desc(r->server, r->pool)); modperl_env_table_populate(aTHX_ scfg->SetEnv); overlay_subprocess_env(r, scfg->SetEnv); } if (!apr_is_empty_table(scfg->PassEnv)) { MP_TRACE_e(MP_FUNC, "\t[%s/0x%lx/%s]" "\n\t@ENV{keys scfg->PassEnv} = values scfg->PassEnv;", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), modperl_server_desc(r->server, r->pool)); modperl_env_table_populate(aTHX_ scfg->PassEnv); overlay_subprocess_env(r, scfg->PassEnv); } MpReqPERL_SET_ENV_SRV_On(rcfg); } void modperl_env_default_populate(pTHX) { modperl_env_ent_t *ent = MP_env_const_vars; HV *hv = ENVHV; U32 mg_flags; modperl_env_untie(mg_flags); while (ent->key) { SV *sv = newSVpvn(ent->val, ent->vlen); hv_store(hv, ent->key, ent->klen, sv, ent->hash); MP_TRACE_e(MP_FUNC, "$ENV{%s} = \"%s\";", ent->key, ent->val); modperl_envelem_tie(sv, ent->key, ent->klen); ent++; } modperl_env_tie(mg_flags); } void modperl_env_request_populate(pTHX_ request_rec *r) { MP_dRCFG; /* this is called under the following conditions * - if PerlOptions +SetupEnv * - if $r->subprocess_env() is called in a void context with no args * * normally, %ENV is only populated once per request (if at all) - * just prior to content generation if +SetupEnv. * * however, in the $r->subprocess_env() case it will be called * more than once - once for each void call, and once again just * prior to content generation. while costly, the multiple * passes are required, otherwise void calls would prohibit later * phases from populating %ENV with new subprocess_env table entries */ MP_TRACE_e(MP_FUNC, "\t[%s/0x%lx/%s%s]" "\n\t@ENV{keys r->subprocess_env} = values r->subprocess_env;", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), modperl_server_desc(r->server, r->pool), r->uri); /* we can eliminate some of the cost by only doing CGI variables once * per-request no matter how many times $r->subprocess_env() is called */ if (! MpReqSETUP_ENV(rcfg)) { ap_add_common_vars(r); ap_add_cgi_vars(r); } modperl_env_table_populate(aTHX_ r->subprocess_env); /* don't set up CGI variables again this request. * this also triggers modperl_env_request_unpopulate, which * resets %ENV between requests - see modperl_config_request_cleanup */ MpReqSETUP_ENV_On(rcfg); } void modperl_env_request_unpopulate(pTHX_ request_rec *r) { MP_dRCFG; /* unset only once */ if (!MpReqSETUP_ENV(rcfg)) { return; } MP_TRACE_e(MP_FUNC, "\n\t[%s/0x%lx/%s%s]\n\tdelete @ENV{keys r->subprocess_env};", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), modperl_server_desc(r->server, r->pool), r->uri); modperl_env_table_unpopulate(aTHX_ r->subprocess_env); MpReqSETUP_ENV_Off(rcfg); } void modperl_env_request_tie(pTHX_ request_rec *r) { EnvMgObjSet(r); EnvMgLenSet(-1); #ifdef MP_PERL_HV_GMAGICAL_AWARE MP_TRACE_e(MP_FUNC, "[%s/0x%lx] tie %%ENV, $r\t (%s%s)", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), modperl_server_desc(r->server, r->pool), r->uri); SvGMAGICAL_on((SV*)ENVHV); #endif } void modperl_env_request_untie(pTHX_ request_rec *r) { EnvMgObjSet(NULL); #ifdef MP_PERL_HV_GMAGICAL_AWARE MP_TRACE_e(MP_FUNC, "[%s/0x%lx] untie %%ENV; # from r\t (%s%s)", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), modperl_server_desc(r->server, r->pool), r->uri); SvGMAGICAL_off((SV*)ENVHV); #endif } /* to store the original virtual tables * these are global, not per-interpreter */ static MGVTBL MP_PERL_vtbl_env; static MGVTBL MP_PERL_vtbl_envelem; #define MP_PL_vtbl_call(name, meth) \ MP_PERL_vtbl_##name.svt_##meth(aTHX_ sv, mg) #define MP_dENV_KEY \ STRLEN klen; \ const char *key = (const char *)MgPV(mg,klen) #define MP_dENV_VAL \ STRLEN vlen; \ const char *val = (const char *)SvPV(sv,vlen) /* * XXX: what we do here might change: * - make it optional for %ENV to be tied to r->subprocess_env * - make it possible to modify environ * - we could allow modification of environ if mpm isn't threaded * - we could allow modification of environ if variable isn't a CGI * variable (still could cause problems) */ /* * problems we are trying to solve: * - environ is shared between threads * + Perl does not serialize access to environ * + even if it did, CGI variables cannot be shared between threads! * problems we create by trying to solve above problems: * - a forked process will not inherit the current %ENV * - C libraries might rely on environ, e.g. DBD::Oracle */ static int modperl_env_magic_set_all(pTHX_ SV *sv, MAGIC *mg) { request_rec *r = (request_rec *)EnvMgObj; if (r) { if (PL_localizing) { /* local %ENV = (FOO => 'bar', BIZ => 'baz') */ HE *entry; STRLEN n_a; hv_iterinit((HV*)sv); while ((entry = hv_iternext((HV*)sv))) { I32 keylen; apr_table_set(r->subprocess_env, hv_iterkey(entry, &keylen), SvPV(hv_iterval((HV*)sv, entry), n_a)); MP_TRACE_e(MP_FUNC, "[%s/0x%lx] localizing: %s => %s", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), hv_iterkey(entry, &keylen), SvPV(hv_iterval((HV*)sv, entry), n_a)); } } } else { #ifdef MP_TRACE HE *entry; STRLEN n_a; MP_TRACE_e(MP_FUNC, "\n\t[%lu/0x%lx] populating %%ENV:", (unsigned long)getpid(), modperl_interp_address(aTHX)); hv_iterinit((HV*)sv); while ((entry = hv_iternext((HV*)sv))) { I32 keylen; MP_TRACE_e(MP_FUNC, "$ENV{%s} = \"%s\";", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), hv_iterkey(entry, &keylen), SvPV(hv_iterval((HV*)sv, entry), n_a)); } #endif return MP_PL_vtbl_call(env, set); } return 0; } static int modperl_env_magic_clear_all(pTHX_ SV *sv, MAGIC *mg) { request_rec *r = (request_rec *)EnvMgObj; if (r) { apr_table_clear(r->subprocess_env); MP_TRACE_e(MP_FUNC, "[%s/0x%lx] clearing all magic off r->subprocess_env", modperl_pid_tid(r->pool), modperl_interp_address(aTHX)); } else { MP_TRACE_e(MP_FUNC, "[%s/0x%lx] %%ENV = ();", modperl_pid_tid(r->pool), modperl_interp_address(aTHX)); return MP_PL_vtbl_call(env, clear); } return 0; } static int modperl_env_magic_set(pTHX_ SV *sv, MAGIC *mg) { request_rec *r = (request_rec *)EnvMgObj; if (r) { MP_dENV_KEY; MP_dENV_VAL; apr_table_set(r->subprocess_env, key, val); MP_TRACE_e(MP_FUNC, "[%s/0x%lx] r->subprocess_env set: %s => %s", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), key, val); } else { #ifdef MP_TRACE MP_dENV_KEY; MP_dENV_VAL; MP_TRACE_e(MP_FUNC, "[%lu/0x%lx] $ENV{%s} = \"%s\";", (unsigned long)getpid(), modperl_interp_address(aTHX), key, val); #endif return MP_PL_vtbl_call(envelem, set); } return 0; } static int modperl_env_magic_clear(pTHX_ SV *sv, MAGIC *mg) { request_rec *r = (request_rec *)EnvMgObj; if (r) { MP_dENV_KEY; apr_table_unset(r->subprocess_env, key); MP_TRACE_e(MP_FUNC, "[%s/0x%lx] r->subprocess_env unset: %s", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), key); } else { #ifdef MP_TRACE MP_dENV_KEY; MP_TRACE_e(MP_FUNC, "[%lu/0x%lx] delete $ENV{%s};", (unsigned long)getpid(), modperl_interp_address(aTHX), key); #endif return MP_PL_vtbl_call(envelem, clear); } return 0; } #ifdef MP_PERL_HV_GMAGICAL_AWARE static int modperl_env_magic_get(pTHX_ SV *sv, MAGIC *mg) { request_rec *r = (request_rec *)EnvMgObj; if (r) { MP_dENV_KEY; const char *val; if ((val = apr_table_get(r->subprocess_env, key))) { sv_setpv(sv, val); MP_TRACE_e(MP_FUNC, "[%s/0x%lx] r->subprocess_env get: %s => %s", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), key, val); } else { sv_setsv(sv, &PL_sv_undef); MP_TRACE_e(MP_FUNC, "[%s/0x%lx] r->subprocess_env get: %s => undef", modperl_pid_tid(r->pool), modperl_interp_address(aTHX), key); } } else { /* there is no svt_get in PL_vtbl_envelem */ #ifdef MP_TRACE MP_dENV_KEY; MP_TRACE_e(MP_FUNC, "[%lu/0x%lx] there is no svt_get in PL_vtbl_envelem: %s", (unsigned long)getpid(), modperl_interp_address(aTHX), key); #endif } return 0; } #endif /* override %ENV virtual tables with our own */ static MGVTBL MP_vtbl_env = { 0, MEMBER_TO_FPTR(modperl_env_magic_set_all), 0, MEMBER_TO_FPTR(modperl_env_magic_clear_all), 0 }; static MGVTBL MP_vtbl_envelem = { 0, MEMBER_TO_FPTR(modperl_env_magic_set), 0, MEMBER_TO_FPTR(modperl_env_magic_clear), 0 }; void modperl_env_init(void) { /* save originals */ StructCopy(&PL_vtbl_env, &MP_PERL_vtbl_env, MGVTBL); StructCopy(&PL_vtbl_envelem, &MP_PERL_vtbl_envelem, MGVTBL); /* replace with our versions */ StructCopy(&MP_vtbl_env, &PL_vtbl_env, MGVTBL); StructCopy(&MP_vtbl_envelem, &PL_vtbl_envelem, MGVTBL); } void modperl_env_unload(void) { /* restore originals */ StructCopy(&MP_PERL_vtbl_env, &PL_vtbl_env, MGVTBL); StructCopy(&MP_PERL_vtbl_envelem, &PL_vtbl_envelem, MGVTBL); }