/*
* mod_spin.c
*
* Copyright (C) 2003 - 2008 Bojan Smojver, Rexursive
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
*/
#include "private.h"
#include
#include
#include
#include
#include
module AP_MODULE_DECLARE_DATA spin_module;
#define SPIN_MAGIC_TYPE "httpd/spin-template"
#define ERROR_BUF_SIZE 256
/* Private process data */
static rxv_spin_private_t *priv;
/* Regular expression for checking session syntax */
static ap_regex_t *ureg;
/* Cached template timeout */
#define TMPL_TMOUT (10*1000000)
/* Connection pool limits */
#define CONN_MAX 5
#define CONN_TMOUT (60*1000000)
static int conn_max=CONN_MAX;
/*
* Little helper functions...
*/
/* context creation utility */
static rxv_spin_ctx_t *context_create(request_rec *r){
rxv_spin_ctx_t *ctx=apr_pcalloc(r->pool,sizeof(*ctx));
ctx->r=r;
ctx->data=apr_pcalloc(r->pool,sizeof(*ctx->data));
ctx->data->size=RXV_SPIN_ROWS;
ctx->data->cols=apr_hash_make(r->pool);
ctx->closed=RXV_SPIN_TRUE;
return ctx;
}
/* scanner extra data creation/destruction */
static apr_status_t extra_destroy(void *data){
rxv_spin_extra_t *extra=data;
/* cleanup all leftover brigades */
apr_brigade_cleanup(extra->output);
if(extra->input) apr_brigade_cleanup(extra->input);
if(extra->split) apr_brigade_cleanup(extra->split);
if(extra->metabb) apr_brigade_cleanup(extra->metabb);
return APR_SUCCESS;
}
static rxv_spin_extra_t *extra_create(request_rec *r,rxv_spin_ctx_t *ctx){
apr_pool_t *pool;
rxv_spin_extra_t *extra=apr_pcalloc(r->pool,sizeof(*extra));
apr_pool_create(&pool,r->pool);
extra->pool=pool;
extra->loop=apr_pcalloc(r->pool,sizeof(*extra->loop)*RXV_SPIN_MAX_DEPTH);
extra->nest=apr_pcalloc(r->pool,sizeof(*extra->nest)*RXV_SPIN_MAX_DEPTH);
extra->row=1; extra->col=0;
extra->erow=extra->ecol=-1;
extra->buf=apr_pcalloc(r->pool,RXV_SPIN_SCAN_MAX+2);
extra->ctx=ctx?ctx:context_create(r);
extra->output=apr_brigade_create(r->pool,r->connection->bucket_alloc);
apr_pool_cleanup_register(r->pool,extra,extra_destroy,apr_pool_cleanup_null);
return extra;
}
/* template cleanups */
static apr_status_t tmpl_cleanup(void *data){
rxv_spin_tmpl_t *tmpl=data;
apr_hash_set(tmpl->priv->tmpls,tmpl->key,APR_HASH_KEY_STRING,NULL);
APR_RING_REMOVE(tmpl,link);
return APR_SUCCESS;
}
static apr_status_t cache_cleanup(void *data){
rxv_spin_tmpl_t *tmpl=data;
rxv_spin_lock(tmpl->priv);
tmpl->count--;
rxv_spin_unlock(tmpl->priv);
return APR_SUCCESS;
}
/* private process data cleanup */
static apr_status_t private_cleanup(void *data){
#if APR_HAS_THREADS
apr_status_t rv;
#endif
request_rec *r;
rxv_spin_tmpl_t *u,*q;
rxv_spin_pool_t *t,*p;
rxv_spin_trylock(priv,rv);
#if APR_HAS_THREADS
if(rv!=APR_SUCCESS) return APR_SUCCESS;
#endif
r=data;
/* clean timed out cached templates */
for(q=APR_RING_FIRST(&priv->tmpl);
q!=APR_RING_SENTINEL(&priv->tmpl,rxv_spin_tmpl,link);
q=u){
/* in use, don't touch */
if(q->count){
u=APR_RING_NEXT(q,link);
continue;
}
if(q->atime+TMPL_TMOUT>=r->request_time)
break;
u=APR_RING_NEXT(q,link); /* save next - we'll destroy the pool */
apr_pool_destroy(q->pool); /* this will unlink and remove from hash */
}
#if APR_HAS_THREADS && !defined(HAVE_APR_RESLIST_ACQUIRED_COUNT)
if(priv->mutex){
rxv_spin_unlock(priv);
return APR_SUCCESS;
}
#endif
/* clean over limit and timed out connections */
for(p=APR_RING_FIRST(&priv->conn);
p!=APR_RING_SENTINEL(&priv->conn,rxv_spin_pool,link);
p=t){
/* waiting to acquire resource, don't touch */
if(p->atime==APR_INT64_MIN){
t=APR_RING_NEXT(p,link);
continue;
}
if(p->atime+CONN_TMOUT>=r->request_time
#if APR_HAS_THREADS && defined(HAVE_APR_RESLIST_ACQUIRED_COUNT)
|| (priv->mutex && apr_reslist_acquired_count(p->list))
#endif
)
break;
t=APR_RING_NEXT(p,link); /* save next - we'll destroy the pool */
apr_pool_destroy(p->pool); /* this will unlink and remove from hash */
}
rxv_spin_unlock(priv);
return APR_SUCCESS;
}
/*
* Configuration...
*/
static void *create_config(apr_pool_t *p){
rxv_spin_config_t *conf=apr_pcalloc(p,sizeof(*conf));
conf->appinit="rxv_spin_init";
conf->appfixup="rxv_spin_prepare";
conf->appentry="rxv_spin_service";
conf->storetbl="spinstore";
conf->ckpath="/";
conf->cache=RXV_SPIN_TRUE;
conf->connpool=RXV_SPIN_TRUE;
return conf;
}
static void *create_config_server(apr_pool_t *p,server_rec *s){
return create_config(p);
}
static void *create_config_directory(apr_pool_t *p,char *dummy){
return create_config(p);
}
#define merge_var(var,flag) (new->set&(flag)?new->var:old->var)
static void *merge_config(apr_pool_t *p,void *o,void *n){
rxv_spin_config_t *old=(rxv_spin_config_t *)o,*new=(rxv_spin_config_t *)n,
*conf=apr_palloc(p,sizeof(*conf));
conf->application=merge_var(application,RXV_SPIN_APPLICATION_SET);
conf->appinit=merge_var(appinit,RXV_SPIN_APPINIT_SET);
conf->appfixup=merge_var(appfixup,RXV_SPIN_APPFIXUP_SET);
conf->appentry=merge_var(appentry,RXV_SPIN_APPENTRY_SET);
conf->cfgfn=merge_var(cfgfn,RXV_SPIN_CFGFN_SET);
conf->driver=merge_var(driver,RXV_SPIN_STORE_SET);
conf->store=merge_var(store,RXV_SPIN_STORE_SET);
conf->appfn=merge_var(appfn,RXV_SPIN_STORE_SET);
conf->storetbl=merge_var(storetbl,RXV_SPIN_STORETBL_SET);
conf->ckname=merge_var(ckname,RXV_SPIN_CKNAME_SET);
conf->ckpath=merge_var(ckpath,RXV_SPIN_CKPATH_SET);
conf->ckdomain=merge_var(ckdomain,RXV_SPIN_CKDOMAIN_SET);
conf->salt=merge_var(salt,RXV_SPIN_SALT_SET);
conf->oslt=merge_var(oslt,RXV_SPIN_OSLT_SET);
conf->timeout=merge_var(timeout,RXV_SPIN_TIMEOUT_SET);
conf->cache=merge_var(cache,RXV_SPIN_CACHE_SET);
conf->connpool=merge_var(connpool,RXV_SPIN_CONNPOOL_SET);
conf->set=new->set;
return conf;
}
static const char *set_application(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
if(!(conf->application=ap_server_root_relative(cmd->pool,arg)))
return apr_pstrcat(cmd->pool,"Invalid SpinApplication path: ",arg,NULL);
conf->set|=RXV_SPIN_APPLICATION_SET;
return NULL;
}
static const char *set_appinit(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
conf->appinit=apr_pstrdup(cmd->pool,arg);
conf->set|=RXV_SPIN_APPINIT_SET;
return NULL;
}
static const char *set_appfixup(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
conf->appfixup=apr_pstrdup(cmd->pool,arg);
conf->set|=RXV_SPIN_APPFIXUP_SET;
return NULL;
}
static const char *set_appentry(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
conf->appentry=apr_pstrdup(cmd->pool,arg);
conf->set|=RXV_SPIN_APPENTRY_SET;
return NULL;
}
static const char *set_appconfig(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
if(!(conf->cfgfn=ap_server_root_relative(cmd->pool,arg)))
return apr_pstrcat(cmd->pool,"Invalid SpinAppConfig path: ",arg,NULL);
conf->set|=RXV_SPIN_CFGFN_SET;
return NULL;
}
static const char *set_store(cmd_parms *cmd,void *data,const char *arg){
char *p;
rxv_spin_config_t *conf=data;
/* check if our store is file */
if(!((p=strchr(arg,':')) && *(p+1)))
return apr_pstrcat(cmd->pool,"Invalid SpinStore: ",arg,NULL);
conf->driver=apr_pstrmemdup(cmd->pool,arg,p-arg);
/* database backend */
if(strcmp(conf->driver,"file")){
conf->store=apr_pstrdup(cmd->pool,arg);
} else{ /* file backend */
if(!(conf->store=ap_server_root_relative(cmd->pool,p+1)))
return apr_pstrcat(cmd->pool,"Invalid SpinStore: ",arg,NULL);
else if(apr_filepath_merge(&conf->appfn,conf->store,"__application",
APR_FILEPATH_SECUREROOT,cmd->pool)!=APR_SUCCESS)
return apr_pstrcat(cmd->pool,"Invalid SpinStore: ",arg,NULL);
}
conf->set|=RXV_SPIN_STORE_SET;
return NULL;
}
static const char *set_storetbl(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
conf->storetbl=apr_pstrdup(cmd->pool,arg);
conf->set|=RXV_SPIN_STORETBL_SET;
return NULL;
}
static const char *set_ckname(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
conf->ckname=apr_pstrdup(cmd->pool,arg);
conf->set|=RXV_SPIN_CKNAME_SET;
return NULL;
}
static const char *set_ckpath(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
conf->ckpath=apr_pstrdup(cmd->pool,arg);
conf->set|=RXV_SPIN_CKPATH_SET;
return NULL;
}
static const char *set_ckdomain(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
conf->ckdomain=apr_pstrdup(cmd->pool,arg);
conf->set|=RXV_SPIN_CKDOMAIN_SET;
return NULL;
}
static const char *set_timeout(cmd_parms *cmd,void *data,const char *arg){
rxv_spin_config_t *conf=data;
if((conf->timeout=apr_atoi64(arg)*1000000)<0)
conf->timeout=0;
conf->set|=RXV_SPIN_TIMEOUT_SET;
return NULL;
}
static const char *set_cache(cmd_parms *cmd, void *data,const char *arg){
rxv_spin_config_t *conf=data;
if(!strcasecmp(arg,"on"))
conf->cache=RXV_SPIN_TRUE;
else if(!strcasecmp(arg,"off"))
conf->cache=RXV_SPIN_FALSE;
else
return apr_pstrcat(cmd->pool,"Invalid SpinCache option",arg,NULL);
conf->set|=RXV_SPIN_CACHE_SET;
return NULL;
}
static const char *set_connpool(cmd_parms *cmd, void *data,const char *arg){
rxv_spin_config_t *conf=data;
if(!strcasecmp(arg,"on"))
conf->connpool=RXV_SPIN_TRUE;
else if(!strcasecmp(arg,"off"))
conf->connpool=RXV_SPIN_FALSE;
else
return apr_pstrcat(cmd->pool,"Invalid SpinConnPool option",arg,NULL);
conf->set|=RXV_SPIN_CONNPOOL_SET;
return NULL;
}
static const char *set_conncount(cmd_parms *cmd, void *data,const char *arg){
const char *err;
if((err=ap_check_cmd_context(cmd,GLOBAL_ONLY)))
return err;
if((conn_max=atoi(arg))<=0) conn_max=CONN_MAX;
return NULL;
}
static const char *set_salt(cmd_parms *cmd, void *data,const char *arg){
rxv_spin_config_t *conf=data;
size_t len;
/* nothing to do, we've been here twice already */
if(conf->set&RXV_SPIN_OSLT_SET)
return NULL;
if((len=strlen(arg))pool,"SpinSalt too short (min 30 char): ",arg,NULL);
/* we've already seen salt once, so this must be the old one */
if(conf->set&RXV_SPIN_SALT_SET){
conf->oslt=apr_pcalloc(cmd->pool,RXV_SPIN_SALT_MAX+1);
memcpy(conf->oslt,arg,len>RXV_SPIN_SALT_MAX?RXV_SPIN_SALT_MAX:len);
conf->set|=RXV_SPIN_OSLT_SET;
} else{
conf->salt=apr_pcalloc(cmd->pool,RXV_SPIN_SALT_MAX+1);
memcpy(conf->salt,arg,len>RXV_SPIN_SALT_MAX?RXV_SPIN_SALT_MAX:len);
conf->set|=RXV_SPIN_SALT_SET;
}
return NULL;
}
/*
* Session validation functions
*/
static apr_status_t sesck_valid(apr_pool_t *pool,const char *uniq,char *hash,
rxv_spin_config_t *conf){
char *ndig;
/* make HMAC from unique ID */
if(!(ndig=rxv_spin_hmac(pool,uniq,conf->salt)))
return APR_EGENERAL;
if(strcmp(ndig,hash)){
char *odig;
/* new salt didn't work, try the old one */
if(!(conf->oslt && (odig=rxv_spin_hmac(pool,uniq,conf->oslt))) ||
strcmp(odig,hash))
return APR_EGENERAL;
/* old salt OK, but we need to replace the hash with the new one */
memcpy(hash,ndig,APR_MD5_DIGESTSIZE*2);
}
return APR_SUCCESS;
}
/*
* The other hooks...
*/
static int post_config(apr_pool_t *pconf,apr_pool_t *plog,
apr_pool_t *ptemp,server_rec *s){
ap_add_version_component(pconf,RXV_SPIN_PACKAGE_SIGNATURE);
return OK;
}
static void child_init(apr_pool_t *p,server_rec *s){
#if APR_HAS_THREADS
int rc;
#endif
apr_status_t rv;
/* initialise private process data */
priv=apr_pcalloc(p,sizeof(*priv));
priv->pool=p;
priv->dso=apr_hash_make(p);
priv->conns=apr_hash_make(p);
priv->tmpls=apr_hash_make(p);
priv->count=conn_max;
APR_RING_INIT(&priv->tmpl,rxv_spin_tmpl,link);
APR_RING_INIT(&priv->conn,rxv_spin_pool,link);
#if APR_HAS_THREADS
/* see if MPM is thread capable */
if((rv=ap_mpm_query(AP_MPMQ_IS_THREADED,&rc))!=APR_SUCCESS){
ap_log_error(APLOG_MARK,APLOG_CRIT,rv,s,
"mod_spin: cannot get MPM threading capability");
return;
}
if(rc!=AP_MPMQ_NOT_SUPPORTED){
/* create global thread mutex */
if((rv=apr_thread_mutex_create(&priv->mutex,
APR_THREAD_MUTEX_DEFAULT,p))!=APR_SUCCESS){
ap_log_error(APLOG_MARK,APLOG_CRIT,rv,s,
"mod_spin: cannot create mutex");
return;
}
}
#endif
/* initialise apr_dbd */
if((rv=apr_dbd_init(p))!=APR_SUCCESS){
ap_log_error(APLOG_MARK,APLOG_CRIT,rv,s,
"mod_spin: cannot initialise APR DBD");
return;
}
/* regular expression for checking the validity of session cookies */
ureg=ap_pregcomp(p,apr_psprintf(p,"^[a-p]{%d}$",APR_MD5_DIGESTSIZE*4),
AP_REG_EXTENDED|AP_REG_NOSUB);
/* check version and initialise XML parser */
LIBXML_TEST_VERSION
}
/*
* Configuration creation functions
*/
static rxv_spin_reqcf_t *req_config(request_rec *r){
rxv_spin_reqcf_t *rcnf;
rcnf=apr_pcalloc(r->pool,sizeof(*rcnf));
ap_set_module_config(r->request_config,&spin_module,rcnf);
return rcnf;
}
static int app_config(request_rec *r,rxv_spin_config_t *conf,
rxv_spin_reqcf_t *rcnf,rxv_spin_ctx_t *ctx){
apr_status_t rv;
apr_dso_handle_t *handle=NULL;
apr_dso_handle_sym_t init=NULL;
/* load the DSO, either from cache or new */
if((rv=rxv_spin_dso_load(ctx,conf->application,&handle))!=APR_SUCCESS){
if(handle){
char *error=apr_palloc(r->pool,ERROR_BUF_SIZE);
apr_dso_error(handle,error,ERROR_BUF_SIZE);
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,"mod_spin: %s",error);
} else{
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: cannot load %s",conf->application);
}
return HTTP_INTERNAL_SERVER_ERROR;
}
if((rv=apr_dso_sym(&rcnf->svc,handle,conf->appentry)!=APR_SUCCESS)){
char *error=apr_palloc(r->pool,ERROR_BUF_SIZE);
apr_dso_error(handle,error,ERROR_BUF_SIZE);
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,"mod_spin: %s",error);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* we don't care if fixup doesn't exist, so no error checking */
apr_dso_sym(&rcnf->fix,handle,conf->appfixup);
/* initialise the app */
if(apr_dso_sym(&init,handle,conf->appinit)==APR_SUCCESS){
rxv_spin_lock(priv);
(*((rxv_spin_init_t)init))(ctx);
rxv_spin_unlock(priv);
}
/* put context into the request configuration */
rcnf->ctx=ctx;
return OK;
}
/*
* The fixup hook...
*/
static int fixup(request_rec *r){
int rc;
rxv_spin_config_t *conf;
rxv_spin_reqcf_t *rcnf;
rxv_spin_ctx_t *ctx;
/* if the configuration is missing, big trouble */
if(!(conf=ap_get_module_config(r->per_dir_config,&spin_module))){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
"mod_spin: missing configuration data");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* make sure connection pool is cleaned up */
apr_pool_cleanup_register(r->pool,r,private_cleanup,apr_pool_cleanup_null);
/* none of our business, don't handle */
if(!(conf->application || conf->store || conf->ckname))
return DECLINED;
/* get request configuration, or create it if not there */
if(!(rcnf=ap_get_module_config(r->request_config,&spin_module)) &&
!(rcnf=req_config(r)))
return HTTP_INTERNAL_SERVER_ERROR;
/* prepare session identifier if configured */
if(conf->ckname && conf->salt){
apreq_handle_t *req=apreq_handle_apache2(r);
/* get/set session identifier and serve it back to the client */
if(req){
char *sesck=NULL,*uniq,*hash;
apreq_cookie_t *cookie;
/* we have session ID already */
if((cookie=apreq_jar_get(req,conf->ckname))){
/* extract session from he cookie */
if(*cookie->v.data=='"' && *(cookie->v.data+cookie->v.dlen-1)=='"')
sesck=apr_pstrmemdup(r->pool,cookie->v.data+1,cookie->v.dlen-2);
else
sesck=apr_pstrmemdup(r->pool,cookie->v.data,cookie->v.dlen);
/* verify session ID */
if(!sesck || ap_regexec(ureg,sesck,0,NULL,0)){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EACCES,r,
"mod_spin: invalid session identifier");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* unique id will be session identifier, hash is the rest */
hash=sesck+APR_MD5_DIGESTSIZE*2;
uniq=apr_pstrmemdup(r->pool,sesck,APR_MD5_DIGESTSIZE*2);
/* do crypto validation against new and possibly old salt */
if(sesck_valid(r->pool,uniq,hash,conf)!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EACCES,r,
"mod_spin: invalid session identifier");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* all clear, we have the session and it is valid */
rcnf->sesid=uniq;
rcnf->valid=RXV_SPIN_TRUE;
} else if(!r->main){ /* we have to build session ID ourselves */
if((uniq=(char *)apr_table_get(r->subprocess_env,"UNIQUE_ID"))){
if(!((uniq=rxv_spin_hash(r->pool,uniq)) &&
(hash=rxv_spin_hmac(r->pool,uniq,conf->salt)))){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r,
"mod_spin: hashing failed");
return HTTP_INTERNAL_SERVER_ERROR;
}
sesck=apr_pstrcat(r->pool,uniq,hash,NULL);
}
/* set session, but not valid yet */
rcnf->sesid=uniq;
}
/* make and bake the cookie */
if(sesck){
char *scookie;
if(!(cookie=apreq_cookie_make(r->pool,
conf->ckname,strlen(conf->ckname),
sesck,APR_MD5_DIGESTSIZE*4))){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r,
"mod_spin: cannot make session cookie");
return HTTP_INTERNAL_SERVER_ERROR;
}
cookie->path=conf->ckpath;
if(conf->ckdomain)
cookie->domain=conf->ckdomain;
if(!(scookie=apreq_cookie_as_string(cookie,r->pool))){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r,
"mod_spin: cannot serialise session cookie");
return HTTP_INTERNAL_SERVER_ERROR;
}
apr_table_addn(r->err_headers_out,"Set-Cookie",scookie);
}
}
}
/* stop processing here for sub-requests */
if(r->main)
return DECLINED;
/* we have to have minimum configuration to do anything */
if(conf->store || conf->application){
ctx=context_create(r);
ctx->priv=priv;
ctx->conf=conf;
ctx->rcnf=rcnf;
ctx->havest=(conf->driver?RXV_SPIN_TRUE:RXV_SPIN_FALSE);
ctx->dbstor=(ctx->havest?(strcmp(conf->driver,"file")?RXV_SPIN_TRUE:
RXV_SPIN_FALSE):
RXV_SPIN_FALSE);
/* find and execute prepare function */
if(conf->application){
if((rc=app_config(r,conf,rcnf,ctx))!=OK)
return rc;
/* call prepare function only if we have one */
if(rcnf->fix){
rc=(*((rxv_spin_prepare_t)rcnf->fix))(ctx);
if(ap_is_HTTP_ERROR(rc)){
ctx->havest=RXV_SPIN_FALSE; /* don't save to store on error */
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
"mod_spin: application error in prepare");
return rc;
}
return rc;
}
}
}
return OK;
}
/*
* The handlers...
*/
static int handler(request_rec *r){
int rc;
char *key;
apr_status_t rv;
rxv_spin_config_t *conf;
rxv_spin_reqcf_t *rcnf;
rxv_spin_extra_t *extra;
rxv_spin_ctx_t *ctx;
apr_file_t *fp;
apr_finfo_t *fi;
apr_mmap_t *mm;
apr_bucket *b;
/* don't do what isn't ours */
if(strcmp(r->handler,"spin-template") && strcmp(r->handler,SPIN_MAGIC_TYPE))
return DECLINED;
/* if the configuration is missing, big trouble */
if(!(conf=ap_get_module_config(r->per_dir_config,&spin_module))){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
"mod_spin: missing configuration data");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* request configuration, or create if not there */
if(!(rcnf=ap_get_module_config(r->request_config,&spin_module)) &&
!(rcnf=req_config(r)))
return HTTP_INTERNAL_SERVER_ERROR;
/* build the necessary memory structures */
extra=extra_create(r,rcnf->ctx);
/* some important shortcuts before anything else */
ctx=extra->ctx;
/* if we didn't populate context during the fixup phase, do it now */
if(!rcnf->ctx){
ctx->priv=priv;
ctx->conf=conf;
ctx->rcnf=rcnf;
ctx->havest=(conf->driver?RXV_SPIN_TRUE:RXV_SPIN_FALSE);
ctx->dbstor=(ctx->havest?(strcmp(conf->driver,"file")?RXV_SPIN_TRUE:
RXV_SPIN_FALSE):
RXV_SPIN_FALSE);
}
/* grab the apreq handle for cookie and parameter parsing */
ctx->req=apreq_handle_apache2(r);
/* run input filters and discard the body, all access via libapreq2 */
if((rc=ap_discard_request_body(r))!=OK){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
"mod_spin: cannot run input filters");
return rc;
}
/* find and execute application */
if(conf->application){
if(!rcnf->ctx && (rc=app_config(r,conf,rcnf,ctx))!=OK)
return rc;
/* call entry function */
rc=(*((rxv_spin_service_t)rcnf->svc))(ctx);
if(ap_is_HTTP_ERROR(rc)){
ctx->havest=RXV_SPIN_FALSE; /* don't save to store on error */
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
"mod_spin: application error in service");
return rc;
}
/* not a failure, but don't process the template */
if(rc!=OK)
return rc;
} else{
ap_log_rerror(APLOG_MARK,APLOG_INFO,APR_SUCCESS,r,
"mod_spin: application not specified");
}
/* check existence of the template */
if(!r->finfo.filetype){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOENT,r,
"mod_spin: file does not exist: %s",r->filename);
return HTTP_NOT_FOUND;
}
/* always open file - we want to be atomic at inode level */
if((rv=apr_file_open(&fp,r->filename,APR_FOPEN_READ,
APR_FPROT_OS_DEFAULT,r->pool))!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: cannot open file: %s",r->filename);
return HTTP_FORBIDDEN;
}
/* get the size of the file, for the mmap below and mtime for caching */
fi=apr_pcalloc(r->pool,sizeof(*fi));
if((rv=apr_file_info_get(fi,APR_FINFO_SIZE|APR_FINFO_MTIME,fp))!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: cannot stat file: %s",r->filename);
return HTTP_FORBIDDEN;
}
/* mmap the file */
if((rv=apr_mmap_create(&mm,fp,
0,fi->size,APR_MMAP_READ,r->pool))!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: cannot mmap file: %s",r->filename);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* close file to save file descriptors */
apr_file_close(fp);
/* mmap bucket that we'll share around */
if(!(b=apr_bucket_mmap_create(mm,0,fi->size,extra->output->bucket_alloc))){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r,
"mod_spin: cannot create mmap bucket");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* create brigade for scanning */
extra->input=apr_brigade_create(r->pool,extra->output->bucket_alloc);
APR_BRIGADE_INSERT_HEAD(extra->input,b);
APR_BUCKET_INSERT_AFTER(b,apr_bucket_eos_create(extra->output->bucket_alloc));
/* get/prepare cache template, if enabled */
if(conf->cache){
rxv_spin_tmpl_t *tmpl;
/* do we have this in cache? */
key=apr_psprintf(r->pool,"%" APR_TIME_T_FMT "%" APR_OFF_T_FMT "%s",
fi->mtime,fi->size,r->filename);
rxv_spin_lock(priv);
if((tmpl=apr_hash_get(priv->tmpls,key,APR_HASH_KEY_STRING))){
if(r->request_time>tmpl->atime) tmpl->atime=r->request_time;
APR_RING_REMOVE(tmpl,link);
APR_RING_INSERT_TAIL(&priv->tmpl,tmpl,rxv_spin_tmpl,link);
} else{
apr_bucket_brigade *input;
apr_pool_t *tpool,*epool=extra->pool;
rxv_spin_root_t **root;
apr_pool_create(&tpool,priv->pool);
tmpl=apr_pcalloc(tpool,sizeof(*tmpl));
tmpl->pool=tpool;
tmpl->key=apr_pstrdup(tpool,key);
tmpl->priv=priv;
tmpl->atime=r->request_time;
input=extra->input;
root=&tmpl->root;
extra->pool=tpool; /* for the duration of the parse */
while(extra->input){
/* reset root, count of buckets to queue */
extra->root=NULL;
extra->count=0;
if((rv=rxv_spin_build(extra))!=APR_SUCCESS){
apr_pool_destroy(tpool);
if(rv==APR_EACCES){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: permission denied: %s",r->filename);
return HTTP_FORBIDDEN;
} else{
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: parsing error: %s: %s",
r->filename,extra->error?extra->error:"unspecified");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
/* store parsed root */
if(extra->root){
*root=apr_pcalloc(tpool,sizeof(**root));
(*root)->root=extra->root;
root=&(*root)->next;
}
}
extra->pool=epool; /* restore real extra pool */
extra->input=input;
APR_RING_INSERT_TAIL(&priv->tmpl,tmpl,rxv_spin_tmpl,link);
apr_hash_set(priv->tmpls,tmpl->key,APR_HASH_KEY_STRING,tmpl);
apr_pool_cleanup_register(tpool,tmpl,tmpl_cleanup,apr_pool_cleanup_null);
}
tmpl->count++;
rxv_spin_unlock(priv);
extra->cache=tmpl;
apr_pool_cleanup_register(r->pool,tmpl,cache_cleanup,apr_pool_cleanup_null);
}
while(extra->input){
/* reset root, count of buckets to queue and clear node pool */
extra->root=NULL;
extra->count=0;
apr_pool_clear(extra->pool);
if((rv=rxv_spin_build(extra))!=APR_SUCCESS){
if(rv==APR_EACCES){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: permission denied: %s",r->filename);
return HTTP_FORBIDDEN;
} else{
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: parsing error: %s: %s",
r->filename,extra->error?extra->error:"unspecified");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
/* finally, process the template */
if(extra->root){
apr_bucket *b=extra->scan;
/* we may have bumped into EOS while scanning, back it up */
if(APR_BUCKET_IS_EOS(extra->scan))
extra->scan=APR_BUCKET_PREV(extra->scan);
if((rv=rxv_spin_render(extra->root,extra))!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: template processing error: %s: %s",
r->filename,extra->error?extra->error:"unspecified");
return HTTP_INTERNAL_SERVER_ERROR;
}
extra->scan=b;
/* very end, add EOS bucket */
if(extra->eos && !extra->input){
APR_BUCKET_REMOVE(extra->eos);
APR_BRIGADE_INSERT_TAIL(extra->output,extra->eos);
}
if(!APR_BRIGADE_EMPTY(extra->output) &&
(rv=ap_pass_brigade(r->output_filters,extra->output))!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: cannot pass brigade to filters");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
/* free up some space */
apr_brigade_cleanup(extra->output);
}
return OK;
}
/*
* The filters...
*/
static apr_status_t filter(ap_filter_t *f,apr_bucket_brigade *b){
apr_status_t rv;
request_rec *r=f->r;
rxv_spin_fltcf_t *fcnf=f->ctx;
rxv_spin_extra_t *extra;
/* fast exit */
if(APR_BRIGADE_EMPTY(b))
return APR_SUCCESS;
/* been here before, just pick up existing stuff */
if(fcnf){
extra=fcnf->extra;
/* reset positions, EOF flag, error etc. */
extra->offset=0;
extra->pos=-1;
extra->soff=0;
extra->blen=extra->bpos=0;
extra->eof=NULL;
extra->error=NULL;
extra->split=NULL;
extra->depth=extra->ldepth=0;
/* initialise parsing variables */
memset(extra->loop,0,sizeof(*extra->loop)*RXV_SPIN_MAX_DEPTH);
memset(extra->nest,0,sizeof(*extra->nest)*RXV_SPIN_MAX_DEPTH);
} else{ /* first time through, create filter config */
int rc;
rxv_spin_config_t *conf;
rxv_spin_reqcf_t *rcnf;
rxv_spin_ctx_t *ctx;
/* new filter context */
f->ctx=fcnf=apr_pcalloc(r->pool,sizeof(*fcnf));
/* if the configuration is missing, big trouble */
if(!(conf=ap_get_module_config(r->per_dir_config,&spin_module))){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
"mod_spin: missing configuration data");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
/* request configuration, or create if not there */
if(!(rcnf=ap_get_module_config(r->request_config,&spin_module)) &&
!(rcnf=req_config(r))){
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
/* build the necessary memory structures */
fcnf->extra=extra=extra_create(r,rcnf->ctx);
/* some important shortcuts before anything else */
ctx=extra->ctx;
/* if we didn't populate context during the fixup phase, do it now */
if(!rcnf->ctx){
ctx->priv=priv;
ctx->conf=conf;
ctx->rcnf=rcnf;
ctx->havest=(conf->driver?RXV_SPIN_TRUE:RXV_SPIN_FALSE);
ctx->dbstor=(ctx->havest?(strcmp(conf->driver,"file")?RXV_SPIN_TRUE:
RXV_SPIN_FALSE):
RXV_SPIN_FALSE);
}
/* grab the apreq handle for cookie and parameter parsing */
ctx->req=apreq_handle_apache2(r);
/* run input filters and discard the body, all access via libapreq2 */
if((rc=ap_discard_request_body(r))!=OK){
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
"mod_spin: cannot run input filters");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
/* find and execute application */
if(conf->application){
if(!rcnf->ctx && (rc=app_config(r,conf,rcnf,ctx))!=OK){
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
/* call entry function */
rc=(*((rxv_spin_service_t)rcnf->svc))(ctx);
/* anything but OK is an error here */
if(rc!=OK){
ctx->havest=RXV_SPIN_FALSE; /* don't save to store on error */
ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r,
ap_is_HTTP_ERROR(rc)?
"mod_spin: application error in service":
"mod_spin: application returned an invalid code");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
} else{
ap_log_rerror(APLOG_MARK,APLOG_INFO,APR_SUCCESS,r,
"mod_spin: application not specified");
}
/* filter specific knickknacks */
extra->f=f;
extra->metabb=apr_brigade_create(r->pool,r->connection->bucket_alloc);
}
/* concatenate saved and input brigade */
if(extra->input)
APR_BRIGADE_CONCAT(extra->input,b);
else
extra->input=b;
/* we are going to modify content, so this must be unset */
apr_table_unset(r->headers_out,"Content-Length");
apr_table_unset(r->headers_out,"Last-Modified");
while(extra->input){
/* reset root, count of buckets to queue and clear node pool */
extra->root=NULL;
extra->count=0;
apr_pool_clear(extra->pool);
if((rv=rxv_spin_build(extra))!=APR_SUCCESS){
if(rv==APR_EACCES)
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: permission denied: %s",r->filename);
else
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: parsing error: %s: %s",
r->filename,extra->error?extra->error:"unspecified");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
/* finally, process the template */
if(extra->root){
if((rv=rxv_spin_render(extra->root,extra))!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: template processing error: %s: %s",
r->filename,extra->error?extra->error:"unspecified");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
/* very end, add EOS bucket */
if(extra->eos && !extra->input){
APR_BUCKET_REMOVE(extra->eos);
APR_BRIGADE_INSERT_TAIL(extra->metabb,extra->eos);
}
/* glue on metadata */
APR_BRIGADE_CONCAT(extra->output,extra->metabb);
if(!APR_BRIGADE_EMPTY(extra->output) &&
(rv=ap_pass_brigade(f->next,extra->output))!=APR_SUCCESS){
ap_log_rerror(APLOG_MARK,APLOG_ERR,rv,r,
"mod_spin: cannot pass brigade to filters");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next,b);
}
}
/* free up some space */
apr_brigade_cleanup(extra->output);
}
/* setaside split brigade, if any */
if(extra->split)
ap_save_brigade(f,&extra->input,&extra->split,r->pool);
/* get rid of the original brigade */
apr_brigade_cleanup(b);
return APR_SUCCESS;
}
/*
* Directives...
*/
static const command_rec spin_commands[]={
AP_INIT_TAKE1("SpinApplication",set_application,NULL,ACCESS_CONF|RSRC_CONF,
"path to the shared library of the application"),
AP_INIT_TAKE1("SpinAppInit",set_appinit,NULL,ACCESS_CONF|RSRC_CONF,
"name of the init function in the shared library"),
AP_INIT_TAKE1("SpinAppPrepare",set_appfixup,NULL,ACCESS_CONF|RSRC_CONF,
"name of the prepare function in the shared library"),
AP_INIT_TAKE1("SpinAppService",set_appentry,NULL,ACCESS_CONF|RSRC_CONF,
"name of the service function in the shared library"),
AP_INIT_TAKE1("SpinAppConfig",set_appconfig,NULL,ACCESS_CONF|RSRC_CONF,
"path to the configuration file"),
AP_INIT_TAKE1("SpinStore",set_store,NULL,ACCESS_CONF|RSRC_CONF,
"store connect string"),
AP_INIT_TAKE1("SpinStoreTable",set_storetbl,NULL,ACCESS_CONF|RSRC_CONF,
"store table name"),
AP_INIT_TAKE1("SpinCookie",set_ckname,NULL,ACCESS_CONF|RSRC_CONF,
"name of the cookie for session tracking"),
AP_INIT_TAKE1("SpinCookiePath",set_ckpath,NULL,ACCESS_CONF|RSRC_CONF,
"path of the cookie for session tracking"),
AP_INIT_TAKE1("SpinCookieDomain",set_ckdomain,NULL,ACCESS_CONF|RSRC_CONF,
"domain of the cookie for session tracking"),
AP_INIT_TAKE1("SpinTimeout",set_timeout,NULL,ACCESS_CONF|RSRC_CONF,
"session timeout in seconds"),
AP_INIT_TAKE1("SpinCache",set_cache,NULL,ACCESS_CONF|RSRC_CONF,
"should templates be cached"),
AP_INIT_TAKE1("SpinConnPool",set_connpool,NULL,ACCESS_CONF|RSRC_CONF,
"should connections be pooled"),
AP_INIT_TAKE1("SpinConnCount",set_conncount,NULL,RSRC_CONF,
"maximum number of connections in the pool"),
AP_INIT_TAKE1("SpinSalt",set_salt,NULL,ACCESS_CONF|RSRC_CONF,
"cryptographic salt"),
{NULL}
};
static void register_hooks(apr_pool_t *p){
ap_hook_post_config(post_config,NULL,NULL,APR_HOOK_MIDDLE);
ap_hook_child_init(child_init,NULL,NULL,APR_HOOK_MIDDLE);
ap_hook_fixups(fixup,NULL,NULL,APR_HOOK_MIDDLE);
ap_hook_handler(handler,NULL,NULL,APR_HOOK_MIDDLE);
ap_register_output_filter("SPIN-TEMPLATE",filter,NULL,AP_FTYPE_RESOURCE);
}
module AP_MODULE_DECLARE_DATA spin_module={
STANDARD20_MODULE_STUFF,
create_config_directory, /* create per-dir config */
merge_config, /* merge per-dir config */
create_config_server, /* server config */
merge_config, /* merge server config */
spin_commands, /* command apr_table_t */
register_hooks /* register hooks */
};