1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
4 
5 #include "plugin.h"
6 #include "stat_cache.h"
7 
8 #include <ctype.h>
9 #include <stdlib.h>
10 #include <string.h>
11 
12 #ifdef HAVE_PCRE_H
13 typedef struct {
14 	pcre *key;
15 
16 	buffer *value;
17 
18 	int once;
19 } rewrite_rule;
20 
21 typedef struct {
22 	rewrite_rule **ptr;
23 
24 	size_t used;
25 	size_t size;
26 } rewrite_rule_buffer;
27 
28 typedef struct {
29 	rewrite_rule_buffer *rewrite;
30 	rewrite_rule_buffer *rewrite_NF;
31 	data_config *context, *context_NF; /* to which apply me */
32 } plugin_config;
33 
34 typedef struct {
35 	enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state;
36 	int loops;
37 } handler_ctx;
38 
39 typedef struct {
40 	PLUGIN_DATA;
41 	buffer *match_buf;
42 
43 	plugin_config **config_storage;
44 
45 	plugin_config conf;
46 } plugin_data;
47 
handler_ctx_init(void)48 static handler_ctx * handler_ctx_init(void) {
49 	handler_ctx * hctx;
50 
51 	hctx = calloc(1, sizeof(*hctx));
52 
53 	hctx->state = REWRITE_STATE_UNSET;
54 	hctx->loops = 0;
55 
56 	return hctx;
57 }
58 
handler_ctx_free(handler_ctx * hctx)59 static void handler_ctx_free(handler_ctx *hctx) {
60 	free(hctx);
61 }
62 
rewrite_rule_buffer_init(void)63 static rewrite_rule_buffer *rewrite_rule_buffer_init(void) {
64 	rewrite_rule_buffer *kvb;
65 
66 	kvb = calloc(1, sizeof(*kvb));
67 
68 	return kvb;
69 }
70 
rewrite_rule_buffer_append(rewrite_rule_buffer * kvb,buffer * key,buffer * value,int once)71 static int rewrite_rule_buffer_append(rewrite_rule_buffer *kvb, buffer *key, buffer *value, int once) {
72 	size_t i;
73 	const char *errptr;
74 	int erroff;
75 
76 	if (!key) return -1;
77 
78 	if (kvb->size == 0) {
79 		kvb->size = 4;
80 		kvb->used = 0;
81 
82 		kvb->ptr = malloc(kvb->size * sizeof(*kvb->ptr));
83 
84 		for(i = 0; i < kvb->size; i++) {
85 			kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
86 		}
87 	} else if (kvb->used == kvb->size) {
88 		kvb->size += 4;
89 
90 		kvb->ptr = realloc(kvb->ptr, kvb->size * sizeof(*kvb->ptr));
91 
92 		for(i = kvb->used; i < kvb->size; i++) {
93 			kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
94 		}
95 	}
96 
97 	if (NULL == (kvb->ptr[kvb->used]->key = pcre_compile(key->ptr,
98 							    0, &errptr, &erroff, NULL))) {
99 
100 		return -1;
101 	}
102 
103 	kvb->ptr[kvb->used]->value = buffer_init();
104 	buffer_copy_string_buffer(kvb->ptr[kvb->used]->value, value);
105 	kvb->ptr[kvb->used]->once = once;
106 
107 	kvb->used++;
108 
109 	return 0;
110 }
111 
rewrite_rule_buffer_free(rewrite_rule_buffer * kvb)112 static void rewrite_rule_buffer_free(rewrite_rule_buffer *kvb) {
113 	size_t i;
114 
115 	for (i = 0; i < kvb->size; i++) {
116 		if (kvb->ptr[i]->key) pcre_free(kvb->ptr[i]->key);
117 		if (kvb->ptr[i]->value) buffer_free(kvb->ptr[i]->value);
118 		free(kvb->ptr[i]);
119 	}
120 
121 	if (kvb->ptr) free(kvb->ptr);
122 
123 	free(kvb);
124 }
125 
126 
INIT_FUNC(mod_rewrite_init)127 INIT_FUNC(mod_rewrite_init) {
128 	plugin_data *p;
129 
130 	p = calloc(1, sizeof(*p));
131 
132 	p->match_buf = buffer_init();
133 
134 	return p;
135 }
136 
FREE_FUNC(mod_rewrite_free)137 FREE_FUNC(mod_rewrite_free) {
138 	plugin_data *p = p_d;
139 
140 	UNUSED(srv);
141 
142 	if (!p) return HANDLER_GO_ON;
143 
144 	buffer_free(p->match_buf);
145 	if (p->config_storage) {
146 		size_t i;
147 		for (i = 0; i < srv->config_context->used; i++) {
148 			plugin_config *s = p->config_storage[i];
149 			rewrite_rule_buffer_free(s->rewrite);
150 			rewrite_rule_buffer_free(s->rewrite_NF);
151 
152 			free(s);
153 		}
154 		free(p->config_storage);
155 	}
156 
157 	free(p);
158 
159 	return HANDLER_GO_ON;
160 }
161 
parse_config_entry(server * srv,array * ca,rewrite_rule_buffer * kvb,const char * option,int once)162 static int parse_config_entry(server *srv, array *ca, rewrite_rule_buffer *kvb, const char *option, int once) {
163 	data_unset *du;
164 
165 	if (NULL != (du = array_get_element(ca, option))) {
166 		data_array *da;
167 		size_t j;
168 
169 		if (du->type != TYPE_ARRAY) {
170 			log_error_write(srv, __FILE__, __LINE__, "sss",
171 					"unexpected type for key: ", option, "array of strings");
172 
173 			return HANDLER_ERROR;
174 		}
175 
176 		da = (data_array *)du;
177 
178 		for (j = 0; j < da->value->used; j++) {
179 			if (da->value->data[j]->type != TYPE_STRING) {
180 				log_error_write(srv, __FILE__, __LINE__, "sssbs",
181 						"unexpected type for key: ",
182 						option,
183 						"[", da->value->data[j]->key, "](string)");
184 
185 				return HANDLER_ERROR;
186 			}
187 
188 			if (0 != rewrite_rule_buffer_append(kvb,
189 							    ((data_string *)(da->value->data[j]))->key,
190 							    ((data_string *)(da->value->data[j]))->value,
191 							    once)) {
192 				log_error_write(srv, __FILE__, __LINE__, "sb",
193 						"pcre-compile failed for", da->value->data[j]->key);
194 			}
195 		}
196 	}
197 
198 	return 0;
199 }
200 #else
parse_config_entry(server * srv,array * ca,const char * option)201 static int parse_config_entry(server *srv, array *ca, const char *option) {
202 	static int logged_message = 0;
203 	if (logged_message) return 0;
204 	if (NULL != array_get_element(ca, option)) {
205 		logged_message = 1;
206 		log_error_write(srv, __FILE__, __LINE__, "s",
207 			"pcre support is missing, please install libpcre and the headers");
208 	}
209 	return 0;
210 }
211 #endif
212 
SETDEFAULTS_FUNC(mod_rewrite_set_defaults)213 SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
214 	size_t i = 0;
215 	config_values_t cv[] = {
216 		{ "url.rewrite-repeat",        NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
217 		{ "url.rewrite-once",          NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
218 
219 		/* these functions only rewrite if the target is not already in the filestore
220 		 *
221 		 * url.rewrite-repeat-if-not-file is the equivalent of url.rewrite-repeat
222 		 * url.rewrite-if-not-file is the equivalent of url.rewrite-once
223 		 *
224 		 */
225 		{ "url.rewrite-repeat-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
226 		{ "url.rewrite-if-not-file",        NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
227 
228 		/* old names, still supported
229 		 *
230 		 * url.rewrite remapped to url.rewrite-once
231 		 * url.rewrite-final    is url.rewrite-once
232 		 *
233 		 */
234 		{ "url.rewrite",               NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
235 		{ "url.rewrite-final",         NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
236 		{ NULL,                        NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
237 	};
238 
239 #ifdef HAVE_PCRE_H
240 	plugin_data *p = p_d;
241 
242 	if (!p) return HANDLER_ERROR;
243 
244 	/* 0 */
245 	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
246 #else
247 	UNUSED(p_d);
248 #endif
249 
250 	for (i = 0; i < srv->config_context->used; i++) {
251 		array *ca;
252 #ifdef HAVE_PCRE_H
253 		plugin_config *s;
254 
255 		s = calloc(1, sizeof(plugin_config));
256 		s->rewrite = rewrite_rule_buffer_init();
257 		s->rewrite_NF = rewrite_rule_buffer_init();
258 		p->config_storage[i] = s;
259 #endif
260 
261 		ca = ((data_config *)srv->config_context->data[i])->value;
262 
263 		if (0 != config_insert_values_global(srv, ca, cv)) {
264 			return HANDLER_ERROR;
265 		}
266 
267 #ifndef HAVE_PCRE_H
268 # define parse_config_entry(srv, ca, x, option, y) parse_config_entry(srv, ca, option)
269 #endif
270 		parse_config_entry(srv, ca, s->rewrite, "url.rewrite-once",      1);
271 		parse_config_entry(srv, ca, s->rewrite, "url.rewrite-final",     1);
272 		parse_config_entry(srv, ca, s->rewrite_NF, "url.rewrite-if-not-file",   1);
273 		parse_config_entry(srv, ca, s->rewrite_NF, "url.rewrite-repeat-if-not-file", 0);
274 		parse_config_entry(srv, ca, s->rewrite, "url.rewrite",           1);
275 		parse_config_entry(srv, ca, s->rewrite, "url.rewrite-repeat",    0);
276 	}
277 
278 	return HANDLER_GO_ON;
279 }
280 
281 #ifdef HAVE_PCRE_H
282 
283 #define PATCH(x) \
284 	p->conf.x = s->x;
mod_rewrite_patch_connection(server * srv,connection * con,plugin_data * p)285 static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p) {
286 	size_t i, j;
287 	plugin_config *s = p->config_storage[0];
288 
289 	PATCH(rewrite);
290 	PATCH(rewrite_NF);
291 	p->conf.context = NULL;
292 	p->conf.context_NF = NULL;
293 
294 	/* skip the first, the global context */
295 	for (i = 1; i < srv->config_context->used; i++) {
296 		data_config *dc = (data_config *)srv->config_context->data[i];
297 		s = p->config_storage[i];
298 
299 		if (COMP_HTTP_URL == dc->comp) continue;
300 
301 		/* condition didn't match */
302 		if (!config_check_cond(srv, con, dc)) continue;
303 
304 		/* merge config */
305 		for (j = 0; j < dc->value->used; j++) {
306 			data_unset *du = dc->value->data[j];
307 
308 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) {
309 				PATCH(rewrite);
310 				p->conf.context = dc;
311 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-once"))) {
312 				PATCH(rewrite);
313 				p->conf.context = dc;
314 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat"))) {
315 				PATCH(rewrite);
316 				p->conf.context = dc;
317 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-if-not-file"))) {
318 				PATCH(rewrite_NF);
319 				p->conf.context_NF = dc;
320 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat-if-not-file"))) {
321 				PATCH(rewrite_NF);
322 				p->conf.context_NF = dc;
323 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
324 				PATCH(rewrite);
325 				p->conf.context = dc;
326 			}
327 		}
328 	}
329 
330 	return 0;
331 }
332 
URIHANDLER_FUNC(mod_rewrite_con_reset)333 URIHANDLER_FUNC(mod_rewrite_con_reset) {
334 	plugin_data *p = p_d;
335 
336 	UNUSED(srv);
337 
338 	if (con->plugin_ctx[p->id]) {
339 		handler_ctx_free(con->plugin_ctx[p->id]);
340 		con->plugin_ctx[p->id] = NULL;
341 	}
342 
343 	return HANDLER_GO_ON;
344 }
345 
process_rewrite_rules(server * srv,connection * con,plugin_data * p,rewrite_rule_buffer * kvb)346 static int process_rewrite_rules(server *srv, connection *con, plugin_data *p, rewrite_rule_buffer *kvb) {
347 	size_t i;
348 	handler_ctx *hctx;
349 
350 	if (con->plugin_ctx[p->id]) {
351 		hctx = con->plugin_ctx[p->id];
352 
353 		if (hctx->loops++ > 100) {
354 			log_error_write(srv, __FILE__, __LINE__,  "s",
355 					"ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request, perhaps you want to use url.rewrite-once instead of url.rewrite-repeat");
356 
357 			return HANDLER_ERROR;
358 		}
359 
360 		if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
361 	}
362 
363 	buffer_copy_string_buffer(p->match_buf, con->request.uri);
364 
365 	for (i = 0; i < kvb->used; i++) {
366 		pcre *match;
367 		const char *pattern;
368 		size_t pattern_len;
369 		int n;
370 		rewrite_rule *rule = kvb->ptr[i];
371 # define N 10
372 		int ovec[N * 3];
373 
374 		match       = rule->key;
375 		pattern     = rule->value->ptr;
376 		pattern_len = rule->value->used - 1;
377 
378 		if ((n = pcre_exec(match, NULL, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) {
379 			if (n != PCRE_ERROR_NOMATCH) {
380 				log_error_write(srv, __FILE__, __LINE__, "sd",
381 						"execution error while matching: ", n);
382 				return HANDLER_ERROR;
383 			}
384 		} else {
385 			const char **list;
386 			size_t start;
387 			size_t k;
388 
389 			/* it matched */
390 			pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
391 
392 			/* search for $[0-9] */
393 
394 			buffer_reset(con->request.uri);
395 
396 			start = 0;
397 			for (k = 0; k+1 < pattern_len; k++) {
398 				if (pattern[k] == '$' || pattern[k] == '%') {
399 					/* got one */
400 
401 					size_t num = pattern[k + 1] - '0';
402 
403 					buffer_append_string_len(con->request.uri, pattern + start, k - start);
404 
405 					if (!isdigit((unsigned char)pattern[k + 1])) {
406 						/* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */
407 						buffer_append_string_len(con->request.uri, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2);
408 					} else if (pattern[k] == '$') {
409 						/* n is always > 0 */
410 						if (num < (size_t)n) {
411 							buffer_append_string(con->request.uri, list[num]);
412 						}
413 					} else if (p->conf.context == NULL) {
414 						/* we have no context, we are global */
415 						log_error_write(srv, __FILE__, __LINE__, "sb",
416 								"used a redirect containing a %[0-9]+ in the global scope, ignored:",
417 								rule->value);
418 
419 					} else {
420 						config_append_cond_match_buffer(con, p->conf.context, con->request.uri, num);
421 					}
422 
423 					k++;
424 					start = k + 1;
425 				}
426 			}
427 
428 			buffer_append_string_len(con->request.uri, pattern + start, pattern_len - start);
429 
430 			pcre_free(list);
431 
432 			if (con->plugin_ctx[p->id] == NULL) {
433 				hctx = handler_ctx_init();
434 				con->plugin_ctx[p->id] = hctx;
435 			} else {
436 				hctx = con->plugin_ctx[p->id];
437 			}
438 
439 			if (rule->once) hctx->state = REWRITE_STATE_FINISHED;
440 
441 			return HANDLER_COMEBACK;
442 		}
443 #undef N
444 	}
445 
446 	return HANDLER_GO_ON;
447 }
448 
URIHANDLER_FUNC(mod_rewrite_physical)449 URIHANDLER_FUNC(mod_rewrite_physical) {
450 	plugin_data *p = p_d;
451 	handler_t r;
452 	stat_cache_entry *sce;
453 
454 	if (con->mode != DIRECT) return HANDLER_GO_ON;
455 
456 	mod_rewrite_patch_connection(srv, con, p);
457 	p->conf.context = p->conf.context_NF;
458 
459 	if (!p->conf.rewrite_NF) return HANDLER_GO_ON;
460 
461 	/* skip if physical.path is a regular file */
462 	sce = NULL;
463 	if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
464 		if (S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;
465 	}
466 
467 	switch(r = process_rewrite_rules(srv, con, p, p->conf.rewrite_NF)) {
468 	case HANDLER_COMEBACK:
469 		buffer_reset(con->physical.path);
470 	default:
471 		return r;
472 	}
473 
474 	return HANDLER_GO_ON;
475 }
476 
URIHANDLER_FUNC(mod_rewrite_uri_handler)477 URIHANDLER_FUNC(mod_rewrite_uri_handler) {
478 	plugin_data *p = p_d;
479 
480 	mod_rewrite_patch_connection(srv, con, p);
481 
482 	if (!p->conf.rewrite) return HANDLER_GO_ON;
483 
484 	return process_rewrite_rules(srv, con, p, p->conf.rewrite);
485 
486 	return HANDLER_GO_ON;
487 }
488 #endif
489 
490 int mod_rewrite_plugin_init(plugin *p);
mod_rewrite_plugin_init(plugin * p)491 int mod_rewrite_plugin_init(plugin *p) {
492 	p->version     = LIGHTTPD_VERSION_ID;
493 	p->name        = buffer_init_string("rewrite");
494 
495 #ifdef HAVE_PCRE_H
496 	p->init        = mod_rewrite_init;
497 	/* it has to stay _raw as we are matching on uri + querystring
498 	 */
499 
500 	p->handle_uri_raw = mod_rewrite_uri_handler;
501 	p->handle_physical = mod_rewrite_physical;
502 	p->cleanup     = mod_rewrite_free;
503 	p->connection_reset = mod_rewrite_con_reset;
504 #endif
505 	p->set_defaults = mod_rewrite_set_defaults;
506 
507 	p->data        = NULL;
508 
509 	return 0;
510 }
511