c155e1db7544dff158840e999b831c3eccdd8274
[m6w6/mod-domaintree] / mod_domaintree.c
1 /*
2 * mod_domaintree.c - Apache2
3 *
4 * $Id$
5 *
6 * Copyright 2005 Michael Wallner <mike@iworks.at>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 *
20 *
21 * <pre>
22 * DomainTreeEnabled On
23 * DomainTreeMaxdepth 25
24 * DomainTreeStripWWW On
25 * DomainTreePrefix /sites
26 * DomainTreeSuffix /html
27 * DomainTreeCache 5
28 * DomainTreeAliasRecursion Off
29 * DomainTreeAlias /??/exmaple /com/exmaple
30 * DomainTreeAlias /???/example /com/example
31 * DomainTreeAlias /*one/ /anyone/
32 *
33 * /sites
34 * +- /at
35 * | +- /co
36 * | | +- /company
37 * | | +- /html
38 * | | +- /sub1
39 * | | | +- /html
40 * | | +- /sub2
41 * | | +- /html
42 * | +- /or
43 * | +- /organisation
44 * | +- /html
45 * +- /com
46 * +- /example
47 * +- /html
48 * </pre>
49 */
50
51 #define MODULE "mod_domaintree"
52 #define AUTHOR "mike@php.net"
53 #define VERSION "1.3"
54
55 /* {{{ Includes */
56
57 #include "apr.h"
58 #include "apr_lib.h"
59 #include "apr_hash.h"
60 #include "apr_tables.h"
61 #include "apr_strings.h"
62
63 #if APR_HAVE_UNISTD_H
64 #include <unistd.h>
65 #endif
66
67 #define APR_WANT_MEMFUNC
68 #define APR_WANT_STRFUNC
69 #include "apr_want.h"
70
71 #include "httpd.h"
72 #include "http_config.h"
73 #include "http_core.h"
74 #include "http_log.h"
75 #include "http_protocol.h"
76 #include "http_request.h"
77
78 /* }}} */
79 /* {{{ domaintree_module */
80
81 module AP_MODULE_DECLARE_DATA domaintree_module;
82
83 /* }}} */
84 /* {{{ Macros & Types */
85
86 #define MOD_DT_CNF domaintree_conf
87 #define MOD_DT_PTR (&domaintree_module)
88
89 #define GET_MOD_DT_CNF(srv) ((MOD_DT_CNF *) ap_get_module_config((srv)->module_config, MOD_DT_PTR))
90
91 #define NUL '\0'
92 #define EMPTY(str) ((str == NULL) || (*(str) == NUL))
93 #define local static APR_INLINE
94
95 #define DT_LOG_ERR ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, DT->server,
96 #define DT_LOG_WRN ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, DT->server,
97 #define DT_LOG_DBG ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server,
98 #define DT_LOG_END );
99
100 typedef int STATUS;
101
102 typedef struct {
103 long recursion;
104 apr_table_t *faketable;
105 } aliases_t;
106
107 struct dircache_entry {
108 char *host;
109 char *path;
110 apr_time_t lacc;
111 apr_pool_t *pool;
112 };
113
114 typedef struct {
115 long clim;
116 apr_hash_t *hmap;
117 apr_pool_t *pool;
118 apr_global_mutex_t *lock;
119 } dircache_t;
120
121 typedef struct {
122 server_rec *server;
123 int enabled;
124 int stripwww;
125 int statroot;
126 long maxdepth;
127 char *prefix;
128 char *suffix;
129 aliases_t aliases;
130 dircache_t dircache;
131 } domaintree_conf;
132
133 /* }}} */
134 /* {{{ Helpers */
135
136 local char *strtr(char *string, char from, char to)
137 {
138 char *ptr = string;
139
140 if (from != to) {
141 while ((ptr = strchr(ptr, from))) {
142 *ptr = to;
143 }
144 }
145 return string;
146 }
147
148 #define TRIM_LEFT 1
149 #define TRIM_RIGHT 2
150 #define TRIM_BOTH (TRIM_LEFT|TRIM_RIGHT)
151 local char *trim(char *string, size_t length, char what, int where)
152 {
153 if (where & TRIM_RIGHT) {
154 while (length-- && (string[length] == what)) {
155 string[length] = NUL;
156 }
157 }
158 if (where & TRIM_LEFT) {
159 while (*string == what) {
160 ++string;
161 }
162 }
163 return string;
164 }
165
166 local int strmatch(const char *match, const char *string, const char **begin, const char **end)
167 {
168 *begin = *end = NULL;
169
170 while (*match)
171 {
172 switch (*match)
173 {
174 case '*':
175 while (*match == '*' || *match == '?') {
176 ++match;
177 }
178
179 if (!*begin) {
180 *begin = string;
181 }
182
183 if (!*match) {
184 *end = string + strlen(string);
185 return 1;
186 }
187
188 if (!(string = strchr(string, *match))) {
189 *end = string;
190 return 0;
191 }
192 break;
193
194 case '?':
195 if (!*begin) {
196 *begin = string;
197 }
198 ++string;
199 ++match;
200 break;
201
202 default:
203 if (*match == *string) {
204 if (!*begin) {
205 *begin = string;
206 }
207 ++match;
208 } else {
209 if (*begin) {
210 *end = string - 1;
211 return 0;
212 }
213 }
214 ++string;
215 break;
216 }
217 }
218
219 *end = string;
220 return 1;
221 }
222
223 local char *struniqchr(char *string, char uniq)
224 {
225 char *ptr = string;
226
227 while (*ptr) {
228 if (*ptr == uniq && *(ptr + 1) == uniq) {
229 char *pos = ptr + 1;
230
231 while (*(pos + 1) == uniq) {
232 ++pos;
233 }
234
235 memmove(ptr, pos, strlen(pos) + 1);
236 }
237 ++ptr;
238 }
239
240 return string;
241 }
242
243 local char *domaintree_host(apr_pool_t *pool, MOD_DT_CNF *DT, const char *host_name)
244 {
245 size_t len;
246 char *port, *host;
247
248 if (EMPTY(host_name)) {
249 DT_LOG_WRN
250 "DomainTree: no host/server name"
251 DT_LOG_END
252 return NULL;
253 }
254
255 /* copy hostname */
256 host = apr_pstrdup(pool, host_name);
257
258 /* check for :NN port */
259 if ((port = strchr(host, ':'))) {
260 len = port - host;
261 *port = NUL;
262 } else {
263 len = strlen(host);
264 }
265
266 /* strip leading & trailing dots */
267 host = trim(host, len, '.', TRIM_BOTH);
268
269 DT_LOG_DBG
270 "DomainTree: host name = %s for %s", host, host_name
271 DT_LOG_END
272
273 return host;
274 }
275
276 local char *domaintree_path(apr_pool_t *pool, MOD_DT_CNF *DT, const char *host_name)
277 {
278 long depth = 0;
279 const char *host = host_name;
280 char *path = NULL, *host_ptr;
281
282 while ((host_ptr = strchr(host, '.'))) {
283
284 /* check max depth */
285 if (++depth > DT->maxdepth) {
286 DT_LOG_ERR
287 "DomainTree: maxdepth exceeded (%ld)", DT->maxdepth
288 DT_LOG_END
289 return NULL;
290 }
291
292 /* append part */
293 if (host_ptr - host) {
294
295 /* strip WWW */
296 if (DT->stripwww && (depth == 1) && (!strncmp(host, "www.", sizeof("www")))) {
297 DT_LOG_DBG
298 "DomainTree: strip www"
299 DT_LOG_END
300 } else {
301 path = apr_pstrcat(pool, apr_pstrndup(pool, host, host_ptr - host), "/", path, NULL);
302 }
303 }
304
305 host = host_ptr + 1;
306 }
307
308 /* append last part if any and duplicate full path */
309 if (*host) {
310 path = apr_pstrcat(pool, host, "/", path, NULL);
311 }
312
313 DT_LOG_DBG
314 "DomainTree: path name = %s for %s", path, host_name
315 DT_LOG_END
316
317 return path;
318 }
319
320 local void domaintree_fake(apr_pool_t *pool, MOD_DT_CNF *DT, char **path)
321 {
322 int i, more;
323 long recurlevel = 0;
324 apr_pool_t *local_pool;
325 const apr_array_header_t *header = apr_table_elts(DT->aliases.faketable);
326 apr_table_entry_t *array = (apr_table_entry_t *) header->elts;
327
328 if (APR_SUCCESS != apr_pool_create(&local_pool, pool)) {
329 return;
330 }
331
332 do {
333 more = 0;
334
335 if (recurlevel++ > DT->aliases.recursion) {
336 DT_LOG_ERR
337 "DomainTree: maximum alias recursion level (%ld) exceeded! "
338 "Check if you have recursive definitions of DomainTreeAlias directives.",
339 DT->aliases.recursion
340 DT_LOG_END
341 break;
342 }
343
344 for (i = 0; i < header->nelts; ++i) {
345 const char *begin, *end;
346
347 DT_LOG_DBG
348 "DomainTree: fake test = %s on %s", array[i].key, *path
349 DT_LOG_END
350
351 if (strmatch(array[i].key, *path, &begin, &end)) {
352 more = 1;
353 *path = apr_pstrcat(local_pool, "/", apr_pstrndup(local_pool, *path, begin - *path), "/", array[i].val, "/", end, NULL);
354
355 DT_LOG_DBG
356 "DomainTree: fake done = %s (%s <> %s)", *path, array[i].key, array[i].val
357 DT_LOG_END
358 }
359 }
360 } while (more && DT->aliases.recursion);
361
362 *path = apr_pstrdup(pool, struniqchr(*path, '/'));
363
364 apr_pool_destroy(local_pool);
365 }
366
367 local char *domaintree_cache_get(MOD_DT_CNF *DT, apr_time_t atime, const char *host)
368 {
369 char *path = NULL;
370 struct dircache_entry *cache_entry;
371
372 apr_global_mutex_lock(DT->dircache.lock);
373
374 if ((cache_entry = apr_hash_get(DT->dircache.hmap, host, APR_HASH_KEY_STRING))) {
375 cache_entry->lacc = atime;
376 path = cache_entry->path;
377 }
378
379 apr_global_mutex_unlock(DT->dircache.lock);
380
381 if (path) {
382 DT_LOG_DBG
383 "DomainTree: cache hit = %s for %s", path, host
384 DT_LOG_END
385 }
386
387 return path;
388 }
389
390 local void domaintree_cache_set(MOD_DT_CNF *DT, apr_time_t atime, const char *host, const char *path)
391 {
392 apr_pool_t *pool;
393 struct dircache_entry *cache_entry;
394
395 apr_pool_create(&pool, DT->dircache.pool);
396 cache_entry = apr_palloc(pool, sizeof(struct dircache_entry));
397
398 cache_entry->pool = pool;
399 cache_entry->lacc = atime;
400 cache_entry->host = apr_pstrdup(pool, host);
401 cache_entry->path = apr_pstrdup(pool, path);
402
403 apr_global_mutex_lock(DT->dircache.lock);
404
405 if (apr_hash_count(DT->dircache.hmap) >= DT->dircache.clim) {
406 apr_hash_index_t *idx;
407 struct dircache_entry *purge_this = NULL;
408
409 DT_LOG_WRN
410 "DomainTree: reached cache limit (%ld)", DT->dircache.clim
411 DT_LOG_END
412
413 for (idx = apr_hash_first(DT->dircache.pool, DT->dircache.hmap); idx; idx = apr_hash_next(idx)) {
414 struct dircache_entry *current;
415
416 apr_hash_this(idx, NULL, NULL, (void **) &current);
417 if ((!purge_this) || (purge_this->lacc > current->lacc)) {
418 purge_this = current;
419 }
420 }
421
422 if (purge_this) {
423 DT_LOG_DBG
424 "DomainTree: cache del = %s", purge_this->host
425 DT_LOG_END
426 apr_hash_set(DT->dircache.hmap, purge_this->host, APR_HASH_KEY_STRING, NULL);
427 apr_pool_destroy(purge_this->pool);
428 }
429 }
430 apr_hash_set(DT->dircache.hmap, cache_entry->host, APR_HASH_KEY_STRING, cache_entry);
431
432 apr_global_mutex_unlock(DT->dircache.lock);
433
434 DT_LOG_DBG
435 "DomainTree: cache set = %s for %s", path, host
436 DT_LOG_END
437 }
438
439 /* }}} */
440 /* {{{ Hooks */
441
442 static STATUS domaintree_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
443 {
444 ap_add_version_component(pconf, "MDT/" VERSION);
445 return OK;
446 }
447
448 static STATUS domaintree_hook_translate_name(request_rec *r)
449 {
450 MOD_DT_CNF *DT = NULL;
451 char *host, *path, *docroot;
452
453 if ((!(DT = GET_MOD_DT_CNF(r->server))) || (!DT->enabled)) {
454 return DECLINED;
455 }
456
457 DT_LOG_DBG
458 "DomainTree: processid = %d", (int) getpid()
459 DT_LOG_END
460
461 /* get a usable host name */
462 if (!(host = domaintree_host(r->pool, DT, ap_get_server_name(r)))) {
463 return DECLINED;
464 }
465
466 /* check cache */
467 if ((!DT->dircache.clim) || (!(path = domaintree_cache_get(DT, r->request_time, host)))) {
468 /* build path */
469 if (!(path = domaintree_path(r->pool, DT, host))) {
470 return DECLINED;
471 }
472
473 /* apply any aliases */
474 if (apr_table_elts(DT->aliases.faketable)->nelts) {
475 domaintree_fake(r->pool, DT, &path);
476 }
477
478 /* add to cache */
479 if (DT->dircache.clim) {
480 domaintree_cache_set(DT, r->request_time, host, path);
481 }
482 }
483
484 /* compose virtual docroot */
485 docroot = struniqchr(apr_pstrcat(r->pool, DT->prefix, "/", path, "/", DT->suffix, "/", NULL), '/');
486
487 /* stat docroot */
488 if (DT->statroot) {
489 apr_finfo_t sb;
490
491 switch (apr_stat(&sb, docroot, APR_FINFO_MIN, r->pool))
492 {
493 case APR_SUCCESS:
494 case APR_INCOMPLETE:
495 DT_LOG_DBG
496 "DomainTree: stat path = %s (success)", docroot
497 DT_LOG_END
498 break;
499
500 default:
501 DT_LOG_DBG
502 "DomainTree: stat path = %s (failure)", docroot
503 DT_LOG_END
504 return DECLINED;
505 break;
506 }
507 }
508
509 /* set virtual docroot */
510 apr_table_set(r->subprocess_env, "VIRTUAL_DOCUMENT_ROOT", docroot);
511
512 /* done */
513 r->canonical_filename = "";
514 r->filename = apr_pstrcat(r->pool, docroot, EMPTY(r->uri) ? NULL : ('/' == *r->uri ? r->uri + 1 : r->uri), NULL);
515
516 DT_LOG_DBG
517 "DomainTree: path done = %s", r->filename
518 DT_LOG_END
519
520 return OK;
521 }
522
523 static void domaintree_hooks(apr_pool_t *pool)
524 {
525 static const char * const pre[] = {"mod_alias.c", "mod_userdir.c", NULL};
526
527 ap_hook_post_config(domaintree_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE);
528 ap_hook_translate_name(domaintree_hook_translate_name, pre, NULL, APR_HOOK_FIRST);
529 }
530
531 /* }}} */
532 /* {{{ Configuration */
533
534 static void *domaintree_create_srv(apr_pool_t *p, server_rec *s)
535 {
536 MOD_DT_CNF *DT;
537
538 DT = (MOD_DT_CNF *) apr_palloc(p, sizeof(MOD_DT_CNF));
539
540 DT->server = s;
541 DT->enabled = 0;
542 DT->stripwww = 1;
543 DT->statroot = 0;
544 DT->maxdepth = 20;
545
546 DT->prefix = "/var/www";
547 DT->suffix = "public_html";
548
549 DT->aliases.recursion = 0;
550 DT->aliases.faketable = apr_table_make(p, 0);
551
552 DT->dircache.clim = 0;
553 DT->dircache.hmap = apr_hash_make(p);
554 apr_pool_create(&DT->dircache.pool, p);
555 apr_global_mutex_create(&DT->dircache.lock, __FILE__, APR_LOCK_DEFAULT, p);
556
557 return DT;
558 }
559
560 static const char *domaintree_enable(cmd_parms *cmd, void *conf, int flag)
561 {
562 GET_MOD_DT_CNF(cmd->server)->enabled = flag;
563 return NULL;
564 }
565
566 static const char *domaintree_stripwww(cmd_parms *cmd, void *conf, int flag)
567 {
568 GET_MOD_DT_CNF(cmd->server)->stripwww = flag;
569 return NULL;
570 }
571
572 static const char *domaintree_statroot(cmd_parms *cmd, void *conf, int flag)
573 {
574 GET_MOD_DT_CNF(cmd->server)->statroot = flag;
575 return NULL;
576 }
577
578 static const char *domaintree_prefix(cmd_parms *cmd, void *conf, const char *prefix)
579 {
580 GET_MOD_DT_CNF(cmd->server)->prefix = EMPTY(prefix) ? "/" : trim(apr_pstrdup(cmd->pool, prefix), strlen(prefix), '/', TRIM_RIGHT);
581 return NULL;
582 }
583
584 static const char *domaintree_suffix(cmd_parms *cmd, void *conf, const char *suffix)
585 {
586 GET_MOD_DT_CNF(cmd->server)->suffix = EMPTY(suffix) ? "" : trim(apr_pstrdup(cmd->pool, suffix), strlen(suffix), '/', TRIM_BOTH);
587 return NULL;
588 }
589
590 static const char *domaintree_maxdepth(cmd_parms *cmd, void *conf, const char *max_depth)
591 {
592 long depth;
593
594 if ((depth = atol(max_depth))) {
595 if (depth > 0L) {
596 GET_MOD_DT_CNF(cmd->server)->maxdepth = depth;
597 } else {
598 return "Maximum DomainTree depth cannot be negative.";
599 }
600 }
601
602 return NULL;
603 }
604
605 static const char *domaintree_aliasrecursion(cmd_parms *cmd, void *conf, const char *alias_recursion)
606 {
607 long recursion;
608
609 if ((recursion = atol(alias_recursion))) {
610 if (recursion > 0L) {
611 GET_MOD_DT_CNF(cmd->server)->aliases.recursion = recursion;
612 } else {
613 return "DomainTree alias recursion cannot be negative.";
614 }
615 }
616
617 return NULL;
618 }
619
620 static const char *domaintree_alias(cmd_parms *cmd, void *conf, const char *fake, const char *real)
621 {
622 char *f = strtr(apr_pstrdup(cmd->pool, fake), '.', '/'), *r = strtr(apr_pstrdup(cmd->pool, real), '.', '/');
623
624 apr_table_set(GET_MOD_DT_CNF(cmd->server)->aliases.faketable, trim(f, strlen(f), '/', TRIM_BOTH), trim(r, strlen(r), '/', TRIM_BOTH));
625
626 return NULL;
627 }
628
629 static const char *domaintree_cache(cmd_parms *cmd, void *conf, const char *cache)
630 {
631 long limit;
632
633 if ((limit = atol(cache))) {
634 if (limit > 0L) {
635 GET_MOD_DT_CNF(cmd->server)->dircache.clim = limit;
636 } else {
637 return "DomainTree cache limit cannot be negative.";
638 }
639 }
640
641 return NULL;
642 }
643
644 /* }}} */
645 /* {{{ Commands */
646
647 static command_rec domaintree_commands[] = {
648 AP_INIT_FLAG(
649 "DomainTreeEnabled", domaintree_enable, NULL, RSRC_CONF,
650 "Turn the module on or off."
651 ),
652
653 AP_INIT_FLAG(
654 "DomainTreeStripWWW", domaintree_stripwww, NULL, RSRC_CONF,
655 "Strip leading www from host. (default On)"
656 ),
657
658 AP_INIT_FLAG(
659 "DomainTreeStatRoot", domaintree_statroot, NULL, RSRC_CONF,
660 "Wheter to check for the evaluated virtual document root with a stat call. (default Off)"
661 ),
662
663 AP_INIT_TAKE1(
664 "DomainTreePrefix", domaintree_prefix, NULL, RSRC_CONF,
665 "DomainTree path prefix. (default /var/www) Do not forget the leading slash!"
666 ),
667
668 AP_INIT_TAKE1(
669 "DomainTreeSuffix", domaintree_suffix, NULL, RSRC_CONF,
670 "DomainTree path suffix. (default public_html)"
671 ),
672
673 AP_INIT_TAKE1(
674 "DomainTreeMaxdepth", domaintree_maxdepth, NULL, RSRC_CONF,
675 "DomainTree max path depth. (default 20)"
676 ),
677
678 AP_INIT_TAKE1(
679 "DomainTreeAliasRecursion", domaintree_aliasrecursion, NULL, RSRC_CONF,
680 "Whether (and how often at the maximum) DomainTree should walk recursively "
681 "through the aliases list as long as matching aliases are found. (default: 0 = turned off)"
682 ),
683
684 AP_INIT_TAKE2(
685 "DomainTreeAlias", domaintree_alias, NULL, RSRC_CONF,
686 "DomainTree aliases; e.g. DomainTreeAlias com/example/tickets com/example/support (dots or slashes equal)"
687 ),
688
689 AP_INIT_TAKE1(
690 "DomainTreeCache", domaintree_cache, NULL, RSRC_CONF,
691 "DomainTree server-wide host to directory cache; specify how many cache entries to allow (default: 0 = turned off)"
692 ),
693
694 { NULL }
695 };
696
697 /* }}} */
698 /* {{{ Module Administrativa */
699
700 module AP_MODULE_DECLARE_DATA domaintree_module = {
701 STANDARD20_MODULE_STUFF,
702 NULL, /* create per-dir */
703 NULL, /* merge per-dir */
704 domaintree_create_srv, /* create per-server */
705 NULL, /* merge per-server */
706 domaintree_commands, /* config commands */
707 domaintree_hooks /* hooks */
708 };
709
710 /* }}} */
711
712 /*
713 * Local variables:
714 * tab-width: 4
715 * c-basic-offset: 4
716 * End:
717 * vim600: noet sw=4 ts=4 fdm=marker
718 * vim<600: noet sw=4 ts=4
719 */