1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * mod_vhost_alias.c: support for dynamically configured mass virtual hosting
19 *
20 * Copyright (c) 1998-1999 Demon Internet Ltd.
21 *
22 * This software was submitted by Demon Internet to the Apache Software Foundation
23 * in May 1999. Future revisions and derivatives of this source code
24 * must acknowledge Demon Internet as the original contributor of
25 * this module. All other licensing and usage conditions are those
26 * of the Apache Software Foundation.
27 *
28 * Originally written by Tony Finch <fanf@demon.net> <dot@dotat.at>.
29 *
30 * Implementation ideas were taken from mod_alias.c. The overall
31 * concept is derived from the OVERRIDE_DOC_ROOT/OVERRIDE_CGIDIR
32 * patch to Apache 1.3b3 and a similar feature in Demon's thttpd,
33 * both written by James Grinter <jrg@blodwen.demon.co.uk>.
34 */
35
36#include "apr.h"
37#include "apr_strings.h"
38#include "ap_hooks.h"
39#include "apr_lib.h"
40
41#define APR_WANT_STRFUNC
42#include "apr_want.h"
43
44#include "httpd.h"
45#include "http_config.h"
46#include "http_core.h"
47#include "http_request.h"  /* for ap_hook_translate_name */
48
49
50module AP_MODULE_DECLARE_DATA vhost_alias_module;
51
52
53/*
54 * basic configuration things
55 * we abbreviate "mod_vhost_alias" to "mva" for shorter names
56 */
57
58typedef enum {
59    VHOST_ALIAS_UNSET, VHOST_ALIAS_NONE, VHOST_ALIAS_NAME, VHOST_ALIAS_IP
60} mva_mode_e;
61
62/*
63 * Per-server module config record.
64 */
65typedef struct mva_sconf_t {
66    const char *doc_root;
67    const char *cgi_root;
68    mva_mode_e doc_root_mode;
69    mva_mode_e cgi_root_mode;
70} mva_sconf_t;
71
72static void *mva_create_server_config(apr_pool_t *p, server_rec *s)
73{
74    mva_sconf_t *conf;
75
76    conf = (mva_sconf_t *) apr_pcalloc(p, sizeof(mva_sconf_t));
77    conf->doc_root = NULL;
78    conf->cgi_root = NULL;
79    conf->doc_root_mode = VHOST_ALIAS_UNSET;
80    conf->cgi_root_mode = VHOST_ALIAS_UNSET;
81    return conf;
82}
83
84static void *mva_merge_server_config(apr_pool_t *p, void *parentv, void *childv)
85{
86    mva_sconf_t *parent = (mva_sconf_t *) parentv;
87    mva_sconf_t *child = (mva_sconf_t *) childv;
88    mva_sconf_t *conf;
89
90    conf = (mva_sconf_t *) apr_pcalloc(p, sizeof(*conf));
91    if (child->doc_root_mode == VHOST_ALIAS_UNSET) {
92        conf->doc_root_mode = parent->doc_root_mode;
93        conf->doc_root = parent->doc_root;
94    }
95    else {
96        conf->doc_root_mode = child->doc_root_mode;
97        conf->doc_root = child->doc_root;
98    }
99    if (child->cgi_root_mode == VHOST_ALIAS_UNSET) {
100        conf->cgi_root_mode = parent->cgi_root_mode;
101        conf->cgi_root = parent->cgi_root;
102    }
103    else {
104        conf->cgi_root_mode = child->cgi_root_mode;
105        conf->cgi_root = child->cgi_root;
106    }
107    return conf;
108}
109
110
111/*
112 * These are just here to tell us what vhost_alias_set should do.
113 * We don't put anything into them; we just use the cell addresses.
114 */
115static int vhost_alias_set_doc_root_ip,
116    vhost_alias_set_cgi_root_ip,
117    vhost_alias_set_doc_root_name,
118    vhost_alias_set_cgi_root_name;
119
120static const char *vhost_alias_set(cmd_parms *cmd, void *dummy, const char *map)
121{
122    mva_sconf_t *conf;
123    mva_mode_e mode, *pmode;
124    const char **pmap;
125    const char *p;
126
127    conf = (mva_sconf_t *) ap_get_module_config(cmd->server->module_config,
128                                                &vhost_alias_module);
129    /* there ought to be a better way of doing this */
130    if (&vhost_alias_set_doc_root_ip == cmd->info) {
131        mode = VHOST_ALIAS_IP;
132        pmap = &conf->doc_root;
133        pmode = &conf->doc_root_mode;
134    }
135    else if (&vhost_alias_set_cgi_root_ip == cmd->info) {
136        mode = VHOST_ALIAS_IP;
137        pmap = &conf->cgi_root;
138        pmode = &conf->cgi_root_mode;
139    }
140    else if (&vhost_alias_set_doc_root_name == cmd->info) {
141        mode = VHOST_ALIAS_NAME;
142        pmap = &conf->doc_root;
143        pmode = &conf->doc_root_mode;
144    }
145    else if (&vhost_alias_set_cgi_root_name == cmd->info) {
146        mode = VHOST_ALIAS_NAME;
147        pmap = &conf->cgi_root;
148        pmode = &conf->cgi_root_mode;
149    }
150    else {
151        return "INTERNAL ERROR: unknown command info";
152    }
153
154    if (!ap_os_is_path_absolute(cmd->pool, map)) {
155        if (strcasecmp(map, "none")) {
156            return "format string must be an absolute path, or 'none'";
157        }
158        *pmap = NULL;
159        *pmode = VHOST_ALIAS_NONE;
160        return NULL;
161    }
162
163    /* sanity check */
164    p = map;
165    while (*p != '\0') {
166        if (*p++ != '%') {
167            continue;
168        }
169        /* we just found a '%' */
170        if (*p == 'p' || *p == '%') {
171            ++p;
172            continue;
173        }
174        /* optional dash */
175        if (*p == '-') {
176            ++p;
177        }
178        /* digit N */
179        if (apr_isdigit(*p)) {
180            ++p;
181        }
182        else {
183            return "syntax error in format string";
184        }
185        /* optional plus */
186        if (*p == '+') {
187            ++p;
188        }
189        /* do we end here? */
190        if (*p != '.') {
191            continue;
192        }
193        ++p;
194        /* optional dash */
195        if (*p == '-') {
196            ++p;
197        }
198        /* digit M */
199        if (apr_isdigit(*p)) {
200            ++p;
201        }
202        else {
203            return "syntax error in format string";
204        }
205        /* optional plus */
206        if (*p == '+') {
207            ++p;
208        }
209    }
210    *pmap = map;
211    *pmode = mode;
212    return NULL;
213}
214
215static const command_rec mva_commands[] =
216{
217    AP_INIT_TAKE1("VirtualScriptAlias", vhost_alias_set,
218                  &vhost_alias_set_cgi_root_name, RSRC_CONF,
219                  "how to create a ScriptAlias based on the host"),
220    AP_INIT_TAKE1("VirtualDocumentRoot", vhost_alias_set,
221                  &vhost_alias_set_doc_root_name, RSRC_CONF,
222                  "how to create the DocumentRoot based on the host"),
223    AP_INIT_TAKE1("VirtualScriptAliasIP", vhost_alias_set,
224                  &vhost_alias_set_cgi_root_ip, RSRC_CONF,
225                  "how to create a ScriptAlias based on the host"),
226    AP_INIT_TAKE1("VirtualDocumentRootIP", vhost_alias_set,
227                  &vhost_alias_set_doc_root_ip, RSRC_CONF,
228                  "how to create the DocumentRoot based on the host"),
229    { NULL }
230};
231
232
233/*
234 * This really wants to be a nested function
235 * but C is too feeble to support them.
236 */
237static APR_INLINE void vhost_alias_checkspace(request_rec *r, char *buf,
238                                             char **pdest, int size)
239{
240    /* XXX: what if size > HUGE_STRING_LEN? */
241    if (*pdest + size > buf + HUGE_STRING_LEN) {
242        **pdest = '\0';
243        if (r->filename) {
244            r->filename = apr_pstrcat(r->pool, r->filename, buf, NULL);
245        }
246        else {
247            r->filename = apr_pstrdup(r->pool, buf);
248        }
249        *pdest = buf;
250    }
251}
252
253static void vhost_alias_interpolate(request_rec *r, const char *name,
254                                    const char *map, const char *uri)
255{
256    /* 0..9 9..0 */
257    enum { MAXDOTS = 19 };
258    const char *dots[MAXDOTS+1];
259    int ndots;
260
261    char buf[HUGE_STRING_LEN];
262    char *dest;
263    const char *docroot;
264
265    int N, M, Np, Mp, Nd, Md;
266    const char *start, *end;
267
268    const char *p;
269
270    ndots = 0;
271    dots[ndots++] = name-1; /* slightly naughty */
272    for (p = name; *p; ++p){
273        if (*p == '.' && ndots < MAXDOTS) {
274            dots[ndots++] = p;
275        }
276    }
277    dots[ndots] = p;
278
279    r->filename = NULL;
280
281    dest = buf;
282    while (*map) {
283        if (*map != '%') {
284            /* normal characters */
285            vhost_alias_checkspace(r, buf, &dest, 1);
286            *dest++ = *map++;
287            continue;
288        }
289        /* we are in a format specifier */
290        ++map;
291        /* %% -> % */
292        if (*map == '%') {
293            ++map;
294            vhost_alias_checkspace(r, buf, &dest, 1);
295            *dest++ = '%';
296            continue;
297        }
298        /* port number */
299        if (*map == 'p') {
300            ++map;
301            /* no. of decimal digits in a short plus one */
302            vhost_alias_checkspace(r, buf, &dest, 7);
303            dest += apr_snprintf(dest, 7, "%d", ap_get_server_port(r));
304            continue;
305        }
306        /* deal with %-N+.-M+ -- syntax is already checked */
307        M = 0;   /* value */
308        Np = Mp = 0; /* is there a plus? */
309        Nd = Md = 0; /* is there a dash? */
310        if (*map == '-') ++map, Nd = 1;
311        N = *map++ - '0';
312        if (*map == '+') ++map, Np = 1;
313        if (*map == '.') {
314            ++map;
315            if (*map == '-') {
316                ++map, Md = 1;
317            }
318            M = *map++ - '0';
319            if (*map == '+') {
320                ++map, Mp = 1;
321            }
322        }
323        /* note that N and M are one-based indices, not zero-based */
324        start = dots[0]+1; /* ptr to the first character */
325        end = dots[ndots]; /* ptr to the character after the last one */
326        if (N != 0) {
327            if (N > ndots) {
328                start = "_";
329                end = start+1;
330            }
331            else if (!Nd) {
332                start = dots[N-1]+1;
333                if (!Np) {
334                    end = dots[N];
335                }
336            }
337            else {
338                if (!Np) {
339                    start = dots[ndots-N]+1;
340                }
341                end = dots[ndots-N+1];
342            }
343        }
344        if (M != 0) {
345            if (M > end - start) {
346                start = "_";
347                end = start+1;
348            }
349            else if (!Md) {
350                start = start+M-1;
351                if (!Mp) {
352                    end = start+1;
353                }
354            }
355            else {
356                if (!Mp) {
357                    start = end-M;
358                }
359                end = end-M+1;
360            }
361        }
362        vhost_alias_checkspace(r, buf, &dest, end - start);
363        for (p = start; p < end; ++p) {
364            *dest++ = apr_tolower(*p);
365        }
366    }
367    /* no double slashes */
368    if (dest - buf > 0 && dest[-1] == '/') {
369        --dest;
370    }
371    *dest = '\0';
372
373    if (r->filename)
374        docroot = apr_pstrcat(r->pool, r->filename, buf, NULL);
375    else
376        docroot = apr_pstrdup(r->pool, buf);
377    r->filename = apr_pstrcat(r->pool, docroot, uri, NULL);
378    ap_set_context_info(r, NULL, docroot);
379    ap_set_document_root(r, docroot);
380}
381
382static int mva_translate(request_rec *r)
383{
384    mva_sconf_t *conf;
385    const char *name, *map, *uri;
386    mva_mode_e mode;
387    const char *cgi;
388
389    conf = (mva_sconf_t *) ap_get_module_config(r->server->module_config,
390                                              &vhost_alias_module);
391    cgi = NULL;
392    if (conf->cgi_root) {
393        cgi = strstr(r->uri, "cgi-bin/");
394        if (cgi && (cgi != r->uri + strspn(r->uri, "/"))) {
395            cgi = NULL;
396        }
397    }
398    if (cgi) {
399        mode = conf->cgi_root_mode;
400        map = conf->cgi_root;
401        uri = cgi + strlen("cgi-bin");
402    }
403    else if (r->uri[0] == '/') {
404        mode = conf->doc_root_mode;
405        map = conf->doc_root;
406        uri = r->uri;
407    }
408    else {
409        return DECLINED;
410    }
411
412    if (mode == VHOST_ALIAS_NAME) {
413        name = ap_get_server_name(r);
414    }
415    else if (mode == VHOST_ALIAS_IP) {
416        name = r->connection->local_ip;
417    }
418    else {
419        return DECLINED;
420    }
421
422    /* ### There is an optimization available here to determine the
423     * absolute portion of the path from the server config phase,
424     * through the first % segment, and note that portion of the path
425     * canonical_path buffer.
426     */
427    r->canonical_filename = "";
428    vhost_alias_interpolate(r, name, map, uri);
429
430    if (cgi) {
431        /* see is_scriptaliased() in mod_cgi */
432        r->handler = "cgi-script";
433        apr_table_setn(r->notes, "alias-forced-type", r->handler);
434        ap_set_context_info(r, "/cgi-bin", NULL);
435    }
436
437    return OK;
438}
439
440static void register_hooks(apr_pool_t *p)
441{
442    static const char * const aszPre[]={ "mod_alias.c","mod_userdir.c",NULL };
443
444    ap_hook_translate_name(mva_translate, aszPre, NULL, APR_HOOK_MIDDLE);
445}
446
447AP_DECLARE_MODULE(vhost_alias) =
448{
449    STANDARD20_MODULE_STUFF,
450    NULL,                       /* dir config creater */
451    NULL,                       /* dir merger --- default is to override */
452    mva_create_server_config,   /* server config */
453    mva_merge_server_config,    /* merge server configs */
454    mva_commands,               /* command apr_table_t */
455    register_hooks              /* register hooks */
456};
457
458