1 #include "first.h"
2
3 #include "base.h"
4 #include "plugin.h"
5 #include "log.h"
6 #include "stat_cache.h"
7
8 #include <stdlib.h>
9 #include <string.h>
10
11 /**
12 *
13 * #
14 * # define a pattern for the host url finding
15 * # %% => % sign
16 * # %0 => domain name + tld
17 * # %1 => tld
18 * # %2 => domain name without tld
19 * # %3 => subdomain 1 name
20 * # %4 => subdomain 2 name
21 * # %_ => fqdn (without port info)
22 * #
23 * evhost.path-pattern = "/home/ckruse/dev/www/%3/htdocs/"
24 *
25 */
26
27 typedef struct {
28 /* pieces for path creation */
29 const buffer *path_pieces;
30 } plugin_config;
31
32 typedef struct {
33 PLUGIN_DATA;
34 plugin_config defaults;
35 plugin_config conf;
36
37 array split_vals;
38 } plugin_data;
39
INIT_FUNC(mod_evhost_init)40 INIT_FUNC(mod_evhost_init) {
41 return ck_calloc(1, sizeof(plugin_data));
42 }
43
mod_evhost_free_path_pieces(const buffer * path_pieces)44 static void mod_evhost_free_path_pieces(const buffer *path_pieces) {
45 buffer *b;
46 *(const buffer **)&b = path_pieces;
47 for (; path_pieces->ptr; ++path_pieces) free(path_pieces->ptr);
48 free(b);
49 }
50
FREE_FUNC(mod_evhost_free)51 FREE_FUNC(mod_evhost_free) {
52 plugin_data * const p = p_d;
53 array_free_data(&p->split_vals);
54 if (NULL == p->cvlist) return;
55 /* (init i to 0 if global context; to 1 to skip empty global context) */
56 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
57 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
58 for (; -1 != cpv->k_id; ++cpv) {
59 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
60 switch (cpv->k_id) {
61 case 0: /* evhost.path-pattern */
62 mod_evhost_free_path_pieces(cpv->v.v);
63 break;
64 default:
65 break;
66 }
67 }
68 }
69 }
70
71 __attribute_cold__
mod_evhost_parse_pattern_err(buffer * bptr)72 static buffer * mod_evhost_parse_pattern_err(buffer *bptr) {
73 for (; bptr->ptr; ++bptr) free(bptr->ptr);
74 return NULL;
75 }
76
mod_evhost_parse_pattern(const char * ptr)77 static buffer * mod_evhost_parse_pattern(const char *ptr) {
78 uint32_t used = 0;
79 const uint32_t sz = 127;/* (sz+1 must match bptr[] num elts below) */
80 const char *pos;
81 buffer bptr[128]; /* (128 elements takes 2k on stack in 64-bit) */
82 memset(bptr, 0, sizeof(bptr));
83
84 for(pos=ptr;*ptr;ptr++) {
85 if(*ptr == '%') {
86 size_t len;
87 if (used >= sz-1) /* (should not happen) */
88 return mod_evhost_parse_pattern_err(bptr);
89
90 /* "%%" "%_" "%x" "%{x.y}" where x and y are *single digit* 0 - 9 */
91 if (ptr[1] == '%' || ptr[1] == '_' || light_isdigit(ptr[1])) {
92 len = 2;
93 } else if (ptr[1] == '{') {
94 if (!light_isdigit(ptr[2]))
95 return mod_evhost_parse_pattern_err(bptr);
96 if (ptr[3] == '.') {
97 if (!light_isdigit(ptr[4]))
98 return mod_evhost_parse_pattern_err(bptr);
99 if (ptr[5] != '}')
100 return mod_evhost_parse_pattern_err(bptr);
101 len = 6;
102 } else if (ptr[3] == '}') {
103 len = 4;
104 } else {
105 return mod_evhost_parse_pattern_err(bptr);
106 }
107 } else {
108 return mod_evhost_parse_pattern_err(bptr);
109 }
110
111 buffer_copy_string_len(bptr+used, pos, ptr-pos);
112 pos = ptr + len;
113
114 buffer_copy_string_len(bptr+used+1, ptr, len);
115 ptr += len - 1; /*(ptr++ in for() loop)*/
116
117 used += 2;
118 }
119 }
120
121 if(*pos != '\0') {
122 if (used >= sz) /* (should not happen) */
123 return mod_evhost_parse_pattern_err(bptr);
124 buffer_copy_string_len(bptr+used, pos, ptr-pos);
125 ++used;
126 }
127
128 buffer * const path_pieces = ck_malloc((used+1) * sizeof(buffer));
129 return memcpy(path_pieces, bptr, (used+1) * sizeof(buffer));
130 }
131
mod_evhost_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)132 static void mod_evhost_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
133 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
134 case 0: /* evhost.path-pattern */
135 if (cpv->vtype == T_CONFIG_LOCAL)
136 pconf->path_pieces = cpv->v.v;
137 break;
138 default:/* should not happen */
139 return;
140 }
141 }
142
mod_evhost_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)143 static void mod_evhost_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
144 do {
145 mod_evhost_merge_config_cpv(pconf, cpv);
146 } while ((++cpv)->k_id != -1);
147 }
148
mod_evhost_patch_config(request_st * const r,plugin_data * const p)149 static void mod_evhost_patch_config(request_st * const r, plugin_data * const p) {
150 p->conf = p->defaults; /* copy small struct instead of memcpy() */
151 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
152 for (int i = 1, used = p->nconfig; i < used; ++i) {
153 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
154 mod_evhost_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
155 }
156 }
157
SETDEFAULTS_FUNC(mod_evhost_set_defaults)158 SETDEFAULTS_FUNC(mod_evhost_set_defaults) {
159 static const config_plugin_keys_t cpk[] = {
160 { CONST_STR_LEN("evhost.path-pattern"),
161 T_CONFIG_STRING,
162 T_CONFIG_SCOPE_CONNECTION }
163 ,{ NULL, 0,
164 T_CONFIG_UNSET,
165 T_CONFIG_SCOPE_UNSET }
166 };
167
168 plugin_data * const p = p_d;
169 if (!config_plugin_values_init(srv, p, cpk, "mod_evhost"))
170 return HANDLER_ERROR;
171
172 /* process and validate config directives
173 * (init i to 0 if global context; to 1 to skip empty global context) */
174 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
175 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
176 for (; -1 != cpv->k_id; ++cpv) {
177 switch (cpv->k_id) {
178 case 0: /* evhost.path-pattern */
179 if (!buffer_is_blank(cpv->v.b)) {
180 const char * const ptr = cpv->v.b->ptr;
181 cpv->v.v = mod_evhost_parse_pattern(ptr);
182 if (NULL == cpv->v.v) {
183 log_error(srv->errh, __FILE__, __LINE__,
184 "invalid evhost.path-pattern: %s", ptr);
185 return HANDLER_ERROR;
186 }
187 cpv->vtype = T_CONFIG_LOCAL;
188 }
189 break;
190 default:/* should not happen */
191 break;
192 }
193 }
194 }
195
196 /* initialize p->defaults from global config context */
197 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
198 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
199 if (-1 != cpv->k_id)
200 mod_evhost_merge_config(&p->defaults, cpv);
201 }
202
203 return HANDLER_GO_ON;
204 }
205
206 /**
207 * assign the different parts of the domain to array-indezes (sub2.sub1.domain.tld)
208 * - %0 - domain.tld
209 * - %1 - tld
210 * - %2 - domain
211 * - %3 - sub1
212 * - ...
213 */
214
mod_evhost_parse_host(buffer * key,array * host,buffer * authority)215 static void mod_evhost_parse_host(buffer *key, array *host, buffer *authority) {
216 char *ptr = authority->ptr + buffer_clen(authority);
217 char *colon = ptr; /* needed to filter out the colon (if exists) */
218 int first = 1;
219 int i;
220
221 /*if (ptr == authority->ptr) return;*//*(no authority checked earlier)*/
222
223 if (*authority->ptr == '[') { /* authority is IPv6 literal address */
224 colon = ptr;
225 if (ptr[-1] != ']') {
226 do { --ptr; } while (ptr > authority->ptr && ptr[-1] != ']');
227 if (*ptr != ':') return; /*(should not happen for valid authority)*/
228 colon = ptr;
229 }
230 ptr = authority->ptr;
231 array_set_key_value(host,CONST_STR_LEN("%0"),ptr,colon-ptr);
232 return;
233 }
234
235 /* first, find the domain + tld */
236 for(; ptr > authority->ptr; --ptr) {
237 if(*ptr == '.') {
238 if(first) first = 0;
239 else break;
240 } else if(*ptr == ':') {
241 colon = ptr;
242 first = 1;
243 }
244 }
245
246 /* if we stopped at a dot, skip the dot */
247 if (*ptr == '.') ptr++;
248 array_set_key_value(host, CONST_STR_LEN("%0"), ptr, colon-ptr);
249
250 /* if the : is not the start of the authority, go on parsing the hostname */
251
252 if (colon != authority->ptr) {
253 for(ptr = colon - 1, i = 1; ptr > authority->ptr; --ptr) {
254 if(*ptr == '.') {
255 if (ptr != colon - 1) {
256 /* is something between the dots */
257 buffer_clear(key);
258 buffer_append_char(key, '%');
259 buffer_append_int(key, i++);
260 array_set_key_value(host, BUF_PTR_LEN(key), ptr+1, colon-ptr-1);
261 }
262 colon = ptr;
263 }
264 }
265
266 /* if the . is not the first character of the hostname */
267 if (colon != ptr) {
268 buffer_clear(key);
269 buffer_append_char(key, '%');
270 buffer_append_int(key, i /* ++ */);
271 array_set_key_value(host, BUF_PTR_LEN(key), ptr, colon-ptr);
272 }
273 }
274 }
275
mod_evhost_build_doc_root_path(buffer * b,array * parsed_host,buffer * authority,const buffer * path_pieces)276 static void mod_evhost_build_doc_root_path(buffer *b, array *parsed_host, buffer *authority, const buffer *path_pieces) {
277 array_reset_data_strings(parsed_host);
278 mod_evhost_parse_host(b, parsed_host, authority);
279 buffer_clear(b);
280
281 for (const char *ptr; (ptr = path_pieces->ptr); ++path_pieces) {
282 if (*ptr == '%') {
283 const data_string *ds;
284
285 if (*(ptr+1) == '%') {
286 /* %% */
287 buffer_append_char(b, '%');
288 } else if (*(ptr+1) == '_' ) {
289 /* %_ == full hostname */
290 char *colon = strchr(authority->ptr, ':');
291
292 if(colon == NULL) {
293 buffer_append_string_buffer(b, authority); /* adds fqdn */
294 } else {
295 /* strip the port out of the authority-part of the URI scheme */
296 buffer_append_string_len(b, authority->ptr, colon - authority->ptr); /* adds fqdn */
297 }
298 } else if (ptr[1] == '{' ) {
299 char s[3] = "% ";
300 s[1] = ptr[2]; /*(assumes single digit before '.', and, optionally, '.' and single digit after '.')*/
301 if (NULL != (ds = (data_string *)array_get_element_klen(parsed_host, s, 2))) {
302 if (ptr[3] != '.' || ptr[4] == '0') {
303 buffer_append_string_buffer(b, &ds->value);
304 } else {
305 if ((size_t)(ptr[4]-'0') <= buffer_clen(&ds->value)) {
306 buffer_append_char(b, ds->value.ptr[(ptr[4]-'0')-1]);
307 }
308 }
309 } else {
310 /* unhandled %-sequence */
311 }
312 } else if (NULL != (ds = (data_string *)array_get_element_klen(parsed_host, BUF_PTR_LEN(path_pieces)))) {
313 buffer_append_string_buffer(b, &ds->value);
314 } else {
315 /* unhandled %-sequence */
316 }
317 } else {
318 buffer_append_string_buffer(b, path_pieces);
319 }
320 }
321
322 buffer_append_slash(b);
323 }
324
mod_evhost_uri_handler(request_st * const r,void * p_d)325 static handler_t mod_evhost_uri_handler(request_st * const r, void *p_d) {
326 plugin_data *p = p_d;
327
328 /* not authority set */
329 if (buffer_is_blank(&r->uri.authority)) return HANDLER_GO_ON;
330
331 mod_evhost_patch_config(r, p);
332
333 /* missing even default(global) conf */
334 if (NULL == p->conf.path_pieces) return HANDLER_GO_ON;
335
336 buffer * const b = r->tmp_buf;/*(tmp_buf cleared before use in call below)*/
337 mod_evhost_build_doc_root_path(b, &p->split_vals, &r->uri.authority, p->conf.path_pieces);
338
339 if (!stat_cache_path_isdir(b)) {
340 log_perror(r->conf.errh, __FILE__, __LINE__, "%s", b->ptr);
341 } else {
342 buffer_copy_buffer(&r->physical.doc_root, b);
343 }
344
345 return HANDLER_GO_ON;
346 }
347
348
349 __attribute_cold__
350 int mod_evhost_plugin_init(plugin *p);
mod_evhost_plugin_init(plugin * p)351 int mod_evhost_plugin_init(plugin *p) {
352 p->version = LIGHTTPD_VERSION_ID;
353 p->name = "evhost";
354 p->init = mod_evhost_init;
355 p->set_defaults = mod_evhost_set_defaults;
356 p->handle_docroot = mod_evhost_uri_handler;
357 p->cleanup = mod_evhost_free;
358
359 return 0;
360 }
361