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