Annotation of embedaddon/lighttpd/src/mod_trigger_b4_dl.c, revision 1.1.1.2
1.1 misho 1: #include "base.h"
2: #include "log.h"
3: #include "buffer.h"
4:
5: #include "plugin.h"
6: #include "response.h"
7: #include "inet_ntop_cache.h"
8:
9: #include <ctype.h>
10: #include <stdlib.h>
11: #include <fcntl.h>
12: #include <string.h>
13:
14: #if defined(HAVE_GDBM_H)
15: # include <gdbm.h>
16: #endif
17:
18: #if defined(HAVE_PCRE_H)
19: # include <pcre.h>
20: #endif
21:
22: #if defined(HAVE_MEMCACHE_H)
23: # include <memcache.h>
24: #endif
25:
26: /**
27: * this is a trigger_b4_dl for a lighttpd plugin
28: *
29: */
30:
31: /* plugin config for all request/connections */
32:
33: typedef struct {
34: buffer *db_filename;
35:
36: buffer *trigger_url;
37: buffer *download_url;
38: buffer *deny_url;
39:
40: array *mc_hosts;
41: buffer *mc_namespace;
42: #if defined(HAVE_PCRE_H)
43: pcre *trigger_regex;
44: pcre *download_regex;
45: #endif
46: #if defined(HAVE_GDBM_H)
47: GDBM_FILE db;
48: #endif
49:
50: #if defined(HAVE_MEMCACHE_H)
51: struct memcache *mc;
52: #endif
53:
54: unsigned short trigger_timeout;
55: unsigned short debug;
56: } plugin_config;
57:
58: typedef struct {
59: PLUGIN_DATA;
60:
61: buffer *tmp_buf;
62:
63: plugin_config **config_storage;
64:
65: plugin_config conf;
66: } plugin_data;
67:
68: /* init the plugin data */
69: INIT_FUNC(mod_trigger_b4_dl_init) {
70: plugin_data *p;
71:
72: p = calloc(1, sizeof(*p));
73:
74: p->tmp_buf = buffer_init();
75:
76: return p;
77: }
78:
79: /* detroy the plugin data */
80: FREE_FUNC(mod_trigger_b4_dl_free) {
81: plugin_data *p = p_d;
82:
83: UNUSED(srv);
84:
85: if (!p) return HANDLER_GO_ON;
86:
87: if (p->config_storage) {
88: size_t i;
89: for (i = 0; i < srv->config_context->used; i++) {
90: plugin_config *s = p->config_storage[i];
91:
92: if (!s) continue;
93:
94: buffer_free(s->db_filename);
95: buffer_free(s->download_url);
96: buffer_free(s->trigger_url);
97: buffer_free(s->deny_url);
98:
99: buffer_free(s->mc_namespace);
100: array_free(s->mc_hosts);
101:
102: #if defined(HAVE_PCRE_H)
103: if (s->trigger_regex) pcre_free(s->trigger_regex);
104: if (s->download_regex) pcre_free(s->download_regex);
105: #endif
106: #if defined(HAVE_GDBM_H)
107: if (s->db) gdbm_close(s->db);
108: #endif
109: #if defined(HAVE_MEMCACHE_H)
110: if (s->mc) mc_free(s->mc);
111: #endif
112:
113: free(s);
114: }
115: free(p->config_storage);
116: }
117:
118: buffer_free(p->tmp_buf);
119:
120: free(p);
121:
122: return HANDLER_GO_ON;
123: }
124:
125: /* handle plugin config and check values */
126:
127: SETDEFAULTS_FUNC(mod_trigger_b4_dl_set_defaults) {
128: plugin_data *p = p_d;
129: size_t i = 0;
130:
131:
132: config_values_t cv[] = {
133: { "trigger-before-download.gdbm-filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
134: { "trigger-before-download.trigger-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
135: { "trigger-before-download.download-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
136: { "trigger-before-download.deny-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
137: { "trigger-before-download.trigger-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
138: { "trigger-before-download.memcache-hosts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
139: { "trigger-before-download.memcache-namespace", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
140: { "trigger-before-download.debug", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
141: { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
142: };
143:
144: if (!p) return HANDLER_ERROR;
145:
1.1.1.2 ! misho 146: p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
1.1 misho 147:
148: for (i = 0; i < srv->config_context->used; i++) {
149: plugin_config *s;
150: #if defined(HAVE_PCRE_H)
151: const char *errptr;
152: int erroff;
153: #endif
154:
155: s = calloc(1, sizeof(plugin_config));
156: s->db_filename = buffer_init();
157: s->download_url = buffer_init();
158: s->trigger_url = buffer_init();
159: s->deny_url = buffer_init();
160: s->mc_hosts = array_init();
161: s->mc_namespace = buffer_init();
162:
163: cv[0].destination = s->db_filename;
164: cv[1].destination = s->trigger_url;
165: cv[2].destination = s->download_url;
166: cv[3].destination = s->deny_url;
167: cv[4].destination = &(s->trigger_timeout);
168: cv[5].destination = s->mc_hosts;
169: cv[6].destination = s->mc_namespace;
170: cv[7].destination = &(s->debug);
171:
172: p->config_storage[i] = s;
173:
174: if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
175: return HANDLER_ERROR;
176: }
177: #if defined(HAVE_GDBM_H)
178: if (!buffer_is_empty(s->db_filename)) {
179: if (NULL == (s->db = gdbm_open(s->db_filename->ptr, 4096, GDBM_WRCREAT | GDBM_NOLOCK, S_IRUSR | S_IWUSR, 0))) {
180: log_error_write(srv, __FILE__, __LINE__, "s",
181: "gdbm-open failed");
182: return HANDLER_ERROR;
183: }
1.1.1.2 ! misho 184: fd_close_on_exec(gdbm_fdesc(s->db));
1.1 misho 185: }
186: #endif
187: #if defined(HAVE_PCRE_H)
188: if (!buffer_is_empty(s->download_url)) {
189: if (NULL == (s->download_regex = pcre_compile(s->download_url->ptr,
190: 0, &errptr, &erroff, NULL))) {
191:
192: log_error_write(srv, __FILE__, __LINE__, "sbss",
193: "compiling regex for download-url failed:",
194: s->download_url, "pos:", erroff);
195: return HANDLER_ERROR;
196: }
197: }
198:
199: if (!buffer_is_empty(s->trigger_url)) {
200: if (NULL == (s->trigger_regex = pcre_compile(s->trigger_url->ptr,
201: 0, &errptr, &erroff, NULL))) {
202:
203: log_error_write(srv, __FILE__, __LINE__, "sbss",
204: "compiling regex for trigger-url failed:",
205: s->trigger_url, "pos:", erroff);
206:
207: return HANDLER_ERROR;
208: }
209: }
210: #endif
211:
212: if (s->mc_hosts->used) {
213: #if defined(HAVE_MEMCACHE_H)
214: size_t k;
215: s->mc = mc_new();
216:
217: for (k = 0; k < s->mc_hosts->used; k++) {
218: data_string *ds = (data_string *)s->mc_hosts->data[k];
219:
220: if (0 != mc_server_add4(s->mc, ds->value->ptr)) {
221: log_error_write(srv, __FILE__, __LINE__, "sb",
222: "connection to host failed:",
223: ds->value);
224:
225: return HANDLER_ERROR;
226: }
227: }
228: #else
229: log_error_write(srv, __FILE__, __LINE__, "s",
230: "memcache support is not compiled in but trigger-before-download.memcache-hosts is set, aborting");
231: return HANDLER_ERROR;
232: #endif
233: }
234:
235:
236: #if (!defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H)) || !defined(HAVE_PCRE_H)
237: log_error_write(srv, __FILE__, __LINE__, "s",
238: "(either gdbm or libmemcache) and pcre are require, but were not found, aborting");
239: return HANDLER_ERROR;
240: #endif
241: }
242:
243: return HANDLER_GO_ON;
244: }
245:
246: #define PATCH(x) \
247: p->conf.x = s->x;
248: static int mod_trigger_b4_dl_patch_connection(server *srv, connection *con, plugin_data *p) {
249: size_t i, j;
250: plugin_config *s = p->config_storage[0];
251:
252: #if defined(HAVE_GDBM)
253: PATCH(db);
254: #endif
255: #if defined(HAVE_PCRE_H)
256: PATCH(download_regex);
257: PATCH(trigger_regex);
258: #endif
259: PATCH(trigger_timeout);
260: PATCH(deny_url);
261: PATCH(mc_namespace);
262: PATCH(debug);
263: #if defined(HAVE_MEMCACHE_H)
264: PATCH(mc);
265: #endif
266:
267: /* skip the first, the global context */
268: for (i = 1; i < srv->config_context->used; i++) {
269: data_config *dc = (data_config *)srv->config_context->data[i];
270: s = p->config_storage[i];
271:
272: /* condition didn't match */
273: if (!config_check_cond(srv, con, dc)) continue;
274:
275: /* merge config */
276: for (j = 0; j < dc->value->used; j++) {
277: data_unset *du = dc->value->data[j];
278:
279: if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.download-url"))) {
280: #if defined(HAVE_PCRE_H)
281: PATCH(download_regex);
282: #endif
283: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-url"))) {
284: # if defined(HAVE_PCRE_H)
285: PATCH(trigger_regex);
286: # endif
287: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.gdbm-filename"))) {
288: #if defined(HAVE_GDBM_H)
289: PATCH(db);
290: #endif
291: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-timeout"))) {
292: PATCH(trigger_timeout);
293: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.debug"))) {
294: PATCH(debug);
295: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.deny-url"))) {
296: PATCH(deny_url);
297: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-namespace"))) {
298: PATCH(mc_namespace);
299: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-hosts"))) {
300: #if defined(HAVE_MEMCACHE_H)
301: PATCH(mc);
302: #endif
303: }
304: }
305: }
306:
307: return 0;
308: }
309: #undef PATCH
310:
311: URIHANDLER_FUNC(mod_trigger_b4_dl_uri_handler) {
312: plugin_data *p = p_d;
313: const char *remote_ip;
314: data_string *ds;
315:
316: #if defined(HAVE_PCRE_H)
317: int n;
318: # define N 10
319: int ovec[N * 3];
320:
321: if (con->mode != DIRECT) return HANDLER_GO_ON;
322:
323: if (con->uri.path->used == 0) return HANDLER_GO_ON;
324:
325: mod_trigger_b4_dl_patch_connection(srv, con, p);
326:
327: if (!p->conf.trigger_regex || !p->conf.download_regex) return HANDLER_GO_ON;
328:
329: # if !defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H)
330: return HANDLER_GO_ON;
331: # elif defined(HAVE_GDBM_H) && defined(HAVE_MEMCACHE_H)
332: if (!p->conf.db && !p->conf.mc) return HANDLER_GO_ON;
333: if (p->conf.db && p->conf.mc) {
334: /* can't decide which one */
335:
336: return HANDLER_GO_ON;
337: }
338: # elif defined(HAVE_GDBM_H)
339: if (!p->conf.db) return HANDLER_GO_ON;
340: # else
341: if (!p->conf.mc) return HANDLER_GO_ON;
342: # endif
343:
344: if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "X-Forwarded-For"))) {
345: /* X-Forwarded-For contains the ip behind the proxy */
346:
347: remote_ip = ds->value->ptr;
348:
349: /* memcache can't handle spaces */
350: } else {
351: remote_ip = inet_ntop_cache_get_ip(srv, &(con->dst_addr));
352: }
353:
354: if (p->conf.debug) {
355: log_error_write(srv, __FILE__, __LINE__, "ss", "(debug) remote-ip:", remote_ip);
356: }
357:
358: /* check if URL is a trigger -> insert IP into DB */
359: if ((n = pcre_exec(p->conf.trigger_regex, NULL, con->uri.path->ptr, con->uri.path->used - 1, 0, 0, ovec, 3 * N)) < 0) {
360: if (n != PCRE_ERROR_NOMATCH) {
361: log_error_write(srv, __FILE__, __LINE__, "sd",
362: "execution error while matching:", n);
363:
364: return HANDLER_ERROR;
365: }
366: } else {
367: # if defined(HAVE_GDBM_H)
368: if (p->conf.db) {
369: /* the trigger matched */
370: datum key, val;
371:
372: key.dptr = (char *)remote_ip;
373: key.dsize = strlen(remote_ip);
374:
375: val.dptr = (char *)&(srv->cur_ts);
376: val.dsize = sizeof(srv->cur_ts);
377:
378: if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) {
379: log_error_write(srv, __FILE__, __LINE__, "s",
380: "insert failed");
381: }
382: }
383: # endif
384: # if defined(HAVE_MEMCACHE_H)
385: if (p->conf.mc) {
386: size_t i;
387: buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace);
388: buffer_append_string(p->tmp_buf, remote_ip);
389:
390: for (i = 0; i < p->tmp_buf->used - 1; i++) {
391: if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-';
392: }
393:
394: if (p->conf.debug) {
395: log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) triggered IP:", p->tmp_buf);
396: }
397:
398: if (0 != mc_set(p->conf.mc,
399: CONST_BUF_LEN(p->tmp_buf),
400: (char *)&(srv->cur_ts), sizeof(srv->cur_ts),
401: p->conf.trigger_timeout, 0)) {
402: log_error_write(srv, __FILE__, __LINE__, "s",
403: "insert failed");
404: }
405: }
406: # endif
407: }
408:
409: /* check if URL is a download -> check IP in DB, update timestamp */
410: if ((n = pcre_exec(p->conf.download_regex, NULL, con->uri.path->ptr, con->uri.path->used - 1, 0, 0, ovec, 3 * N)) < 0) {
411: if (n != PCRE_ERROR_NOMATCH) {
412: log_error_write(srv, __FILE__, __LINE__, "sd",
413: "execution error while matching: ", n);
414: return HANDLER_ERROR;
415: }
416: } else {
417: /* the download uri matched */
418: # if defined(HAVE_GDBM_H)
419: if (p->conf.db) {
420: datum key, val;
421: time_t last_hit;
422:
423: key.dptr = (char *)remote_ip;
424: key.dsize = strlen(remote_ip);
425:
426: val = gdbm_fetch(p->conf.db, key);
427:
428: if (val.dptr == NULL) {
429: /* not found, redirect */
430:
431: response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
432: con->http_status = 307;
433: con->file_finished = 1;
434:
435: return HANDLER_FINISHED;
436: }
437:
438: memcpy(&last_hit, val.dptr, sizeof(time_t));
439:
440: free(val.dptr);
441:
442: if (srv->cur_ts - last_hit > p->conf.trigger_timeout) {
443: /* found, but timeout, redirect */
444:
445: response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
446: con->http_status = 307;
447: con->file_finished = 1;
448:
449: if (p->conf.db) {
450: if (0 != gdbm_delete(p->conf.db, key)) {
451: log_error_write(srv, __FILE__, __LINE__, "s",
452: "delete failed");
453: }
454: }
455:
456: return HANDLER_FINISHED;
457: }
458:
459: val.dptr = (char *)&(srv->cur_ts);
460: val.dsize = sizeof(srv->cur_ts);
461:
462: if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) {
463: log_error_write(srv, __FILE__, __LINE__, "s",
464: "insert failed");
465: }
466: }
467: # endif
468:
469: # if defined(HAVE_MEMCACHE_H)
470: if (p->conf.mc) {
471: void *r;
472: size_t i;
473:
474: buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace);
475: buffer_append_string(p->tmp_buf, remote_ip);
476:
477: for (i = 0; i < p->tmp_buf->used - 1; i++) {
478: if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-';
479: }
480:
481: if (p->conf.debug) {
482: log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) checking IP:", p->tmp_buf);
483: }
484:
485: /**
486: *
487: * memcached is do expiration for us, as long as we can fetch it every thing is ok
488: * and the timestamp is updated
489: *
490: */
491: if (NULL == (r = mc_aget(p->conf.mc,
492: CONST_BUF_LEN(p->tmp_buf)
493: ))) {
494:
495: response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
496:
497: con->http_status = 307;
498: con->file_finished = 1;
499:
500: return HANDLER_FINISHED;
501: }
502:
503: free(r);
504:
505: /* set a new timeout */
506: if (0 != mc_set(p->conf.mc,
507: CONST_BUF_LEN(p->tmp_buf),
508: (char *)&(srv->cur_ts), sizeof(srv->cur_ts),
509: p->conf.trigger_timeout, 0)) {
510: log_error_write(srv, __FILE__, __LINE__, "s",
511: "insert failed");
512: }
513: }
514: # endif
515: }
516:
517: #else
518: UNUSED(srv);
519: UNUSED(con);
520: UNUSED(p_d);
521: #endif
522:
523: return HANDLER_GO_ON;
524: }
525:
526: #if defined(HAVE_GDBM_H)
527: TRIGGER_FUNC(mod_trigger_b4_dl_handle_trigger) {
528: plugin_data *p = p_d;
529: size_t i;
530:
531: /* check DB each minute */
532: if (srv->cur_ts % 60 != 0) return HANDLER_GO_ON;
533:
534: /* cleanup */
535: for (i = 0; i < srv->config_context->used; i++) {
536: plugin_config *s = p->config_storage[i];
537: datum key, val, okey;
538:
539: if (!s->db) continue;
540:
541: okey.dptr = NULL;
542:
543: /* according to the manual this loop + delete does delete all entries on its way
544: *
545: * we don't care as the next round will remove them. We don't have to perfect here.
546: */
547: for (key = gdbm_firstkey(s->db); key.dptr; key = gdbm_nextkey(s->db, okey)) {
548: time_t last_hit;
549: if (okey.dptr) {
550: free(okey.dptr);
551: okey.dptr = NULL;
552: }
553:
554: val = gdbm_fetch(s->db, key);
555:
556: memcpy(&last_hit, val.dptr, sizeof(time_t));
557:
558: free(val.dptr);
559:
560: if (srv->cur_ts - last_hit > s->trigger_timeout) {
561: gdbm_delete(s->db, key);
562: }
563:
564: okey = key;
565: }
566: if (okey.dptr) free(okey.dptr);
567:
568: /* reorg once a day */
569: if ((srv->cur_ts % (60 * 60 * 24) != 0)) gdbm_reorganize(s->db);
570: }
571: return HANDLER_GO_ON;
572: }
573: #endif
574:
575: /* this function is called at dlopen() time and inits the callbacks */
576:
577: int mod_trigger_b4_dl_plugin_init(plugin *p);
578: int mod_trigger_b4_dl_plugin_init(plugin *p) {
579: p->version = LIGHTTPD_VERSION_ID;
580: p->name = buffer_init_string("trigger_b4_dl");
581:
582: p->init = mod_trigger_b4_dl_init;
583: p->handle_uri_clean = mod_trigger_b4_dl_uri_handler;
584: p->set_defaults = mod_trigger_b4_dl_set_defaults;
585: #if defined(HAVE_GDBM_H)
586: p->handle_trigger = mod_trigger_b4_dl_handle_trigger;
587: #endif
588: p->cleanup = mod_trigger_b4_dl_free;
589:
590: p->data = NULL;
591:
592: return 0;
593: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>