/** * mod_spin.c * * Copyright (C) 2003 - 2005 Bojan Smojver, Rexursive * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licence as published by the Free * Software Foundation; either version 2 of the Licence, 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 General Public Licence for * more details. * * You should have received a copy of the GNU General Public Licence along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA * * In addition, as two special exceptions, Bojan Smojver, Rexursive, gives * permission to: * * 1. Link, both statically and dynamically, the code of this program with * Apache HTTP Server, Apache Portable Runtime and Apache Portable Runtime * Utility Library from Apache Software Foundation (or with modified versions * of the above, that use the same licence - Apache Software Licence 1.1, 2.0 * or any later version), and distribute linked combinations including the * program and the above software from Apache Software Foundation. You must * obey the GNU General Public Licence in all respects for all of the code * used other than Apache HTTP Server, Apache Portable Runtime and Apache * Portable Runtime Utility Library. * * 2. Dynamically link any shared library with this program at run-time, * through the interface of SpinApplication/SpinAppEntry or LoadModule * run-time configuration directives of Apache HTTP Server, as provided by * this program or Apache HTTP Server itself, regardless of licensing terms of * those shared libraries. You must obey the GNU General Public Licence in all * respects for all of the code used other than that of those shared * libraries. * * If you modify this file, you may extend these exceptions to your version of * the file, but you are not obligated to do so. If you do not wish to do so, * delete one or both of these exception statements from your version. * */ #include "private.h" #include #include #include #include module AP_MODULE_DECLARE_DATA spin_module; #define SPIN_MAGIC_TYPE "httpd/spin-template" #define ERROR_BUF_SIZE 256 /* Process specific pool */ static apr_pool_t *ppool; /* Thread private key */ static apr_threadkey_t *tkey; /* Crypto items for session */ #define SALT_MIN_SIZE 30 #define MD5_B64_SIZE 25 static char *salt,*oslt; /* Count after which we dump the caches */ static apr_uint64_t clearcnt; /* * Configuration... */ static void *create_config(apr_pool_t *p){ rxv_spin_config_t *conf=apr_pcalloc(p,sizeof(*conf)); conf->appentry="rxv_spin_service"; conf->sendfile=1; 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->appentry=merge_var(appentry,RXV_SPIN_APPENTRY_SET); conf->cfgfn=merge_var(cfgfn,RXV_SPIN_CFGFN_SET); conf->workspace=merge_var(workspace,RXV_SPIN_WORKSPACE_SET); conf->cookie=merge_var(cookie,RXV_SPIN_COOKIE_SET); conf->basfn=merge_var(basfn,RXV_SPIN_WORKSPACE_SET); conf->pagfn=merge_var(pagfn,RXV_SPIN_WORKSPACE_SET); conf->sendfile=merge_var(sendfile,RXV_SPIN_SENDFILE_SET); conf->cacheall=merge_var(cacheall,RXV_SPIN_CACHEALL_SET); conf->set=new->set; return conf; } static const char *set_application(cmd_parms *cmd,void *conf_v,const char *arg){ rxv_spin_config_t *conf=(rxv_spin_config_t *)conf_v; 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_appentry(cmd_parms *cmd,void *conf_v,const char *arg){ rxv_spin_config_t *conf=(rxv_spin_config_t *)conf_v; if(!(conf->appentry=apr_pstrdup(cmd->pool,arg))) return apr_pstrcat(cmd->pool,"Invalid SpinAppEntry: ",arg,NULL); conf->set|=RXV_SPIN_APPENTRY_SET; return NULL; } static const char *set_appconfig(cmd_parms *cmd,void *conf_v,const char *arg){ rxv_spin_config_t *conf=(rxv_spin_config_t *)conf_v; 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_workspace(cmd_parms *cmd,void *conf_v,const char *arg){ rxv_spin_config_t *conf=(rxv_spin_config_t *)conf_v; if(!(conf->workspace=ap_server_root_relative(cmd->pool,arg))) return apr_pstrcat(cmd->pool,"Invalid SpinWorkspace path: ",arg,NULL); if(apr_filepath_merge(&conf->basfn,conf->workspace,"__app", APR_FILEPATH_SECUREROOT,cmd->pool)!=APR_SUCCESS) return apr_pstrcat(cmd->pool,"Cannot construct store base path: ",arg,NULL); if(!(conf->pagfn=apr_pstrcat(cmd->pool,conf->basfn,APR_SDBM_PAGFEXT,NULL))) return apr_pstrcat(cmd->pool,"Cannot construct store page path: ",arg,NULL); conf->set|=RXV_SPIN_WORKSPACE_SET; return NULL; } static const char *set_cookie(cmd_parms *cmd,void *conf_v,const char *arg){ rxv_spin_config_t *conf=(rxv_spin_config_t *)conf_v; if(!(conf->cookie=apr_pstrdup(cmd->pool,arg))) return "Cannot allocate session cookie"; conf->set|=RXV_SPIN_COOKIE_SET; return NULL; } static const char *set_sendfile(cmd_parms *cmd, void *conf_v,const char *arg) { rxv_spin_config_t *conf=(rxv_spin_config_t *)conf_v; if(!strcasecmp(arg,"on")) conf->sendfile=1; else if(!strcasecmp(arg,"off")) conf->sendfile=0; else return apr_pstrcat(cmd->pool,"Invalid SpinSendfile option",arg,NULL); conf->set|=RXV_SPIN_SENDFILE_SET; return NULL; } static const char *set_cacheall(cmd_parms *cmd, void *conf_v,const char *arg) { rxv_spin_config_t *conf=(rxv_spin_config_t *)conf_v; if(!strcasecmp(arg,"on")) conf->cacheall=1; else if(!strcasecmp(arg,"off")) conf->cacheall=0; else return apr_pstrcat(cmd->pool,"Invalid SpinCacheAll option",arg,NULL); conf->set|=RXV_SPIN_CACHEALL_SET; return NULL; } static const char *set_clearcnt(cmd_parms *cmd, void *conf_v,const char *arg) { const char *err; if((err=ap_check_cmd_context(cmd,GLOBAL_ONLY))) return err; clearcnt=apr_atoi64(arg); return NULL; } static const char *set_salt(cmd_parms *cmd, void *conf_v,const char *arg) { const char *err; if((err=ap_check_cmd_context(cmd,GLOBAL_ONLY))) return err; /* nothing to do, we've been here twice already */ if(oslt) return NULL; if(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(salt){ if(!(oslt=apr_pstrdup(cmd->pool,arg))) return "Cannot allocate old crypto salt"; } else{ if(!(salt=apr_pstrdup(cmd->pool,arg))) return "Cannot allocate new crypto salt"; } return NULL; } /* * MD5 crypto functions */ static char *md5b64(apr_pool_t *pool,const char *uniq,const char *salt){ unsigned char *digest,*b64dig; apr_md5_ctx_t *ctx; if(!((ctx=apr_palloc(pool,sizeof(*ctx))) && (digest=apr_palloc(pool,APR_MD5_DIGESTSIZE+1)) && (b64dig=apr_palloc(pool,MD5_B64_SIZE+1)))) return NULL; apr_md5_init(ctx); if(apr_md5_update(ctx,uniq,strlen(uniq))!=APR_SUCCESS || apr_md5_update(ctx,salt,strlen(salt))!=APR_SUCCESS) return NULL; apr_md5_final(digest,ctx); if(apr_base64_encode((char*)b64dig, (char*)digest,APR_MD5_DIGESTSIZE)!=MD5_B64_SIZE) return NULL; return (char*)b64dig; } static apr_status_t md5b64_validate(apr_pool_t *pool,const char *uniq, char *b64dig,size_t b64diglen){ char *ub64dig; if(b64diglen!=MD5_B64_SIZE-1 || !(ub64dig=md5b64(pool,uniq,salt))) return APR_EGENERAL; if(strcmp(ub64dig,b64dig)){ char *ob64dig; /* new salt didn't work, try the old one */ if(!(oslt && (ob64dig=md5b64(pool,uniq,oslt))) || strcmp(ob64dig,b64dig)) return APR_EGENERAL; /* old salt OK, but we need to replace the hash with the new one */ apr_cpystrn(b64dig,ub64dig,b64diglen+1); } return APR_SUCCESS; } /* * The handlers... */ static int handler(request_rec *r){ int sstatus; conn_rec *c=r->connection; rxv_spin_config_t *conf; rxv_spin_conncf_t *ccnf; rxv_spin_reqcf_t *rcnf; apr_status_t status; apr_dso_handle_t *handle=NULL; apr_dso_handle_sym_t shandle; rxv_spin_extra_t *extra; rxv_spin_context_t *ctx; rxv_spin_guts_t *guts; rxv_spin_private_t *priv; if(strcmp(r->handler,"spin-template") && strcmp(r->handler,SPIN_MAGIC_TYPE)) return DECLINED; /* get module, connection and request configration */ conf=ap_get_module_config(r->per_dir_config,&spin_module); ccnf=ap_get_module_config(c->conn_config,&spin_module); rcnf=ap_get_module_config(r->request_config,&spin_module); /* if the big two are missing, big trouble */ if(!(conf && ccnf)){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r, "mod_spin: missing configuration data"); return HTTP_INTERNAL_SERVER_ERROR; } /* build the necessary memory structures */ if(!(extra=rxv_spin_extra_create(r->pool,c->bucket_alloc))){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r, "mod_spin: cannot allocate scanner extra data"); return HTTP_INTERNAL_SERVER_ERROR; } /* some important shortcuts before anything else */ ctx=extra->ctx; guts=ctx->guts; priv=ccnf->priv; /* verify workspace path and set application/session database filenames */ if(conf->workspace){ apr_finfo_t *finfo; if(!(finfo=apr_pcalloc(r->pool,sizeof(*finfo)))){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r, "mod_spin: cannot allocate file info structure"); return HTTP_INTERNAL_SERVER_ERROR; } /* security checks on the workspace directory */ if((status=apr_stat(finfo,conf->workspace, APR_FINFO_LINK|APR_FINFO_PROT,r->pool))!=APR_SUCCESS){ ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: cannot stat workspace directory"); return HTTP_INTERNAL_SERVER_ERROR; } if(finfo->filetype!=APR_DIR){ ap_log_rerror(APLOG_MARK,APLOG_CRIT,APR_ENOTDIR,r, "mod_spin: workspace not a directory"); return HTTP_INTERNAL_SERVER_ERROR; } if(finfo->protection & (APR_GREAD|APR_GWRITE|APR_GEXECUTE|APR_WREAD|APR_WWRITE|APR_WEXECUTE)){ ap_log_rerror(APLOG_MARK,APLOG_CRIT,APR_EACCES,r, "mod_spin: workspace not read/write/execute by owner only"); return HTTP_INTERNAL_SERVER_ERROR; } /* make sure there are no path tricks in the session identifier */ if(rcnf && rcnf->sesid && rcnf->valid && (status=apr_filepath_merge(&guts->sesfn,conf->workspace,rcnf->sesid, APR_FILEPATH_SECUREROOT, r->pool))!=APR_SUCCESS){ ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: session database path merge failed"); return HTTP_INTERNAL_SERVER_ERROR; } guts->appfn=conf->basfn; guts->pagfn=conf->pagfn; } /* looking good so far, set sendfile and cache flags */ extra->sendfile=conf->sendfile; extra->cacheall=conf->cacheall; /* if counting, maybe we need to schedule thread private pool for clearing */ if(clearcnt>0 && priv->count++>clearcnt) priv->kill=1; /* put template cache and used templates into the scanner structure */ extra->cache=priv->cache; extra->used=ccnf->used; /* put database connection pool into the context */ ctx->cpool=priv->cpool; /* put raw request, parsed request and cookie data from apreq into context */ ctx->r=r; /* set configuration file filename */ guts->cfgfn=conf->cfgfn; /* grab the apreq handle for cookie and parameter parsing */ ctx->req=apreq_handle_apache2(r); if(rcnf){ guts->sesid=rcnf->sesid; guts->valid=rcnf->valid; } /* run input filters and discard the body, all access via libapreq2 */ if((sstatus=ap_discard_request_body(r))!=OK){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_EGENERAL,r, "mod_spin: cannot run input filters"); return sstatus; } /* find and execute application */ if(conf->application){ /* if we have the DSO in cache, fetch it */ if(!(handle=(apr_dso_handle_t *)apr_hash_get(priv->dso,conf->application, APR_HASH_KEY_STRING))){ /* no luck, load new one */ if((status=apr_dso_load(&handle,conf->application, priv->pool))!=APR_SUCCESS){ char *error; if((error=apr_palloc(r->pool,ERROR_BUF_SIZE))){ apr_dso_error(handle,error,ERROR_BUF_SIZE); ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r,"mod_spin: %s",error); } else{ ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: cannot load %s",conf->application); } return HTTP_INTERNAL_SERVER_ERROR; } apr_hash_set(priv->dso,conf->application,APR_HASH_KEY_STRING,handle); } if((status=apr_dso_sym(&shandle,handle,conf->appentry)!=APR_SUCCESS)){ char *error; if((error=apr_palloc(r->pool,ERROR_BUF_SIZE))){ apr_dso_error(handle,error,ERROR_BUF_SIZE); ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r,"mod_spin: %s",error); } else{ ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: %s unavaliable",conf->appentry); } return HTTP_INTERNAL_SERVER_ERROR; } /* call entry function */ switch(sstatus=(*((rxv_spin_service_t)shandle))(ctx)){ case DECLINED: case DONE: return sstatus; break; case OK: break; default: if(ap_is_HTTP_REDIRECT(sstatus)){ return sstatus; } else{ ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: application error"); return HTTP_INTERNAL_SERVER_ERROR; } break; } } 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; } /* open, scan/parse and process the template */ if((status=rxv_spin_file(r->filename,extra, priv->pool,&r->finfo))!=APR_SUCCESS){ switch(status){ case APR_EACCES: ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: permission denied: %s",r->filename); sstatus=HTTP_FORBIDDEN; break; case APR_EGENERAL: ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: parse error: %s: %s", r->filename,extra->error?extra->error:"unspecified"); sstatus=HTTP_INTERNAL_SERVER_ERROR; break; default: ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: cannot process template: %s", r->filename); sstatus=HTTP_INTERNAL_SERVER_ERROR; break; } return sstatus; } /* send up the food chain, we're done here */ if(!APR_BRIGADE_EMPTY(extra->brigade) && (status=ap_pass_brigade(r->output_filters,extra->brigade))!=APR_SUCCESS){ ap_log_rerror(APLOG_MARK,APLOG_ERR,status,r, "mod_spin: cannot pass brigade to filters"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } /* * 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("SpinAppEntry",set_appentry,NULL,ACCESS_CONF|RSRC_CONF, "name of the entry function in the shared library"), AP_INIT_TAKE1("SpinAppConfig",set_appconfig,NULL,ACCESS_CONF|RSRC_CONF, "path to the configuration file"), AP_INIT_TAKE1("SpinWorkspace",set_workspace,NULL,ACCESS_CONF|RSRC_CONF, "path to the workspace directory"), AP_INIT_TAKE1("SpinCookie",set_cookie,NULL,ACCESS_CONF|RSRC_CONF, "name of the cookie for session tracking"), AP_INIT_TAKE1("SpinSendfile",set_sendfile,NULL,ACCESS_CONF|RSRC_CONF, "should sendfile() be used to transmit file chunks"), AP_INIT_TAKE1("SpinCacheAll",set_cacheall,NULL,ACCESS_CONF|RSRC_CONF, "should files be fully cached"), AP_INIT_TAKE1("SpinClearCount",set_clearcnt,NULL,RSRC_CONF, "after how many requests the private pool gets cleared"), AP_INIT_TAKE1("SpinSalt",set_salt,NULL,RSRC_CONF, "cryptographic salt"), {NULL} }; /* * 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 private_destroy(void *data){ apr_pool_destroy(((rxv_spin_private_t *)data)->pool); } static void child_init(apr_pool_t *p,server_rec *s){ ppool=s->process->pool; apr_status_t status; /* create thread private key to identify thread private data */ if((status=apr_threadkey_private_create(&tkey, private_destroy,ppool))!=APR_SUCCESS) ap_log_error(APLOG_MARK,APLOG_CRIT,status,s, "mod_spin: cannot create thread private key"); /* check version and initialise XML parser */ LIBXML_TEST_VERSION } static apr_status_t templates_clean(void *data){ apr_hash_t *used=(apr_hash_t *)data; apr_pool_t *pool=apr_hash_pool_get(used); apr_hash_index_t *hi; const void *key; void *val; apr_ssize_t len; rxv_spin_cache_t *ready; for(hi=apr_hash_first(pool,used);hi;hi=apr_hash_next(hi)){ apr_hash_this(hi,&key,&len,&val); ready=(rxv_spin_cache_t *)val; if(ready->kill) apr_pool_destroy(ready->pool); } return APR_SUCCESS; } static int pre_conn(conn_rec *c,void *csd){ rxv_spin_conncf_t *conf=apr_pcalloc(c->pool,sizeof(*conf)); apr_status_t status; if(!conf){ ap_log_error(APLOG_MARK,APLOG_CRIT,APR_ENOMEM,c->base_server, "mod_spin: cannot create connection configuration"); return HTTP_INTERNAL_SERVER_ERROR; } /* get thread private data */ if((status=apr_threadkey_private_get((void *)(&conf->priv), tkey))!=APR_SUCCESS){ ap_log_error(APLOG_MARK,APLOG_CRIT,status,c->base_server, "mod_spin: cannot get thread private data"); return HTTP_INTERNAL_SERVER_ERROR; } /* maybe we need to kill this whole pool */ if(conf->priv && conf->priv->kill){ apr_pool_destroy(conf->priv->pool); conf->priv=NULL; } /* thread private data missing, create and store it for this thread */ if(!conf->priv){ if(!(conf->priv=rxv_spin_private_create(ppool))){ ap_log_error(APLOG_MARK,APLOG_CRIT,APR_ENOMEM,c->base_server, "mod_spin: cannot create thread private data"); return HTTP_INTERNAL_SERVER_ERROR; } if(apr_threadkey_private_set(conf->priv,tkey)!=APR_SUCCESS){ apr_pool_destroy(conf->priv->pool); ap_log_error(APLOG_MARK,APLOG_CRIT,status,c->base_server, "mod_spin: cannot set thread private data"); return HTTP_INTERNAL_SERVER_ERROR; } } /* create hash table of used templates */ if((conf->used=apr_hash_make(c->pool))){ ap_set_module_config(c->conn_config,&spin_module,conf); apr_pool_cleanup_register(c->pool,conf->used,templates_clean, apr_pool_cleanup_null); } else{ ap_log_error(APLOG_MARK,APLOG_CRIT,APR_ENOMEM,c->base_server, "mod_spin: cannot create template cache"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } static int fixups(request_rec *r){ rxv_spin_config_t *conf; rxv_spin_reqcf_t *rcnf; /* don't handle if not configured for sessions or if sub-request */ if(!((conf=ap_get_module_config(r->per_dir_config,&spin_module)) && conf->cookie && salt) || r->main) return DECLINED; /* request configuration */ if(!(rcnf=apr_pcalloc(r->pool,sizeof(*rcnf)))){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r, "mod_spin: cannot create request configuration data"); return HTTP_INTERNAL_SERVER_ERROR; } ap_set_module_config(r->request_config,&spin_module,rcnf); /* get/set session identifier and serve it back to the client */ if(conf->cookie && salt){ apreq_handle_t *req=apreq_handle_apache2(r); if(req){ char *sesid=NULL,*uniq,*hash; apreq_cookie_t *cookie; /* we have session ID already */ if((cookie=apreq_cookie(req,conf->cookie))){ char *p; size_t clen; /* extract session from he cookie */ if(*cookie->v.data=='"' && *(cookie->v.data+cookie->v.dlen-1)=='"'){ clen=cookie->v.dlen-2; sesid=apr_pstrmemdup(r->pool,cookie->v.data+1,clen); } else{ clen=cookie->v.dlen; sesid=apr_pstrmemdup(r->pool,cookie->v.data,clen); } /* split the session ID from the MD5 hash */ if(!(sesid && (p=strchr(sesid,'.')))){ 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 */ uniq=apr_pstrmemdup(r->pool,sesid,p-sesid); hash=p+1; /* do crypto validation against new and possibly old salt */ if(md5b64_validate(r->pool,uniq,hash,clen-(hash-sesid))!=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=1; } else{ /* we have to build session ID ourselves */ if((uniq=(char *)apr_table_get(r->subprocess_env,"UNIQUE_ID"))){ if(!(hash=md5b64(r->pool,uniq,salt))){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r, "mod_spin: MD5 encoding failed"); return HTTP_INTERNAL_SERVER_ERROR; } if(!(sesid=apr_pstrcat(r->pool,uniq,".",hash,NULL))){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r, "mod_spin: cannot make session id"); return HTTP_INTERNAL_SERVER_ERROR; } } /* set session, but not valid yet */ rcnf->sesid=uniq; } /* make and bake the cookie */ if(sesid){ char *scookie; if(!(cookie=apreq_cookie_make(r->pool, conf->cookie,strlen(conf->cookie), sesid,strlen(sesid)))){ ap_log_rerror(APLOG_MARK,APLOG_ERR,APR_ENOMEM,r, "mod_spin: cannot make session cookie"); return HTTP_INTERNAL_SERVER_ERROR; } cookie->path="/"; 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); } } } return OK; } 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_pre_connection(pre_conn,NULL,NULL,APR_HOOK_MIDDLE); ap_hook_fixups(fixups,NULL,NULL,APR_HOOK_MIDDLE); ap_hook_handler(handler,NULL,NULL,APR_HOOK_MIDDLE); } 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 */ };