xref: /lighttpd1.4/src/mod_alias.c (revision fcf0dc3e)
1 #include "first.h"
2 
3 #include "base.h"
4 #include "array.h"
5 #include "buffer.h"
6 #include "log.h"
7 
8 #include "plugin.h"
9 
10 #include <stdlib.h>
11 #include <string.h>
12 
13 typedef struct {
14     const array *alias;
15 } plugin_config;
16 
17 typedef struct {
18     PLUGIN_DATA;
19     plugin_config defaults;
20     plugin_config conf;
21 } plugin_data;
22 
23 INIT_FUNC(mod_alias_init) {
24     return calloc(1, sizeof(plugin_data));
25 }
26 
27 static void mod_alias_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
28     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
29       case 0: /* alias.url */
30         pconf->alias = cpv->v.a;
31         break;
32       default:/* should not happen */
33         return;
34     }
35 }
36 
37 static void mod_alias_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
38     do {
39         mod_alias_merge_config_cpv(pconf, cpv);
40     } while ((++cpv)->k_id != -1);
41 }
42 
43 static void mod_alias_patch_config(request_st * const r, plugin_data * const p) {
44     p->conf = p->defaults; /* copy small struct instead of memcpy() */
45     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
46     for (int i = 1, used = p->nconfig; i < used; ++i) {
47         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
48             mod_alias_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
49     }
50 }
51 
52 static int mod_alias_check_order(server * const srv, const array * const a) {
53     for (uint32_t j = 0; j < a->used; ++j) {
54         const buffer * const prefix = &a->data[j]->key;
55         const size_t plen = buffer_clen(prefix);
56         for (uint32_t k = j + 1; k < a->used; ++k) {
57             const buffer * const key = &a->data[k]->key;
58             if (buffer_clen(key) < plen) {
59                 break;
60             }
61             if (memcmp(key->ptr, prefix->ptr, plen) != 0) {
62                 break;
63             }
64             /* ok, they have same prefix. check position */
65             const data_unset *dj = a->data[j];
66             const data_unset *dk = a->data[k];
67             const data_unset **data = (const data_unset **)a->data;
68             while (*data != dj && *data != dk) ++data;
69             if (*data == dj) {
70                 log_error(srv->errh, __FILE__, __LINE__,
71                   "alias.url: `%s' will never match as `%s' matched first",
72                   key->ptr, prefix->ptr);
73                 return 0;
74             }
75         }
76     }
77     return 1;
78 }
79 
80 SETDEFAULTS_FUNC(mod_alias_set_defaults) {
81     static const config_plugin_keys_t cpk[] = {
82       { CONST_STR_LEN("alias.url"),
83         T_CONFIG_ARRAY_KVSTRING,
84         T_CONFIG_SCOPE_CONNECTION }
85      ,{ NULL, 0,
86         T_CONFIG_UNSET,
87         T_CONFIG_SCOPE_UNSET }
88     };
89 
90     plugin_data * const p = p_d;
91     if (!config_plugin_values_init(srv, p, cpk, "mod_alias"))
92         return HANDLER_ERROR;
93 
94     /* process and validate config directives
95      * (init i to 0 if global context; to 1 to skip empty global context) */
96     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
97         const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
98         for (; -1 != cpv->k_id; ++cpv) {
99             switch (cpv->k_id) {
100               case 0: /* alias.url */
101                 if (cpv->v.a->used >= 2 && !mod_alias_check_order(srv,cpv->v.a))
102                     return HANDLER_ERROR;
103                 break;
104               default:/* should not happen */
105                 break;
106             }
107         }
108     }
109 
110     /* initialize p->defaults from global config context */
111     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
112         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
113         if (-1 != cpv->k_id)
114             mod_alias_merge_config(&p->defaults, cpv);
115     }
116 
117     return HANDLER_GO_ON;
118 }
119 
120 static handler_t
121 mod_alias_remap (request_st * const r, const array * const aliases)
122 {
123     /* do not include trailing slash on basedir */
124     uint32_t basedir_len = buffer_clen(&r->physical.basedir);
125     if (buffer_has_pathsep_suffix(&r->physical.basedir)) --basedir_len;
126 
127     const uint32_t path_len = buffer_clen(&r->physical.path);
128     if (0 == path_len || path_len < basedir_len) return HANDLER_GO_ON;
129 
130     const uint32_t uri_len = path_len - basedir_len;
131     const char *uri_ptr = r->physical.path.ptr + basedir_len;
132     data_string * const ds = (data_string *)
133       (!r->conf.force_lowercase_filenames
134         ? array_match_key_prefix_klen(aliases, uri_ptr, uri_len)
135         : array_match_key_prefix_nc_klen(aliases, uri_ptr, uri_len));
136     if (NULL == ds) return HANDLER_GO_ON;
137 
138     /* matched */
139 
140     const uint32_t alias_len = buffer_clen(&ds->key);
141     const uint32_t vlen = buffer_clen(&ds->value);
142 
143     /* check for path traversal in url-path following alias if key
144      * does not end in slash, but replacement value ends in slash */
145     if (uri_ptr[alias_len] == '.') {
146         const char *s = uri_ptr + alias_len + 1;
147         if (*s == '.') ++s;
148         if (*s == '/' || *s == '\0') {
149             if (0 != alias_len && ds->key.ptr[alias_len-1] != '/'
150                 && 0 != vlen && ds->value.ptr[vlen-1] == '/') {
151                 r->http_status = 403;
152                 return HANDLER_FINISHED;
153             }
154         }
155     }
156 
157     /*(not buffer_append_path_len();
158      * alias could be prefix instead of complete path segment,
159      * (though resulting r->physical.basedir would not be a dir))*/
160     if (vlen != basedir_len + alias_len) {
161         const uint32_t nlen = vlen + uri_len - alias_len;
162         if (path_len + buffer_string_space(&r->physical.path) < nlen) {
163             buffer_string_prepare_append(&r->physical.path, nlen - path_len);
164             uri_ptr = r->physical.path.ptr + basedir_len;/*(refresh if alloc)*/
165         }
166         memmove(r->physical.path.ptr + vlen,
167                 uri_ptr + alias_len, uri_len - alias_len);
168         buffer_truncate(&r->physical.path, nlen);
169     }
170     memcpy(r->physical.path.ptr, ds->value.ptr, vlen);
171 
172     buffer_copy_string_len(&r->physical.basedir, ds->value.ptr, vlen);
173 
174     return HANDLER_GO_ON;
175 }
176 
177 PHYSICALPATH_FUNC(mod_alias_physical_handler) {
178     plugin_data * const p = p_d;
179     mod_alias_patch_config(r, p);
180     return p->conf.alias ? mod_alias_remap(r, p->conf.alias) : HANDLER_GO_ON;
181 }
182 
183 
184 int mod_alias_plugin_init(plugin *p);
185 int mod_alias_plugin_init(plugin *p) {
186 	p->version     = LIGHTTPD_VERSION_ID;
187 	p->name        = "alias";
188 
189 	p->init           = mod_alias_init;
190 	p->handle_physical= mod_alias_physical_handler;
191 	p->set_defaults   = mod_alias_set_defaults;
192 
193 	return 0;
194 }
195