Annotation of embedaddon/lighttpd/src/mod_trigger_b4_dl.c, revision 1.1.1.1
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:
146: p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
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: }
184: #ifdef FD_CLOEXEC
185: fcntl(gdbm_fdesc(s->db), F_SETFD, FD_CLOEXEC);
186: #endif
187: }
188: #endif
189: #if defined(HAVE_PCRE_H)
190: if (!buffer_is_empty(s->download_url)) {
191: if (NULL == (s->download_regex = pcre_compile(s->download_url->ptr,
192: 0, &errptr, &erroff, NULL))) {
193:
194: log_error_write(srv, __FILE__, __LINE__, "sbss",
195: "compiling regex for download-url failed:",
196: s->download_url, "pos:", erroff);
197: return HANDLER_ERROR;
198: }
199: }
200:
201: if (!buffer_is_empty(s->trigger_url)) {
202: if (NULL == (s->trigger_regex = pcre_compile(s->trigger_url->ptr,
203: 0, &errptr, &erroff, NULL))) {
204:
205: log_error_write(srv, __FILE__, __LINE__, "sbss",
206: "compiling regex for trigger-url failed:",
207: s->trigger_url, "pos:", erroff);
208:
209: return HANDLER_ERROR;
210: }
211: }
212: #endif
213:
214: if (s->mc_hosts->used) {
215: #if defined(HAVE_MEMCACHE_H)
216: size_t k;
217: s->mc = mc_new();
218:
219: for (k = 0; k < s->mc_hosts->used; k++) {
220: data_string *ds = (data_string *)s->mc_hosts->data[k];
221:
222: if (0 != mc_server_add4(s->mc, ds->value->ptr)) {
223: log_error_write(srv, __FILE__, __LINE__, "sb",
224: "connection to host failed:",
225: ds->value);
226:
227: return HANDLER_ERROR;
228: }
229: }
230: #else
231: log_error_write(srv, __FILE__, __LINE__, "s",
232: "memcache support is not compiled in but trigger-before-download.memcache-hosts is set, aborting");
233: return HANDLER_ERROR;
234: #endif
235: }
236:
237:
238: #if (!defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H)) || !defined(HAVE_PCRE_H)
239: log_error_write(srv, __FILE__, __LINE__, "s",
240: "(either gdbm or libmemcache) and pcre are require, but were not found, aborting");
241: return HANDLER_ERROR;
242: #endif
243: }
244:
245: return HANDLER_GO_ON;
246: }
247:
248: #define PATCH(x) \
249: p->conf.x = s->x;
250: static int mod_trigger_b4_dl_patch_connection(server *srv, connection *con, plugin_data *p) {
251: size_t i, j;
252: plugin_config *s = p->config_storage[0];
253:
254: #if defined(HAVE_GDBM)
255: PATCH(db);
256: #endif
257: #if defined(HAVE_PCRE_H)
258: PATCH(download_regex);
259: PATCH(trigger_regex);
260: #endif
261: PATCH(trigger_timeout);
262: PATCH(deny_url);
263: PATCH(mc_namespace);
264: PATCH(debug);
265: #if defined(HAVE_MEMCACHE_H)
266: PATCH(mc);
267: #endif
268:
269: /* skip the first, the global context */
270: for (i = 1; i < srv->config_context->used; i++) {
271: data_config *dc = (data_config *)srv->config_context->data[i];
272: s = p->config_storage[i];
273:
274: /* condition didn't match */
275: if (!config_check_cond(srv, con, dc)) continue;
276:
277: /* merge config */
278: for (j = 0; j < dc->value->used; j++) {
279: data_unset *du = dc->value->data[j];
280:
281: if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.download-url"))) {
282: #if defined(HAVE_PCRE_H)
283: PATCH(download_regex);
284: #endif
285: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-url"))) {
286: # if defined(HAVE_PCRE_H)
287: PATCH(trigger_regex);
288: # endif
289: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.gdbm-filename"))) {
290: #if defined(HAVE_GDBM_H)
291: PATCH(db);
292: #endif
293: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-timeout"))) {
294: PATCH(trigger_timeout);
295: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.debug"))) {
296: PATCH(debug);
297: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.deny-url"))) {
298: PATCH(deny_url);
299: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-namespace"))) {
300: PATCH(mc_namespace);
301: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-hosts"))) {
302: #if defined(HAVE_MEMCACHE_H)
303: PATCH(mc);
304: #endif
305: }
306: }
307: }
308:
309: return 0;
310: }
311: #undef PATCH
312:
313: URIHANDLER_FUNC(mod_trigger_b4_dl_uri_handler) {
314: plugin_data *p = p_d;
315: const char *remote_ip;
316: data_string *ds;
317:
318: #if defined(HAVE_PCRE_H)
319: int n;
320: # define N 10
321: int ovec[N * 3];
322:
323: if (con->mode != DIRECT) return HANDLER_GO_ON;
324:
325: if (con->uri.path->used == 0) return HANDLER_GO_ON;
326:
327: mod_trigger_b4_dl_patch_connection(srv, con, p);
328:
329: if (!p->conf.trigger_regex || !p->conf.download_regex) return HANDLER_GO_ON;
330:
331: # if !defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H)
332: return HANDLER_GO_ON;
333: # elif defined(HAVE_GDBM_H) && defined(HAVE_MEMCACHE_H)
334: if (!p->conf.db && !p->conf.mc) return HANDLER_GO_ON;
335: if (p->conf.db && p->conf.mc) {
336: /* can't decide which one */
337:
338: return HANDLER_GO_ON;
339: }
340: # elif defined(HAVE_GDBM_H)
341: if (!p->conf.db) return HANDLER_GO_ON;
342: # else
343: if (!p->conf.mc) return HANDLER_GO_ON;
344: # endif
345:
346: if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "X-Forwarded-For"))) {
347: /* X-Forwarded-For contains the ip behind the proxy */
348:
349: remote_ip = ds->value->ptr;
350:
351: /* memcache can't handle spaces */
352: } else {
353: remote_ip = inet_ntop_cache_get_ip(srv, &(con->dst_addr));
354: }
355:
356: if (p->conf.debug) {
357: log_error_write(srv, __FILE__, __LINE__, "ss", "(debug) remote-ip:", remote_ip);
358: }
359:
360: /* check if URL is a trigger -> insert IP into DB */
361: if ((n = pcre_exec(p->conf.trigger_regex, NULL, con->uri.path->ptr, con->uri.path->used - 1, 0, 0, ovec, 3 * N)) < 0) {
362: if (n != PCRE_ERROR_NOMATCH) {
363: log_error_write(srv, __FILE__, __LINE__, "sd",
364: "execution error while matching:", n);
365:
366: return HANDLER_ERROR;
367: }
368: } else {
369: # if defined(HAVE_GDBM_H)
370: if (p->conf.db) {
371: /* the trigger matched */
372: datum key, val;
373:
374: key.dptr = (char *)remote_ip;
375: key.dsize = strlen(remote_ip);
376:
377: val.dptr = (char *)&(srv->cur_ts);
378: val.dsize = sizeof(srv->cur_ts);
379:
380: if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) {
381: log_error_write(srv, __FILE__, __LINE__, "s",
382: "insert failed");
383: }
384: }
385: # endif
386: # if defined(HAVE_MEMCACHE_H)
387: if (p->conf.mc) {
388: size_t i;
389: buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace);
390: buffer_append_string(p->tmp_buf, remote_ip);
391:
392: for (i = 0; i < p->tmp_buf->used - 1; i++) {
393: if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-';
394: }
395:
396: if (p->conf.debug) {
397: log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) triggered IP:", p->tmp_buf);
398: }
399:
400: if (0 != mc_set(p->conf.mc,
401: CONST_BUF_LEN(p->tmp_buf),
402: (char *)&(srv->cur_ts), sizeof(srv->cur_ts),
403: p->conf.trigger_timeout, 0)) {
404: log_error_write(srv, __FILE__, __LINE__, "s",
405: "insert failed");
406: }
407: }
408: # endif
409: }
410:
411: /* check if URL is a download -> check IP in DB, update timestamp */
412: if ((n = pcre_exec(p->conf.download_regex, NULL, con->uri.path->ptr, con->uri.path->used - 1, 0, 0, ovec, 3 * N)) < 0) {
413: if (n != PCRE_ERROR_NOMATCH) {
414: log_error_write(srv, __FILE__, __LINE__, "sd",
415: "execution error while matching: ", n);
416: return HANDLER_ERROR;
417: }
418: } else {
419: /* the download uri matched */
420: # if defined(HAVE_GDBM_H)
421: if (p->conf.db) {
422: datum key, val;
423: time_t last_hit;
424:
425: key.dptr = (char *)remote_ip;
426: key.dsize = strlen(remote_ip);
427:
428: val = gdbm_fetch(p->conf.db, key);
429:
430: if (val.dptr == NULL) {
431: /* not found, redirect */
432:
433: response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
434: con->http_status = 307;
435: con->file_finished = 1;
436:
437: return HANDLER_FINISHED;
438: }
439:
440: memcpy(&last_hit, val.dptr, sizeof(time_t));
441:
442: free(val.dptr);
443:
444: if (srv->cur_ts - last_hit > p->conf.trigger_timeout) {
445: /* found, but timeout, redirect */
446:
447: response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
448: con->http_status = 307;
449: con->file_finished = 1;
450:
451: if (p->conf.db) {
452: if (0 != gdbm_delete(p->conf.db, key)) {
453: log_error_write(srv, __FILE__, __LINE__, "s",
454: "delete failed");
455: }
456: }
457:
458: return HANDLER_FINISHED;
459: }
460:
461: val.dptr = (char *)&(srv->cur_ts);
462: val.dsize = sizeof(srv->cur_ts);
463:
464: if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) {
465: log_error_write(srv, __FILE__, __LINE__, "s",
466: "insert failed");
467: }
468: }
469: # endif
470:
471: # if defined(HAVE_MEMCACHE_H)
472: if (p->conf.mc) {
473: void *r;
474: size_t i;
475:
476: buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace);
477: buffer_append_string(p->tmp_buf, remote_ip);
478:
479: for (i = 0; i < p->tmp_buf->used - 1; i++) {
480: if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-';
481: }
482:
483: if (p->conf.debug) {
484: log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) checking IP:", p->tmp_buf);
485: }
486:
487: /**
488: *
489: * memcached is do expiration for us, as long as we can fetch it every thing is ok
490: * and the timestamp is updated
491: *
492: */
493: if (NULL == (r = mc_aget(p->conf.mc,
494: CONST_BUF_LEN(p->tmp_buf)
495: ))) {
496:
497: response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url));
498:
499: con->http_status = 307;
500: con->file_finished = 1;
501:
502: return HANDLER_FINISHED;
503: }
504:
505: free(r);
506:
507: /* set a new timeout */
508: if (0 != mc_set(p->conf.mc,
509: CONST_BUF_LEN(p->tmp_buf),
510: (char *)&(srv->cur_ts), sizeof(srv->cur_ts),
511: p->conf.trigger_timeout, 0)) {
512: log_error_write(srv, __FILE__, __LINE__, "s",
513: "insert failed");
514: }
515: }
516: # endif
517: }
518:
519: #else
520: UNUSED(srv);
521: UNUSED(con);
522: UNUSED(p_d);
523: #endif
524:
525: return HANDLER_GO_ON;
526: }
527:
528: #if defined(HAVE_GDBM_H)
529: TRIGGER_FUNC(mod_trigger_b4_dl_handle_trigger) {
530: plugin_data *p = p_d;
531: size_t i;
532:
533: /* check DB each minute */
534: if (srv->cur_ts % 60 != 0) return HANDLER_GO_ON;
535:
536: /* cleanup */
537: for (i = 0; i < srv->config_context->used; i++) {
538: plugin_config *s = p->config_storage[i];
539: datum key, val, okey;
540:
541: if (!s->db) continue;
542:
543: okey.dptr = NULL;
544:
545: /* according to the manual this loop + delete does delete all entries on its way
546: *
547: * we don't care as the next round will remove them. We don't have to perfect here.
548: */
549: for (key = gdbm_firstkey(s->db); key.dptr; key = gdbm_nextkey(s->db, okey)) {
550: time_t last_hit;
551: if (okey.dptr) {
552: free(okey.dptr);
553: okey.dptr = NULL;
554: }
555:
556: val = gdbm_fetch(s->db, key);
557:
558: memcpy(&last_hit, val.dptr, sizeof(time_t));
559:
560: free(val.dptr);
561:
562: if (srv->cur_ts - last_hit > s->trigger_timeout) {
563: gdbm_delete(s->db, key);
564: }
565:
566: okey = key;
567: }
568: if (okey.dptr) free(okey.dptr);
569:
570: /* reorg once a day */
571: if ((srv->cur_ts % (60 * 60 * 24) != 0)) gdbm_reorganize(s->db);
572: }
573: return HANDLER_GO_ON;
574: }
575: #endif
576:
577: /* this function is called at dlopen() time and inits the callbacks */
578:
579: int mod_trigger_b4_dl_plugin_init(plugin *p);
580: int mod_trigger_b4_dl_plugin_init(plugin *p) {
581: p->version = LIGHTTPD_VERSION_ID;
582: p->name = buffer_init_string("trigger_b4_dl");
583:
584: p->init = mod_trigger_b4_dl_init;
585: p->handle_uri_clean = mod_trigger_b4_dl_uri_handler;
586: p->set_defaults = mod_trigger_b4_dl_set_defaults;
587: #if defined(HAVE_GDBM_H)
588: p->handle_trigger = mod_trigger_b4_dl_handle_trigger;
589: #endif
590: p->cleanup = mod_trigger_b4_dl_free;
591:
592: p->data = NULL;
593:
594: return 0;
595: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>