/* mod_xrealpathheader.c v0.1 2011-02-08 Copyright 2011 Rafał Frühling This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Based on mod_xsendfile.c by Nils Maier USE THIS ON YOUR OWN RISK. IT HAS NOT BEEN TESTED THROUGHLY. IN FACT, THIS MODULE WASN'T TESTED AT ALL, EXCEPT IN "OH, IT RUNS ON MY SYSTEM" ENVIRONMENT. YOU HAVE BEEN WARNED. */ /* * mod_xrealpathheader.c: Lookup request's file and check if it's a symlink. * If so, add X-RealPath: header with the actal file the symlink links * to. * This module is handy in a http accelerator/proxy (like Varnish) * environment where information gathered by parsing X-RealPath header * can be used to alter accelerator/proxy behaviour, like setting max * thoughput for a VBR video to 110% of what is needed for user to have * a smooth broadcast. For ex. when X-RealPath is: * X-RealPath: video.1126.ogm * Varnish can be configured to send the file at 1126 kbit/s max, which * is 110% of 1 Mbit/s. (A special non-standard patch is needed thou.) * Module Name: mod_xrealpathheader.c * Content handlers: yes * Configuration Phase Participation: Create Directory Config, Merge * Directory Configs, Create Server Config, Merge Server Configs Request * Phase Participation: Content Handlers * Module Directives: * SendRealPathHeader - On|Off - Send symlink additional headers (default: Off) * SendRealPathHeaderDebug - On|Off - Module debug info (default: Off) * Installation: * apxs2 -cia mod_xrealpathheader.c */ /* * Include the core server components. */ #include "ap_config.h" #include "httpd.h" #include "http_request.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "apr_version.h" #include "apu_version.h" #include "apr_strings.h" #include "apr_lib.h" #include #ifndef UNSET #define UNSET (-1) #endif #define AP_XSYMLINKREALPATH_HEADER "X-RealPath" #define MODULE_VER "0.1" #define VERSION_COMPONENT "mod_xrealpathheader/"MODULE_VER #define MODFLAG_ENABLED 1 #define MODFLAG_DISABLED 0 /* from ssl/ssl_engine_config.c */ #define cfgMerge(el,unset) mrg->el = (add->el == (unset)) ? base->el : add->el #define cfgMergeArray(el) mrg->el = apr_array_append(p, add->el, base->el) #define cfgMergeString(el) cfgMerge(el, NULL) #define cfgMergeBool(el) cfgMerge(el, UNSET) #define cfgMergeInt(el) cfgMerge(el, UNSET) /* * This module */ module AP_MODULE_DECLARE_DATA xrealpathheader_module; /* per-dir configuration */ typedef struct { int module_enabled; int module_debug; } module_dir_conf; static apr_version_t vsn; static void debugLog(const request_rec *r, const module_dir_conf *cfg, const char *fmt, ...) { if (cfg->module_debug == MODFLAG_ENABLED) { char buf[512]; va_list ap; va_start(ap, fmt); apr_vsnprintf(buf, sizeof (buf), fmt, ap); va_end(ap); /* we use warn loglevel to be able to debug without * setting the entire server into debug logging mode */ ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, r, "[mod_xrealpathheader] %s", buf); } } static const char *set_module_param_flag(cmd_parms *parms, void *mconfig, int flag) { char rstring[255]; module_dir_conf *cfg = (module_dir_conf *) mconfig; // if it comes from non-dir definition if (parms->path == NULL) { cfg = (module_dir_conf *) ap_get_module_config(parms->server->module_config, &xrealpathheader_module); } if (!cfg) { sprintf(rstring,"Cannot get configuration object, path=%s",parms->path); return rstring; } if (!strcasecmp(parms->cmd->name, "sendrealpathheader")) { cfg->module_enabled = flag ? MODFLAG_ENABLED : MODFLAG_DISABLED; } else if (!strcasecmp(parms->cmd->name, "sendrealpathheaderdebug")) { cfg->module_debug = flag ? MODFLAG_ENABLED : MODFLAG_DISABLED; } else { return apr_psprintf(parms->pool, "Not a valid command in this context: %s %s", parms->cmd->name, flag ? "On": "Off"); } // cfg->module_enabled = MODFLAG_ENABLED; // cfg->module_debug = MODFLAG_ENABLED; return NULL; } static module_dir_conf *module_config_create(apr_pool_t *p) { module_dir_conf *cfg; cfg = (module_dir_conf *) apr_pcalloc(p, sizeof(module_dir_conf)); cfg->module_enabled = MODFLAG_DISABLED; cfg->module_debug = MODFLAG_DISABLED; return cfg; // (void *) in create_module_*_config } static void *create_module_dir_config(apr_pool_t *p, char *path) { return (void *) module_config_create(p); } static void *create_module_server_config(apr_pool_t *p, server_rec *s) { return (void *) module_config_create(p); } static void *merge_module_config(apr_pool_t *p, void *basev, void *addv) { module_dir_conf *mrg = (module_dir_conf *) apr_pcalloc(p, sizeof(module_dir_conf)); module_dir_conf *base = (module_dir_conf *) basev; module_dir_conf *add = (module_dir_conf *) addv; cfgMergeBool(module_enabled); cfgMergeBool(module_debug); return (void *) mrg; } /* Post-config */ static int module_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { apr_version(&vsn); ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "[mod_xrealpathheader] compiled with APR/APR-Util %s/%s", APR_VERSION_STRING, APU_VERSION_STRING); ap_add_version_component(pconf, VERSION_COMPONENT); return OK; } /* * This function is registered as a handler for HTTP methods and will * therefore be invoked for all GET requests (and others). Regardless * of the request type, this function simply sends a message to * STDERR (which httpd redirects to logs/error_log). A real module * would do *alot* more at this point. */ static int mod_xrealpathheader_method_handler(request_rec *r) { struct stat statinfo; char buf[1024]; module_dir_conf *cfg = NULL; cfg = (module_dir_conf *) ap_get_module_config(r->per_dir_config, &xrealpathheader_module); #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "mod_xrealpathheader enabled=%d debug=%d", cfg->module_enabled, cfg->module_debug); #endif if (cfg->module_enabled != MODFLAG_ENABLED) { debugLog(r,cfg,"module not enabled for file '%s'",r->finfo.fname); return DECLINED; } debugLog(r,cfg,"hostname=%s method=%s filename=%s name=%s fname=%s filetype=%d", r->hostname, r->method, r->filename, r->finfo.name, r->finfo.fname, r->finfo.filetype); if (lstat(r->finfo.fname,&statinfo) < 0) { debugLog(r,cfg,"cannot stat '%s'", r->finfo.fname); } else { if (S_ISLNK(statinfo.st_mode)) { // OK, symlink, add header debugLog(r,cfg,"file '%s' is a symlink ...", r->finfo.fname); int c = readlink(r->finfo.fname,buf,sizeof(buf)); if (c >= 0) { buf[c] = '\0'; debugLog(r,cfg,"... to file '%s'", buf); } if (c == sizeof(buf)) { debugLog(r,cfg,"Link path size reached %d bytes.",sizeof(buf)); } apr_table_add(r->headers_out, AP_XSYMLINKREALPATH_HEADER, buf); return DECLINED; } // if s_islnk } // Return DECLINED so that the Apache core will keep looking for // other modules to handle this request. This effectively makes // this module completely transparent. return DECLINED; } /* * This function is a callback and it declares what other functions * should be called for request processing and configuration requests. * This callback function declares the Handlers for other events. */ static void mod_xrealpathheader_register_hooks (apr_pool_t *p) { ap_hook_post_config (module_post_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(mod_xrealpathheader_method_handler, NULL, NULL, APR_HOOK_LAST); } /** * A declaration of the configuration directives that are supported by this module. */ static const command_rec mod_xrealpathheader_cmds[] = { AP_INIT_FLAG( "SendRealPathHeader", set_module_param_flag, NULL, OR_FILEINFO, "On|Off - Send symlink additional headers (default: Off)"), AP_INIT_FLAG( "SendRealPathHeaderDebug", set_module_param_flag, NULL, OR_FILEINFO, "On|Off - Module debug info (default: Off)"), {NULL} }; /* * Declare and populate the module's data structure. The * name of this structure ('xrealpathheader_module') is important - it * must match the name of the module. This structure is the * only "glue" between the httpd core and the module. */ module AP_MODULE_DECLARE_DATA xrealpathheader_module = { // Only one callback function is provided. Real // modules will need to declare callback functions for // server/directory configuration, configuration merging // and other tasks. STANDARD20_MODULE_STUFF, // NULL, // NULL, create_module_dir_config, /* create per-directory config structures */ merge_module_config, /* merge per-directory config structures */ create_module_server_config, /* create per-server config structures */ merge_module_config, /* merge per-server config structures */ mod_xrealpathheader_cmds, /* command handlers */ mod_xrealpathheader_register_hooks, /* callback for registering hooks */ };