9 #include "inet_ntop_cache.h"
16 #if (defined(HAVE_GDBM_H) || defined(USE_MEMCACHED)) && defined(HAVE_PCRE_H)
18 #if defined(HAVE_GDBM_H)
22 #if defined(HAVE_PCRE_H)
26 #if defined(USE_MEMCACHED)
27 # include <libmemcached/memcached.h>
31 * this is a trigger_b4_dl for a lighttpd plugin
35 /* plugin config for all request/connections */
46 #if defined(HAVE_PCRE_H)
50 #if defined(HAVE_GDBM_H)
54 #if defined(USE_MEMCACHED)
58 unsigned short trigger_timeout
;
67 plugin_config
**config_storage
;
72 /* init the plugin data */
73 INIT_FUNC(mod_trigger_b4_dl_init
) {
76 p
= calloc(1, sizeof(*p
));
78 p
->tmp_buf
= buffer_init();
83 /* detroy the plugin data */
84 FREE_FUNC(mod_trigger_b4_dl_free
) {
89 if (!p
) return HANDLER_GO_ON
;
91 if (p
->config_storage
) {
93 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
94 plugin_config
*s
= p
->config_storage
[i
];
96 if (NULL
== s
) continue;
98 buffer_free(s
->db_filename
);
99 buffer_free(s
->download_url
);
100 buffer_free(s
->trigger_url
);
101 buffer_free(s
->deny_url
);
103 buffer_free(s
->mc_namespace
);
104 array_free(s
->mc_hosts
);
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
);
110 #if defined(HAVE_GDBM_H)
111 if (s
->db
) gdbm_close(s
->db
);
113 #if defined(USE_MEMCACHED)
114 if (s
->memc
) memcached_free(s
->memc
);
119 free(p
->config_storage
);
122 buffer_free(p
->tmp_buf
);
126 return HANDLER_GO_ON
;
129 /* handle plugin config and check values */
131 SETDEFAULTS_FUNC(mod_trigger_b4_dl_set_defaults
) {
132 plugin_data
*p
= p_d
;
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
}
148 if (!p
) return HANDLER_ERROR
;
150 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
152 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
153 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
155 #if defined(HAVE_PCRE_H)
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();
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
);
177 p
->config_storage
[i
] = s
;
179 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
180 return HANDLER_ERROR
;
182 #if defined(HAVE_GDBM_H)
183 if (!buffer_string_is_empty(s
->db_filename
)) {
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",
187 return HANDLER_ERROR
;
189 fd_close_on_exec(gdbm_fdesc(s
->db
));
192 #if defined(HAVE_PCRE_H)
193 if (!buffer_string_is_empty(s
->download_url
)) {
194 if (NULL
== (s
->download_regex
= pcre_compile(s
->download_url
->ptr
,
195 0, &errptr
, &erroff
, NULL
))) {
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
;
204 if (!buffer_string_is_empty(s
->trigger_url
)) {
205 if (NULL
== (s
->trigger_regex
= pcre_compile(s
->trigger_url
->ptr
,
206 0, &errptr
, &erroff
, NULL
))) {
208 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
209 "compiling regex for trigger-url failed:",
210 s
->trigger_url
, "pos:", erroff
);
212 return HANDLER_ERROR
;
217 if (s
->mc_hosts
->used
) {
218 #if defined(USE_MEMCACHED)
219 buffer
*option_string
= buffer_init();
223 data_string
*ds
= (data_string
*)s
->mc_hosts
->data
[0];
225 buffer_append_string_len(option_string
, CONST_STR_LEN("--SERVER="));
226 buffer_append_string_buffer(option_string
, ds
->value
);
229 for (k
= 1; k
< s
->mc_hosts
->used
; k
++) {
230 data_string
*ds
= (data_string
*)s
->mc_hosts
->data
[k
];
232 buffer_append_string_len(option_string
, CONST_STR_LEN(" --SERVER="));
233 buffer_append_string_buffer(option_string
, ds
->value
);
236 s
->memc
= memcached(CONST_BUF_LEN(option_string
));
238 if (NULL
== s
->memc
) {
239 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
240 "configuring memcached failed for option string:",
243 buffer_free(option_string
);
245 if (NULL
== s
->memc
) return HANDLER_ERROR
;
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
;
254 #if (!defined(HAVE_GDBM_H) && !defined(USE_MEMCACHED)) || !defined(HAVE_PCRE_H)
255 log_error_write(srv
, __FILE__
, __LINE__
, "s",
256 "(either gdbm or libmemcache) and pcre are require, but were not found, aborting");
257 return HANDLER_ERROR
;
261 return HANDLER_GO_ON
;
266 static int mod_trigger_b4_dl_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
268 plugin_config
*s
= p
->config_storage
[0];
270 #if defined(HAVE_GDBM)
273 #if defined(HAVE_PCRE_H)
274 PATCH(download_regex
);
275 PATCH(trigger_regex
);
277 PATCH(trigger_timeout
);
281 #if defined(USE_MEMCACHED)
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
];
290 /* condition didn't match */
291 if (!config_check_cond(srv
, con
, dc
)) continue;
294 for (j
= 0; j
< dc
->value
->used
; j
++) {
295 data_unset
*du
= dc
->value
->data
[j
];
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
);
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
);
305 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.gdbm-filename"))) {
306 #if defined(HAVE_GDBM_H)
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"))) {
313 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.deny-url"))) {
315 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.memcache-namespace"))) {
317 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.memcache-hosts"))) {
318 #if defined(USE_MEMCACHED)
329 URIHANDLER_FUNC(mod_trigger_b4_dl_uri_handler
) {
330 plugin_data
*p
= p_d
;
331 const char *remote_ip
;
334 #if defined(HAVE_PCRE_H)
339 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
341 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
343 mod_trigger_b4_dl_patch_connection(srv
, con
, p
);
345 if (!p
->conf
.trigger_regex
|| !p
->conf
.download_regex
) return HANDLER_GO_ON
;
347 # if !defined(HAVE_GDBM_H) && !defined(USE_MEMCACHED)
348 return HANDLER_GO_ON
;
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
) {
352 /* can't decide which one */
354 return HANDLER_GO_ON
;
356 # elif defined(HAVE_GDBM_H)
357 if (!p
->conf
.db
) return HANDLER_GO_ON
;
359 if (!p
->conf
.memc
) return HANDLER_GO_ON
;
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 */
365 remote_ip
= ds
->value
->ptr
;
367 /* memcache can't handle spaces */
369 remote_ip
= inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
));
373 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "(debug) remote-ip:", remote_ip
);
376 /* check if URL is a trigger -> insert IP into DB */
377 if ((n
= pcre_exec(p
->conf
.trigger_regex
, NULL
, CONST_BUF_LEN(con
->uri
.path
), 0, 0, ovec
, 3 * N
)) < 0) {
378 if (n
!= PCRE_ERROR_NOMATCH
) {
379 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
380 "execution error while matching:", n
);
382 return HANDLER_ERROR
;
385 # if defined(HAVE_GDBM_H)
387 /* the trigger matched */
390 key
.dptr
= (char *)remote_ip
;
391 key
.dsize
= strlen(remote_ip
);
393 val
.dptr
= (char *)&(srv
->cur_ts
);
394 val
.dsize
= sizeof(srv
->cur_ts
);
396 if (0 != gdbm_store(p
->conf
.db
, key
, val
, GDBM_REPLACE
)) {
397 log_error_write(srv
, __FILE__
, __LINE__
, "s",
402 # if defined(USE_MEMCACHED)
405 buffer_copy_buffer(p
->tmp_buf
, p
->conf
.mc_namespace
);
406 buffer_append_string(p
->tmp_buf
, remote_ip
);
408 len
= buffer_string_length(p
->tmp_buf
);
409 for (i
= 0; i
< len
; i
++) {
410 if (p
->tmp_buf
->ptr
[i
] == ' ') p
->tmp_buf
->ptr
[i
] = '-';
414 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "(debug) triggered IP:", p
->tmp_buf
);
417 if (MEMCACHED_SUCCESS
!= memcached_set(p
->conf
.memc
,
418 CONST_BUF_LEN(p
->tmp_buf
),
419 (const char *)&(srv
->cur_ts
), sizeof(srv
->cur_ts
),
420 p
->conf
.trigger_timeout
, 0)) {
421 log_error_write(srv
, __FILE__
, __LINE__
, "s",
428 /* check if URL is a download -> check IP in DB, update timestamp */
429 if ((n
= pcre_exec(p
->conf
.download_regex
, NULL
, CONST_BUF_LEN(con
->uri
.path
), 0, 0, ovec
, 3 * N
)) < 0) {
430 if (n
!= PCRE_ERROR_NOMATCH
) {
431 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
432 "execution error while matching: ", n
);
433 return HANDLER_ERROR
;
436 /* the download uri matched */
437 # if defined(HAVE_GDBM_H)
442 key
.dptr
= (char *)remote_ip
;
443 key
.dsize
= strlen(remote_ip
);
445 val
= gdbm_fetch(p
->conf
.db
, key
);
447 if (val
.dptr
== NULL
) {
448 /* not found, redirect */
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;
454 return HANDLER_FINISHED
;
457 memcpy(&last_hit
, val
.dptr
, sizeof(time_t));
461 if (srv
->cur_ts
- last_hit
> p
->conf
.trigger_timeout
) {
462 /* found, but timeout, redirect */
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;
469 if (0 != gdbm_delete(p
->conf
.db
, key
)) {
470 log_error_write(srv
, __FILE__
, __LINE__
, "s",
475 return HANDLER_FINISHED
;
478 val
.dptr
= (char *)&(srv
->cur_ts
);
479 val
.dsize
= sizeof(srv
->cur_ts
);
481 if (0 != gdbm_store(p
->conf
.db
, key
, val
, GDBM_REPLACE
)) {
482 log_error_write(srv
, __FILE__
, __LINE__
, "s",
488 # if defined(USE_MEMCACHED)
492 buffer_copy_buffer(p
->tmp_buf
, p
->conf
.mc_namespace
);
493 buffer_append_string(p
->tmp_buf
, remote_ip
);
495 len
= buffer_string_length(p
->tmp_buf
);
496 for (i
= 0; i
< len
; i
++) {
497 if (p
->tmp_buf
->ptr
[i
] == ' ') p
->tmp_buf
->ptr
[i
] = '-';
501 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "(debug) checking IP:", p
->tmp_buf
);
506 * memcached is do expiration for us, as long as we can fetch it every thing is ok
507 * and the timestamp is updated
510 if (MEMCACHED_SUCCESS
!= memcached_exist(p
->conf
.memc
, CONST_BUF_LEN(p
->tmp_buf
))) {
511 response_header_insert(srv
, con
, CONST_STR_LEN("Location"), CONST_BUF_LEN(p
->conf
.deny_url
));
513 con
->http_status
= 307;
514 con
->file_finished
= 1;
516 return HANDLER_FINISHED
;
519 /* set a new timeout */
520 if (MEMCACHED_SUCCESS
!= memcached_set(p
->conf
.memc
,
521 CONST_BUF_LEN(p
->tmp_buf
),
522 (const char *)&(srv
->cur_ts
), sizeof(srv
->cur_ts
),
523 p
->conf
.trigger_timeout
, 0)) {
524 log_error_write(srv
, __FILE__
, __LINE__
, "s",
537 return HANDLER_GO_ON
;
540 #if defined(HAVE_GDBM_H)
541 TRIGGER_FUNC(mod_trigger_b4_dl_handle_trigger
) {
542 plugin_data
*p
= p_d
;
545 /* check DB each minute */
546 if (srv
->cur_ts
% 60 != 0) return HANDLER_GO_ON
;
549 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
550 plugin_config
*s
= p
->config_storage
[i
];
551 datum key
, val
, okey
;
553 if (!s
->db
) continue;
557 /* according to the manual this loop + delete does delete all entries on its way
559 * we don't care as the next round will remove them. We don't have to perfect here.
561 for (key
= gdbm_firstkey(s
->db
); key
.dptr
; key
= gdbm_nextkey(s
->db
, okey
)) {
568 val
= gdbm_fetch(s
->db
, key
);
570 memcpy(&last_hit
, val
.dptr
, sizeof(time_t));
574 if (srv
->cur_ts
- last_hit
> s
->trigger_timeout
) {
575 gdbm_delete(s
->db
, key
);
580 if (okey
.dptr
) free(okey
.dptr
);
582 /* reorg once a day */
583 if ((srv
->cur_ts
% (60 * 60 * 24) != 0)) gdbm_reorganize(s
->db
);
585 return HANDLER_GO_ON
;
589 /* this function is called at dlopen() time and inits the callbacks */
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");
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
;
602 p
->cleanup
= mod_trigger_b4_dl_free
;
611 #pragma message("(either gdbm or libmemcache) and pcre are required, but were not found")
613 int mod_trigger_b4_dl_plugin_init(plugin
*p
);
614 int mod_trigger_b4_dl_plugin_init(plugin
*p
) {