9 #include "inet_ntop_cache.h"
16 #if defined(HAVE_GDBM_H)
20 #if defined(HAVE_PCRE_H)
24 #if defined(USE_MEMCACHED)
25 # include <libmemcached/memcached.h>
29 * this is a trigger_b4_dl for a lighttpd plugin
33 /* plugin config for all request/connections */
44 #if defined(HAVE_PCRE_H)
48 #if defined(HAVE_GDBM_H)
52 #if defined(USE_MEMCACHED)
56 unsigned short trigger_timeout
;
65 plugin_config
**config_storage
;
70 /* init the plugin data */
71 INIT_FUNC(mod_trigger_b4_dl_init
) {
74 p
= calloc(1, sizeof(*p
));
76 p
->tmp_buf
= buffer_init();
81 /* detroy the plugin data */
82 FREE_FUNC(mod_trigger_b4_dl_free
) {
87 if (!p
) return HANDLER_GO_ON
;
89 if (p
->config_storage
) {
91 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
92 plugin_config
*s
= p
->config_storage
[i
];
94 if (NULL
== s
) continue;
96 buffer_free(s
->db_filename
);
97 buffer_free(s
->download_url
);
98 buffer_free(s
->trigger_url
);
99 buffer_free(s
->deny_url
);
101 buffer_free(s
->mc_namespace
);
102 array_free(s
->mc_hosts
);
104 #if defined(HAVE_PCRE_H)
105 if (s
->trigger_regex
) pcre_free(s
->trigger_regex
);
106 if (s
->download_regex
) pcre_free(s
->download_regex
);
108 #if defined(HAVE_GDBM_H)
109 if (s
->db
) gdbm_close(s
->db
);
111 #if defined(USE_MEMCACHED)
112 if (s
->memc
) memcached_free(s
->memc
);
117 free(p
->config_storage
);
120 buffer_free(p
->tmp_buf
);
124 return HANDLER_GO_ON
;
127 /* handle plugin config and check values */
129 SETDEFAULTS_FUNC(mod_trigger_b4_dl_set_defaults
) {
130 plugin_data
*p
= p_d
;
134 config_values_t cv
[] = {
135 { "trigger-before-download.gdbm-filename", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
136 { "trigger-before-download.trigger-url", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
137 { "trigger-before-download.download-url", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
138 { "trigger-before-download.deny-url", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
139 { "trigger-before-download.trigger-timeout", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
140 { "trigger-before-download.memcache-hosts", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
141 { "trigger-before-download.memcache-namespace", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 6 */
142 { "trigger-before-download.debug", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 7 */
143 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
146 if (!p
) return HANDLER_ERROR
;
148 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
150 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
151 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
153 #if defined(HAVE_PCRE_H)
158 s
= calloc(1, sizeof(plugin_config
));
159 s
->db_filename
= buffer_init();
160 s
->download_url
= buffer_init();
161 s
->trigger_url
= buffer_init();
162 s
->deny_url
= buffer_init();
163 s
->mc_hosts
= array_init();
164 s
->mc_namespace
= buffer_init();
166 cv
[0].destination
= s
->db_filename
;
167 cv
[1].destination
= s
->trigger_url
;
168 cv
[2].destination
= s
->download_url
;
169 cv
[3].destination
= s
->deny_url
;
170 cv
[4].destination
= &(s
->trigger_timeout
);
171 cv
[5].destination
= s
->mc_hosts
;
172 cv
[6].destination
= s
->mc_namespace
;
173 cv
[7].destination
= &(s
->debug
);
175 p
->config_storage
[i
] = s
;
177 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
178 return HANDLER_ERROR
;
180 #if defined(HAVE_GDBM_H)
181 if (!buffer_string_is_empty(s
->db_filename
)) {
182 if (NULL
== (s
->db
= gdbm_open(s
->db_filename
->ptr
, 4096, GDBM_WRCREAT
| GDBM_NOLOCK
, S_IRUSR
| S_IWUSR
, 0))) {
183 log_error_write(srv
, __FILE__
, __LINE__
, "s",
185 return HANDLER_ERROR
;
187 fd_close_on_exec(gdbm_fdesc(s
->db
));
190 #if defined(HAVE_PCRE_H)
191 if (!buffer_string_is_empty(s
->download_url
)) {
192 if (NULL
== (s
->download_regex
= pcre_compile(s
->download_url
->ptr
,
193 0, &errptr
, &erroff
, NULL
))) {
195 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
196 "compiling regex for download-url failed:",
197 s
->download_url
, "pos:", erroff
);
198 return HANDLER_ERROR
;
202 if (!buffer_string_is_empty(s
->trigger_url
)) {
203 if (NULL
== (s
->trigger_regex
= pcre_compile(s
->trigger_url
->ptr
,
204 0, &errptr
, &erroff
, NULL
))) {
206 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
207 "compiling regex for trigger-url failed:",
208 s
->trigger_url
, "pos:", erroff
);
210 return HANDLER_ERROR
;
215 if (s
->mc_hosts
->used
) {
216 #if defined(USE_MEMCACHED)
217 buffer
*option_string
= buffer_init();
221 data_string
*ds
= (data_string
*)s
->mc_hosts
->data
[0];
223 buffer_append_string_len(option_string
, CONST_STR_LEN("--SERVER="));
224 buffer_append_string_buffer(option_string
, ds
->value
);
227 for (k
= 1; k
< s
->mc_hosts
->used
; k
++) {
228 data_string
*ds
= (data_string
*)s
->mc_hosts
->data
[k
];
230 buffer_append_string_len(option_string
, CONST_STR_LEN(" --SERVER="));
231 buffer_append_string_buffer(option_string
, ds
->value
);
234 s
->memc
= memcached(CONST_BUF_LEN(option_string
));
236 if (NULL
== s
->memc
) {
237 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
238 "configuring memcached failed for option string:",
241 buffer_free(option_string
);
243 if (NULL
== s
->memc
) return HANDLER_ERROR
;
245 log_error_write(srv
, __FILE__
, __LINE__
, "s",
246 "memcache support is not compiled in but trigger-before-download.memcache-hosts is set, aborting");
247 return HANDLER_ERROR
;
252 return HANDLER_GO_ON
;
257 static int mod_trigger_b4_dl_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
259 plugin_config
*s
= p
->config_storage
[0];
261 #if defined(HAVE_GDBM)
264 #if defined(HAVE_PCRE_H)
265 PATCH(download_regex
);
266 PATCH(trigger_regex
);
268 PATCH(trigger_timeout
);
272 #if defined(USE_MEMCACHED)
276 /* skip the first, the global context */
277 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
278 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
279 s
= p
->config_storage
[i
];
281 /* condition didn't match */
282 if (!config_check_cond(srv
, con
, dc
)) continue;
285 for (j
= 0; j
< dc
->value
->used
; j
++) {
286 data_unset
*du
= dc
->value
->data
[j
];
288 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.download-url"))) {
289 #if defined(HAVE_PCRE_H)
290 PATCH(download_regex
);
292 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.trigger-url"))) {
293 # if defined(HAVE_PCRE_H)
294 PATCH(trigger_regex
);
296 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.gdbm-filename"))) {
297 #if defined(HAVE_GDBM_H)
300 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.trigger-timeout"))) {
301 PATCH(trigger_timeout
);
302 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.debug"))) {
304 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.deny-url"))) {
306 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.memcache-namespace"))) {
308 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("trigger-before-download.memcache-hosts"))) {
309 #if defined(USE_MEMCACHED)
320 URIHANDLER_FUNC(mod_trigger_b4_dl_uri_handler
) {
321 plugin_data
*p
= p_d
;
322 const char *remote_ip
;
325 #if defined(HAVE_PCRE_H)
330 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
332 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
334 mod_trigger_b4_dl_patch_connection(srv
, con
, p
);
336 if (!p
->conf
.trigger_regex
|| !p
->conf
.download_regex
) return HANDLER_GO_ON
;
338 # if !defined(HAVE_GDBM_H) && !defined(USE_MEMCACHED)
339 return HANDLER_GO_ON
;
340 # elif defined(HAVE_GDBM_H) && defined(USE_MEMCACHED)
341 if (!p
->conf
.db
&& !p
->conf
.memc
) return HANDLER_GO_ON
;
342 if (p
->conf
.db
&& p
->conf
.memc
) {
343 /* can't decide which one */
345 return HANDLER_GO_ON
;
347 # elif defined(HAVE_GDBM_H)
348 if (!p
->conf
.db
) return HANDLER_GO_ON
;
350 if (!p
->conf
.memc
) return HANDLER_GO_ON
;
353 if (NULL
!= (ds
= (data_string
*)array_get_element(con
->request
.headers
, "X-Forwarded-For"))) {
354 /* X-Forwarded-For contains the ip behind the proxy */
356 remote_ip
= ds
->value
->ptr
;
358 /* memcache can't handle spaces */
360 remote_ip
= inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
));
364 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "(debug) remote-ip:", remote_ip
);
367 /* check if URL is a trigger -> insert IP into DB */
368 if ((n
= pcre_exec(p
->conf
.trigger_regex
, NULL
, CONST_BUF_LEN(con
->uri
.path
), 0, 0, ovec
, 3 * N
)) < 0) {
369 if (n
!= PCRE_ERROR_NOMATCH
) {
370 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
371 "execution error while matching:", n
);
373 return HANDLER_ERROR
;
376 # if defined(HAVE_GDBM_H)
378 /* the trigger matched */
381 key
.dptr
= (char *)remote_ip
;
382 key
.dsize
= strlen(remote_ip
);
384 val
.dptr
= (char *)&(srv
->cur_ts
);
385 val
.dsize
= sizeof(srv
->cur_ts
);
387 if (0 != gdbm_store(p
->conf
.db
, key
, val
, GDBM_REPLACE
)) {
388 log_error_write(srv
, __FILE__
, __LINE__
, "s",
393 # if defined(USE_MEMCACHED)
396 buffer_copy_buffer(p
->tmp_buf
, p
->conf
.mc_namespace
);
397 buffer_append_string(p
->tmp_buf
, remote_ip
);
399 len
= buffer_string_length(p
->tmp_buf
);
400 for (i
= 0; i
< len
; i
++) {
401 if (p
->tmp_buf
->ptr
[i
] == ' ') p
->tmp_buf
->ptr
[i
] = '-';
405 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "(debug) triggered IP:", p
->tmp_buf
);
408 if (MEMCACHED_SUCCESS
!= memcached_set(p
->conf
.memc
,
409 CONST_BUF_LEN(p
->tmp_buf
),
410 (const char *)&(srv
->cur_ts
), sizeof(srv
->cur_ts
),
411 p
->conf
.trigger_timeout
, 0)) {
412 log_error_write(srv
, __FILE__
, __LINE__
, "s",
419 /* check if URL is a download -> check IP in DB, update timestamp */
420 if ((n
= pcre_exec(p
->conf
.download_regex
, NULL
, CONST_BUF_LEN(con
->uri
.path
), 0, 0, ovec
, 3 * N
)) < 0) {
421 if (n
!= PCRE_ERROR_NOMATCH
) {
422 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
423 "execution error while matching: ", n
);
424 return HANDLER_ERROR
;
427 /* the download uri matched */
428 # if defined(HAVE_GDBM_H)
433 key
.dptr
= (char *)remote_ip
;
434 key
.dsize
= strlen(remote_ip
);
436 val
= gdbm_fetch(p
->conf
.db
, key
);
438 if (val
.dptr
== NULL
) {
439 /* not found, redirect */
441 response_header_insert(srv
, con
, CONST_STR_LEN("Location"), CONST_BUF_LEN(p
->conf
.deny_url
));
442 con
->http_status
= 307;
443 con
->file_finished
= 1;
445 return HANDLER_FINISHED
;
448 memcpy(&last_hit
, val
.dptr
, sizeof(time_t));
452 if (srv
->cur_ts
- last_hit
> p
->conf
.trigger_timeout
) {
453 /* found, but timeout, redirect */
455 response_header_insert(srv
, con
, CONST_STR_LEN("Location"), CONST_BUF_LEN(p
->conf
.deny_url
));
456 con
->http_status
= 307;
457 con
->file_finished
= 1;
460 if (0 != gdbm_delete(p
->conf
.db
, key
)) {
461 log_error_write(srv
, __FILE__
, __LINE__
, "s",
466 return HANDLER_FINISHED
;
469 val
.dptr
= (char *)&(srv
->cur_ts
);
470 val
.dsize
= sizeof(srv
->cur_ts
);
472 if (0 != gdbm_store(p
->conf
.db
, key
, val
, GDBM_REPLACE
)) {
473 log_error_write(srv
, __FILE__
, __LINE__
, "s",
479 # if defined(USE_MEMCACHED)
483 buffer_copy_buffer(p
->tmp_buf
, p
->conf
.mc_namespace
);
484 buffer_append_string(p
->tmp_buf
, remote_ip
);
486 len
= buffer_string_length(p
->tmp_buf
);
487 for (i
= 0; i
< len
; i
++) {
488 if (p
->tmp_buf
->ptr
[i
] == ' ') p
->tmp_buf
->ptr
[i
] = '-';
492 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "(debug) checking IP:", p
->tmp_buf
);
497 * memcached is do expiration for us, as long as we can fetch it every thing is ok
498 * and the timestamp is updated
501 if (MEMCACHED_SUCCESS
!= memcached_exist(p
->conf
.memc
, CONST_BUF_LEN(p
->tmp_buf
))) {
502 response_header_insert(srv
, con
, CONST_STR_LEN("Location"), CONST_BUF_LEN(p
->conf
.deny_url
));
504 con
->http_status
= 307;
505 con
->file_finished
= 1;
507 return HANDLER_FINISHED
;
510 /* set a new timeout */
511 if (MEMCACHED_SUCCESS
!= memcached_set(p
->conf
.memc
,
512 CONST_BUF_LEN(p
->tmp_buf
),
513 (const char *)&(srv
->cur_ts
), sizeof(srv
->cur_ts
),
514 p
->conf
.trigger_timeout
, 0)) {
515 log_error_write(srv
, __FILE__
, __LINE__
, "s",
528 return HANDLER_GO_ON
;
531 #if defined(HAVE_GDBM_H)
532 TRIGGER_FUNC(mod_trigger_b4_dl_handle_trigger
) {
533 plugin_data
*p
= p_d
;
536 /* check DB each minute */
537 if (srv
->cur_ts
% 60 != 0) return HANDLER_GO_ON
;
540 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
541 plugin_config
*s
= p
->config_storage
[i
];
542 datum key
, val
, okey
;
544 if (!s
->db
) continue;
548 /* according to the manual this loop + delete does delete all entries on its way
550 * we don't care as the next round will remove them. We don't have to perfect here.
552 for (key
= gdbm_firstkey(s
->db
); key
.dptr
; key
= gdbm_nextkey(s
->db
, okey
)) {
559 val
= gdbm_fetch(s
->db
, key
);
561 memcpy(&last_hit
, val
.dptr
, sizeof(time_t));
565 if (srv
->cur_ts
- last_hit
> s
->trigger_timeout
) {
566 gdbm_delete(s
->db
, key
);
571 if (okey
.dptr
) free(okey
.dptr
);
573 /* reorg once a day */
574 if ((srv
->cur_ts
% (60 * 60 * 24) != 0)) gdbm_reorganize(s
->db
);
576 return HANDLER_GO_ON
;
580 /* this function is called at dlopen() time and inits the callbacks */
582 int mod_trigger_b4_dl_plugin_init(plugin
*p
);
583 int mod_trigger_b4_dl_plugin_init(plugin
*p
) {
584 p
->version
= LIGHTTPD_VERSION_ID
;
585 p
->name
= buffer_init_string("trigger_b4_dl");
587 p
->init
= mod_trigger_b4_dl_init
;
588 p
->handle_uri_clean
= mod_trigger_b4_dl_uri_handler
;
589 p
->set_defaults
= mod_trigger_b4_dl_set_defaults
;
590 #if defined(HAVE_GDBM_H)
591 p
->handle_trigger
= mod_trigger_b4_dl_handle_trigger
;
593 p
->cleanup
= mod_trigger_b4_dl_free
;