/* * mod_vhost_hash_alias.c -- An Apache2 ServerName hashing module * * Copyright (C) 2004,2005 Yann Droneaud * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifdef HAVE_CONFIG_H #include "config.h" #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #endif #ifndef MODULE_NAME #define MODULE_NAME "mod_vhost_hash_alias" #endif #ifndef MODULE_VERSION #define MODULE_VERSION "1.0" #endif #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include "http_connection.h" #include "apr_strings.h" #include #include "lib/encoding3548.h" module AP_MODULE_DECLARE_DATA vhost_hash_alias_module; struct hash_alias_prefix { struct hash_alias_prefix *next; size_t prefix_len; char prefix[0]; }; typedef struct hash_alias_prefix hash_alias_prefix_t; struct hash_alias_chunk { struct hash_alias_chunk *next; size_t size; /* size of the chunk */ }; typedef struct hash_alias_chunk hash_alias_chunk_t; /* global configuration */ typedef struct hash_alias_config { hashid hash_id; size_t hash_size; /* size of the hash (binary data) */ int encoding_id; /* convert the binary data to hexa, ... */ size_t hash_str_len; /* len of the hash converted in string */ size_t hash_str_limit; /* len of the used part of the hash string */ size_t hash_path_len; /* len of the path built from the hash string */ hash_alias_chunk_t *hash_chunk; /* split scheme */ const char * prefix; size_t prefix_len; const char *document_root_prefix; /* document root base */ const char *document_root_suffix; /* document root suffix */ hash_alias_prefix_t *alias_prefix; int debug; int enabled; /* By default, hash is disabled for virtual host */ } hash_alias_config_t; static void * hash_alias_create_server_config(apr_pool_t *p, server_rec *s) { hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) apr_palloc(p, sizeof(hash_alias_config_t)); hash_alias_config->enabled = 0; hash_alias_config->debug = 0; hash_alias_config->prefix = NULL; hash_alias_config->prefix_len = 0; hash_alias_config->hash_id = -1; hash_alias_config->hash_size = 0; hash_alias_config->encoding_id = -1; hash_alias_config->hash_str_len = 0; hash_alias_config->hash_str_limit = 0; hash_alias_config->hash_path_len = 0; hash_alias_config->hash_chunk = NULL; hash_alias_config->alias_prefix = NULL; hash_alias_config->document_root_prefix = NULL; hash_alias_config->document_root_suffix = NULL; return hash_alias_config; } /* merge the virtual host configuration with the global configuration */ static void * hash_alias_merge_server_configs(apr_pool_t *p, void *basev, void *virtv) { hash_alias_config_t *hash_alias_config; hash_alias_config_t *hash_alias_config_base = (hash_alias_config_t *)basev; hash_alias_config_t *hash_alias_config_virt = (hash_alias_config_t *)virtv; hash_alias_config = (hash_alias_config_t *) apr_palloc(p, sizeof(hash_alias_config_t)); /* virtual fully inherit from main configuration */ *hash_alias_config = *hash_alias_config_base; /* could be disabled in VirtualHost */ hash_alias_config->enabled = hash_alias_config_virt->enabled; return hash_alias_config; } static const char * hash_alias_cmd_enable(cmd_parms *cmd, void *dummy, int f) { hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); hash_alias_config->enabled = f; return NULL; } static const char * hash_alias_cmd_debug(cmd_parms *cmd, void *dummy, const char *opt) { hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); hash_alias_config->debug = atoi(opt); return NULL; } static const char * hash_alias_cmd_add_alias_prefix(cmd_parms *cmd, void *dummy, const char *opt) { apr_pool_t *p; hash_alias_config_t * hash_alias_config; hash_alias_prefix_t * prefix; size_t len; p = cmd->pool; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); len = strlen(opt); prefix = (hash_alias_prefix_t *) apr_palloc(p, sizeof(hash_alias_prefix_t) + len + 1); /* copy the string */ strcpy(prefix->prefix, opt); prefix->prefix_len = len; /* push the new prefix on top of the list */ if (hash_alias_config->alias_prefix != NULL) { prefix->next = hash_alias_config->alias_prefix; } else { prefix->next = NULL; } hash_alias_config->alias_prefix = prefix; return NULL; } static struct hash_alias_dsc { const char *name; hashid id; } hash_alias_table[] = { { "md5", MHASH_MD5 }, { "sha", MHASH_SHA1 }, { "sha1", MHASH_SHA1 }, { "sha256", MHASH_SHA256 }, { "ripemd", MHASH_RIPEMD160 }, { "ripemd160", MHASH_RIPEMD160 }, { "crc", MHASH_CRC32 }, { "crc32", MHASH_CRC32 }, { "crc32b", MHASH_CRC32B }, { "adler", MHASH_ADLER32 }, { "adler32", MHASH_ADLER32 }, { NULL, -1 } }; static const char * hash_alias_cmd_type(cmd_parms *cmd, void *dummy, char *type) { int i; hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); for(i = 0; hash_alias_table[i].name != NULL; i++) { if (!strcasecmp(hash_alias_table[i].name, type)) break; } if (hash_alias_table[i].name == NULL) { return MODULE_NAME ": Invalid hash name"; } hash_alias_config->hash_id = hash_alias_table[i].id; hash_alias_config->hash_size = mhash_get_block_size(hash_alias_table[i].id); return NULL; } static struct hash_alias_encoding_dsc { const char *name; size_t (*encode_len)(size_t bin_size); size_t (*encode)(char *out, const unsigned char *in, size_t in_size); } hash_alias_encodings[] = { { "hexa", base16_encode_len, base16_low_encode }, { "base16_low", base16_encode_len, base16_low_encode }, { "base16_up", base16_encode_len, base16_up_encode }, { "base16", base16_encode_len, base16_encode }, { "base32_low", base32_encode_len, base32_low_encode }, { "base32_up", base32_encode_len, base32_up_encode }, { "base32", base32_encode_len, base32_encode }, { "base64_ufs", base64_encode_len, base64_ufs_encode }, { NULL, NULL, NULL } }; static const char * hash_alias_cmd_encoding(cmd_parms *cmd, void *dummy, char *encoding) { int i; hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); for(i = 0; hash_alias_encodings[i].name != NULL; i++) { if (!strcasecmp(hash_alias_encodings[i].name, encoding)) break; } if (hash_alias_encodings[i].name == NULL) { return MODULE_NAME ": Invalid encoding name"; } hash_alias_config->encoding_id = i; return NULL; } static const char * hash_alias_cmd_split(cmd_parms *cmd, void *dummy, char *size_str) { size_t chunk_size; hash_alias_chunk_t *ptr; hash_alias_chunk_t *hash_chunk; hash_alias_config_t * hash_alias_config; chunk_size = atoi(size_str); if (chunk_size <= 0) { return "Invalid chunk size\n"; } hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); /* go to the last element */ for(ptr = hash_alias_config->hash_chunk; ptr != NULL && ptr->next != NULL; ptr = ptr->next); hash_chunk = (hash_alias_chunk_t *) apr_palloc(cmd->pool, sizeof(hash_alias_chunk_t)); hash_chunk->next = NULL; hash_chunk->size = chunk_size; if (ptr != NULL) { ptr->next = hash_chunk; } else { hash_alias_config->hash_chunk = hash_chunk; } return NULL; } static const char * hash_alias_cmd_limit(cmd_parms *cmd, void *dummy, char *size_str) { hash_alias_config_t * hash_alias_config; int size; size = atoi(size_str); if (size <= 0) { return "Invalid limit length\n"; } hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); /* The limit will be check at the end of the config */ hash_alias_config->hash_str_limit = size; return NULL; } static const char * hash_alias_cmd_prefix(cmd_parms *cmd, void *dummy, char *prefix) { hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); if (prefix == NULL) { return MODULE_NAME ": Invalid HashPrefix"; } hash_alias_config->prefix = prefix; hash_alias_config->prefix_len = strlen(prefix); return NULL; } static const char * hash_alias_cmd_document_root_suffix(cmd_parms *cmd, void *dummy, char *suffix) { hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); hash_alias_config->document_root_suffix = suffix; return NULL; } static const char * hash_alias_cmd_document_root_prefix(cmd_parms *cmd, void *dummy, char *prefix) { hash_alias_config_t * hash_alias_config; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(cmd->server->module_config, &vhost_hash_alias_module); /* XXX check the validity of the path (exist + directory + access) */ hash_alias_config->document_root_prefix = prefix; return NULL; } static int hash_alias_post_config_one(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { hash_alias_config_t *hash_alias_config; int count; size_t hash_path_len; size_t hash_str_len; hash_alias_chunk_t *ptr; size_t rem; size_t end; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(s->module_config, &vhost_hash_alias_module); if (!hash_alias_config->enabled) { return OK; } if (hash_alias_config->debug > 0) { if (s->is_virtual) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, s, MODULE_NAME ": virtual host %s:%d", s->server_hostname, s->port); } else { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, s, MODULE_NAME ": main host %s:%d", s->server_hostname, s->port); } } /* compute the size of the converted digest */ if (hash_alias_config->encoding_id == -1) { hash_alias_config->encoding_id = 0; /* default to hexa */ } hash_alias_config->hash_str_len = hash_alias_encodings[hash_alias_config->encoding_id].encode_len(hash_alias_config->hash_size); /* here, check size being less or equal to hash_alias_config->hash_str_len */ if (hash_alias_config->hash_str_limit <= 0) { hash_alias_config->hash_str_limit = hash_alias_config->hash_str_len; } else if (hash_alias_config->hash_str_limit > hash_alias_config->hash_str_len) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, s, MODULE_NAME ": out of bound HashLimit"); return DECLINED; } /* compute the size of the path buffer */ if (hash_alias_config->hash_chunk != NULL) { hash_path_len = 0; hash_str_len = 0; for(ptr = hash_alias_config->hash_chunk; ptr != NULL; ptr = ptr->next) { hash_str_len += ptr->size; hash_path_len += ptr->size + 1; /* one for the '/' */ if (ptr->next == NULL) break; } if (hash_path_len > 0) hash_path_len --; /* don't count the last '/' */ /* the split is out side of the hash */ if (hash_str_len > hash_alias_config->hash_str_limit) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, s, MODULE_NAME ": split scheme outside of the hash string"); return DECLINED; } /* the last chunk could be repeated, repeat it until the end of the hash_str */ rem = hash_alias_config->hash_str_limit - hash_str_len; if (rem > 0) { count = rem / ptr->size; /* count of remaing chunk like this one */ end = rem % ptr->size; hash_alias_config->hash_path_len = hash_path_len + count * (ptr->size + 1) + ((end != (size_t) 0) ? (ssize_t) end : -1); /* remove the last '/' if the hash end on a boundary */ } else { hash_alias_config->hash_path_len = hash_path_len; } } else { hash_alias_config->hash_path_len = hash_alias_config->hash_str_len; } return OK; } static int hash_alias_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *root_server) { server_rec *s; int ret; for(s = root_server; s != NULL; s = s->next) { ret = hash_alias_post_config_one(p, plog, ptemp, s); if (ret != OK) return ret; } ap_add_version_component(p, MODULE_NAME "/" MODULE_VERSION); ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, NULL, MODULE_NAME " " MODULE_VERSION " initialized"); return OK; } static const command_rec hash_alias_command_table[] = { AP_INIT_FLAG("HashEnable", /* directive name */ hash_alias_cmd_enable, /* config action routine */ NULL, /* argument to include in call */ RSRC_CONF, /* where available */ "On or Off to enable or disable (default) the whole rewriting engine" /* directive description */ ), AP_INIT_TAKE1("HashDebugLevel", /* directive name */ hash_alias_cmd_debug, /* config action routine */ NULL, /* argument to include in call */ RSRC_CONF, /* where available */ "1 or more to enable or 0 to disable (default) debug messages" /* directive description */ ), AP_INIT_TAKE1("HashType", hash_alias_cmd_type, NULL, RSRC_CONF, "Hash algorithm (md5, sha1, sha256, ...)"), AP_INIT_TAKE1("HashEncoding", hash_alias_cmd_encoding, NULL, RSRC_CONF, "Hash encoding (hexa, base16[_up|_low], base32[_up|_low], base64_ufs)"), AP_INIT_ITERATE("HashSplit", hash_alias_cmd_split, NULL, RSRC_CONF, "Chunk sizes in character"), AP_INIT_TAKE1("HashLimit", hash_alias_cmd_limit, NULL, RSRC_CONF, "Don't use more than characters of the hash string"), AP_INIT_TAKE1("HashPrefix", hash_alias_cmd_prefix, NULL, RSRC_CONF, "Prefix added to the ServerName before hashing"), AP_INIT_TAKE1("HashDocumentRootSuffix", hash_alias_cmd_document_root_suffix, NULL, RSRC_CONF, "Directory where are the web pages (typically htdocs/)"), AP_INIT_TAKE1("HashDocumentRootPrefix", hash_alias_cmd_document_root_prefix, NULL, RSRC_CONF, "Base directory used to build the path to the virtual host, (default to DocumentRoot"), AP_INIT_ITERATE("HashAddAliasPrefix", /* directive name */ hash_alias_cmd_add_alias_prefix, /* config action routine */ NULL, /* argument to include in call */ RSRC_CONF, /* where available */ "Alias .servername to servername" /* directive description */ ), {NULL} }; /* * take the request, hash the server name and return the "filename" * filled with the docroot + hash + htdocs + URI. * */ static int hash_alias_translate_servername(request_rec *r) { /* * Some potential useful variable * r->server, r->hostname, r->protocol, r->method, r->uri * s->is_virtual; */ hash_alias_config_t *hash_alias_config; hash_alias_prefix_t *ptr; MHASH mhash_ctx; unsigned char *hash; char *hash_str; char *hash_str_ptr; char *hash_path; char *hash_path_ptr; const char *servername; const char *docroot; const char *siteroot; const char *uri; hash_alias_chunk_t * hash_chunk; size_t len; size_t size; hash_alias_config = (hash_alias_config_t *) ap_get_module_config(r->server->module_config, &vhost_hash_alias_module); if (!hash_alias_config->enabled) return DECLINED; servername = ap_get_server_name(r); /* strip server alias */ for(ptr = hash_alias_config->alias_prefix; ptr != NULL; ptr = ptr->next) { if (strncasecmp(servername, ptr->prefix, ptr->prefix_len) == 0) { const char *srvn = servername += ptr->prefix_len; if (*srvn == '.') { servername = srvn + 1; break; } } } /* create hash context */ mhash_ctx = mhash_init(hash_alias_config->hash_id); if (mhash_ctx == MHASH_FAILED) return 500; /* hash(prefix + servername) */ if (hash_alias_config->prefix_len > 0) mhash(mhash_ctx, hash_alias_config->prefix, hash_alias_config->prefix_len); mhash(mhash_ctx, servername, strlen(servername)); hash = alloca(hash_alias_config->hash_size); mhash_deinit(mhash_ctx, hash); hash_str = alloca(hash_alias_config->hash_str_len + 1); /* encode the binary value */ hash_alias_encodings[hash_alias_config->encoding_id].encode(hash_str, hash, hash_alias_config->hash_size); hash_str[hash_alias_config->hash_str_len] = '\0'; hash_path = alloca(hash_alias_config->hash_path_len + 1); len = hash_alias_config->hash_str_limit; if (hash_alias_config->hash_chunk != NULL) { /* Split according to hash_chunk_size[]. The last element is repeated 1 2 4 4 4 4 ... until EOS or HashLimit */ hash_chunk = hash_alias_config->hash_chunk; hash_path_ptr = hash_path; hash_str_ptr = hash_str; while(len > 0) { size = hash_chunk->size; if (size > len) size = len; memcpy(hash_path_ptr, hash_str_ptr, size); hash_path_ptr += size; hash_str_ptr += size; len -= size; *(hash_path_ptr ++) = '/'; /* repeat the last chunk until the end of the string */ if (hash_chunk->next != NULL) hash_chunk = hash_chunk->next; } *(hash_path_ptr - 1) = '\0'; } else { /* do not split */ memcpy(hash_path, hash_str, len); hash_path[len] = '\0'; } uri = r->uri; if (*uri == '/') uri++; /* get the base document root */ if (hash_alias_config->document_root_prefix == NULL) { hash_alias_config->document_root_prefix = ap_document_root(r); } /* build base directory of the site */ siteroot = apr_psprintf(r->pool, "%s/%s/%s", hash_alias_config->document_root_prefix, hash_path, servername); /* compute the base path (virtual document root) */ if (hash_alias_config->document_root_suffix != NULL) { docroot = apr_psprintf(r->pool, "%s/%s", siteroot, hash_alias_config->document_root_suffix); } else { docroot = siteroot; } /* compute the final path (document_root + uri) */ r->filename = apr_psprintf(r->pool, "%s/%s", docroot, uri); /* export the virtual document root */ apr_table_setn(r->subprocess_env, "SITE_ROOT_HASH", siteroot); apr_table_setn(r->subprocess_env, "DOCUMENT_ROOT_HASH", docroot); if (hash_alias_config->debug > 0) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r, MODULE_NAME ": docroot = %s", docroot); ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r, MODULE_NAME ": %s:%d %s %s/%s ->", r->server->server_hostname, r->server->port, hash_alias_config->prefix, servername, uri); ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r, MODULE_NAME ": -> %s", r->filename); } return OK; } static void hash_alias_register_hooks(apr_pool_t *p) { ap_hook_translate_name(hash_alias_translate_servername, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(hash_alias_post_config, NULL, NULL, APR_HOOK_MIDDLE); } /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA vhost_hash_alias_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ hash_alias_create_server_config, /* create per-server config structures */ hash_alias_merge_server_configs, /* merge per-server config structures */ hash_alias_command_table, /* table of config file commands */ hash_alias_register_hooks /* register hooks */ };