1 /*-
2 * Copyright 2016 Vsevolod Stakhov
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "config.h"
17 #include "libserver/dynamic_cfg.h"
18 #include "libserver/cfg_file_private.h"
19 #include "libutil/rrd.h"
20 #include "libserver/maps/map.h"
21 #include "libserver/maps/map_helpers.h"
22 #include "libserver/maps/map_private.h"
23 #include "libserver/http/http_private.h"
24 #include "libserver/http/http_router.h"
25 #include "libstat/stat_api.h"
26 #include "rspamd.h"
27 #include "libserver/worker_util.h"
28 #include "worker_private.h"
29 #include "lua/lua_common.h"
30 #include "cryptobox.h"
31 #include "ottery.h"
32 #include "fuzzy_wire.h"
33 #include "unix-std.h"
34 #include "utlist.h"
35 #include "libmime/lang_detection.h"
36 #include <math.h>
37
38 /* 60 seconds for worker's IO */
39 #define DEFAULT_WORKER_IO_TIMEOUT 60000
40
41 /* HTTP paths */
42 #define PATH_AUTH "/auth"
43 #define PATH_SYMBOLS "/symbols"
44 #define PATH_ACTIONS "/actions"
45 #define PATH_MAPS "/maps"
46 #define PATH_GET_MAP "/getmap"
47 #define PATH_GRAPH "/graph"
48 #define PATH_PIE_CHART "/pie"
49 #define PATH_HEALTHY "/healthy"
50 #define PATH_HISTORY "/history"
51 #define PATH_HISTORY_RESET "/historyreset"
52 #define PATH_LEARN_SPAM "/learnspam"
53 #define PATH_LEARN_HAM "/learnham"
54 #define PATH_METRICS "/metrics"
55 #define PATH_READY "/ready"
56 #define PATH_SAVE_ACTIONS "/saveactions"
57 #define PATH_SAVE_SYMBOLS "/savesymbols"
58 #define PATH_SAVE_MAP "/savemap"
59 #define PATH_SCAN "/scan"
60 #define PATH_CHECK "/check"
61 #define PATH_CHECKV2 "/checkv2"
62 #define PATH_STAT "/stat"
63 #define PATH_STAT_RESET "/statreset"
64 #define PATH_COUNTERS "/counters"
65 #define PATH_ERRORS "/errors"
66 #define PATH_NEIGHBOURS "/neighbours"
67 #define PATH_PLUGINS "/plugins"
68 #define PATH_PING "/ping"
69
70 #define msg_err_session(...) rspamd_default_log_function(G_LOG_LEVEL_CRITICAL, \
71 session->pool->tag.tagname, session->pool->tag.uid, \
72 G_STRFUNC, \
73 __VA_ARGS__)
74 #define msg_warn_session(...) rspamd_default_log_function (G_LOG_LEVEL_WARNING, \
75 session->pool->tag.tagname, session->pool->tag.uid, \
76 G_STRFUNC, \
77 __VA_ARGS__)
78 #define msg_info_session(...) rspamd_default_log_function (G_LOG_LEVEL_INFO, \
79 session->pool->tag.tagname, session->pool->tag.uid, \
80 G_STRFUNC, \
81 __VA_ARGS__)
82 #define msg_err_ctx(...) rspamd_default_log_function(G_LOG_LEVEL_CRITICAL, \
83 "controller", ctx->cfg->cfg_pool->tag.uid, \
84 G_STRFUNC, \
85 __VA_ARGS__)
86 #define msg_warn_ctx(...) rspamd_default_log_function (G_LOG_LEVEL_WARNING, \
87 "controller", ctx->cfg->cfg_pool->tag.uid, \
88 G_STRFUNC, \
89 __VA_ARGS__)
90 #define msg_info_ctx(...) rspamd_default_log_function (G_LOG_LEVEL_INFO, \
91 "controller", ctx->cfg->cfg_pool->tag.uid, \
92 G_STRFUNC, \
93 __VA_ARGS__)
94
95 #define msg_debug_session(...) rspamd_conditional_debug_fast (NULL, session->from_addr, \
96 rspamd_controller_log_id, "controller", session->pool->tag.uid, \
97 G_STRFUNC, \
98 __VA_ARGS__)
99
100 INIT_LOG_MODULE(controller)
101
102 /* Graph colors */
103 #define COLOR_CLEAN "#58A458"
104 #define COLOR_PROBABLE_SPAM "#D67E7E"
105 #define COLOR_GREYLIST "#A0A0A0"
106 #define COLOR_REJECT "#CB4B4B"
107 #define COLOR_TOTAL "#9440ED"
108
109 static const guint64 rspamd_controller_ctx_magic = 0xf72697805e6941faULL;
110
111 extern void fuzzy_stat_command (struct rspamd_task *task);
112
113 gpointer init_controller_worker (struct rspamd_config *cfg);
114 void start_controller_worker (struct rspamd_worker *worker);
115
116 worker_t controller_worker = {
117 "controller", /* Name */
118 init_controller_worker, /* Init function */
119 start_controller_worker, /* Start function */
120 RSPAMD_WORKER_HAS_SOCKET | RSPAMD_WORKER_KILLABLE |
121 RSPAMD_WORKER_SCANNER | RSPAMD_WORKER_CONTROLLER,
122 RSPAMD_WORKER_SOCKET_TCP, /* TCP socket */
123 RSPAMD_WORKER_VER /* Version info */
124 };
125 /*
126 * Worker's context
127 */
128 struct rspamd_controller_worker_ctx {
129 guint64 magic;
130 /* Events base */
131 struct ev_loop *event_loop;
132 /* DNS resolver */
133 struct rspamd_dns_resolver *resolver;
134 /* Config */
135 struct rspamd_config *cfg;
136 /* END OF COMMON PART */
137 ev_tstamp timeout;
138 /* Whether we use ssl for this server */
139 gboolean use_ssl;
140 /* Webui password */
141 gchar *password;
142 /* Privilleged password */
143 gchar *enable_password;
144 /* Cached versions of the passwords */
145 rspamd_ftok_t cached_password;
146 rspamd_ftok_t cached_enable_password;
147 /* HTTP server */
148 struct rspamd_http_context *http_ctx;
149 struct rspamd_http_connection_router *http;
150 /* Server's start time */
151 ev_tstamp start_time;
152 /* Main server */
153 struct rspamd_main *srv;
154 /* SSL cert */
155 gchar *ssl_cert;
156 /* SSL private key */
157 gchar *ssl_key;
158 /* A map of secure IP */
159 const ucl_object_t *secure_ip;
160 struct rspamd_radix_map_helper *secure_map;
161
162 /* Static files dir */
163 gchar *static_files_dir;
164
165 /* Custom commands registered by plugins */
166 GHashTable *custom_commands;
167
168 /* Plugins registered from lua */
169 GHashTable *plugins;
170
171 /* Worker */
172 struct rspamd_worker *worker;
173
174 /* Local keypair */
175 gpointer key;
176
177 struct rspamd_rrd_file *rrd;
178 struct rspamd_lang_detector *lang_det;
179 gdouble task_timeout;
180
181 /* Health check stuff */
182 guint workers_count;
183 guint scanners_count;
184 guint workers_hb_lost;
185 ev_timer health_check_timer;
186 };
187
188 struct rspamd_controller_plugin_cbdata {
189 lua_State *L;
190 struct rspamd_controller_worker_ctx *ctx;
191 gchar *plugin;
192 struct ucl_lua_funcdata *handler;
193 ucl_object_t *obj;
194 gboolean is_enable;
195 gboolean need_task;
196 guint version;
197 };
198
199 static gboolean
rspamd_is_encrypted_password(const gchar * password,struct rspamd_controller_pbkdf const ** pbkdf)200 rspamd_is_encrypted_password (const gchar *password,
201 struct rspamd_controller_pbkdf const **pbkdf)
202 {
203 const gchar *start, *end;
204 gint64 id;
205 gsize size, i;
206 gboolean ret = FALSE;
207 const struct rspamd_controller_pbkdf *p;
208
209 if (password[0] == '$') {
210 /* Parse id */
211 start = password + 1;
212 end = start;
213 size = 0;
214
215 while (*end != '\0' && g_ascii_isdigit (*end)) {
216 size++;
217 end++;
218 }
219
220 if (size > 0) {
221 gchar *endptr;
222 id = strtoul (start, &endptr, 10);
223
224 if ((endptr == NULL || *endptr == *end)) {
225 for (i = 0; i < RSPAMD_PBKDF_ID_MAX - 1; i ++) {
226 p = &pbkdf_list[i];
227
228 if (p->id == id) {
229 ret = TRUE;
230 if (pbkdf != NULL) {
231 *pbkdf = &pbkdf_list[i];
232 }
233
234 break;
235 }
236 }
237 }
238 }
239 }
240
241 return ret;
242 }
243
244 static const gchar *
rspamd_encrypted_password_get_str(const gchar * password,gsize skip,gsize * length)245 rspamd_encrypted_password_get_str (const gchar * password, gsize skip,
246 gsize * length)
247 {
248 const gchar *str, *start, *end;
249 gsize size;
250
251 start = password + skip;
252 end = start;
253 size = 0;
254
255 while (*end != '\0' && g_ascii_isalnum (*end)) {
256 size++;
257 end++;
258 }
259
260 if (size) {
261 str = start;
262 *length = size;
263 }
264 else {
265 str = NULL;
266 }
267
268 return str;
269 }
270
271 static gboolean
rspamd_check_encrypted_password(struct rspamd_controller_worker_ctx * ctx,const rspamd_ftok_t * password,const gchar * check,const struct rspamd_controller_pbkdf * pbkdf,gboolean is_enable)272 rspamd_check_encrypted_password (struct rspamd_controller_worker_ctx *ctx,
273 const rspamd_ftok_t * password, const gchar * check,
274 const struct rspamd_controller_pbkdf *pbkdf,
275 gboolean is_enable)
276 {
277 const gchar *salt, *hash;
278 gchar *salt_decoded, *key_decoded;
279 gsize salt_len = 0, key_len = 0;
280 gboolean ret = TRUE;
281 guchar *local_key;
282 rspamd_ftok_t *cache;
283 gpointer m;
284
285 /* First of all check cached versions to save resources */
286 if (is_enable && ctx->cached_enable_password.len != 0) {
287 if (password->len != ctx->cached_enable_password.len ||
288 !rspamd_constant_memcmp (password->begin,
289 ctx->cached_enable_password.begin, password->len)) {
290 msg_info_ctx ("incorrect or absent enable password has been specified");
291 return FALSE;
292 }
293
294 return TRUE;
295 }
296 else if (!is_enable && ctx->cached_password.len != 0) {
297 if (password->len != ctx->cached_password.len ||
298 !rspamd_constant_memcmp (password->begin,
299 ctx->cached_password.begin, password->len)) {
300 /* We still need to check enable password here */
301 if (ctx->cached_enable_password.len != 0) {
302 if (password->len != ctx->cached_enable_password.len ||
303 !rspamd_constant_memcmp (password->begin,
304 ctx->cached_enable_password.begin,
305 password->len)) {
306 msg_info_ctx (
307 "incorrect or absent password has been specified");
308
309 return FALSE;
310 }
311 else {
312 /* Cached matched */
313 return TRUE;
314 }
315 }
316 else {
317 /* We might want to check uncached version */
318 goto check_uncached;
319 }
320 }
321 else {
322 /* Cached matched */
323 return TRUE;
324 }
325 }
326
327 check_uncached:
328 g_assert (pbkdf != NULL);
329 /* get salt */
330 salt = rspamd_encrypted_password_get_str (check, 3, &salt_len);
331 /* get hash */
332 hash = rspamd_encrypted_password_get_str (check, 3 + salt_len + 1,
333 &key_len);
334 if (salt != NULL && hash != NULL) {
335
336 /* decode salt */
337 salt_decoded = rspamd_decode_base32 (salt, salt_len, &salt_len, RSPAMD_BASE32_DEFAULT);
338
339 if (salt_decoded == NULL || salt_len != pbkdf->salt_len) {
340 /* We have some unknown salt here */
341 msg_info_ctx ("incorrect salt: %z, while %z expected",
342 salt_len, pbkdf->salt_len);
343 g_free (salt_decoded);
344
345 return FALSE;
346 }
347
348 key_decoded = rspamd_decode_base32 (hash, key_len, &key_len, RSPAMD_BASE32_DEFAULT);
349
350 if (key_decoded == NULL || key_len != pbkdf->key_len) {
351 /* We have some unknown salt here */
352 msg_info_ctx ("incorrect key: %z, while %z expected",
353 key_len, pbkdf->key_len);
354 g_free (salt_decoded);
355 g_free (key_decoded); /* valid even if key_decoded == NULL */
356
357 return FALSE;
358 }
359
360 local_key = g_alloca (pbkdf->key_len);
361 rspamd_cryptobox_pbkdf (password->begin, password->len,
362 salt_decoded, salt_len,
363 local_key, pbkdf->key_len, pbkdf->complexity,
364 pbkdf->type);
365
366 if (!rspamd_constant_memcmp (key_decoded, local_key, pbkdf->key_len)) {
367 msg_info_ctx ("incorrect or absent password has been specified");
368 ret = FALSE;
369 }
370
371 g_free (salt_decoded);
372 g_free (key_decoded);
373 }
374
375 if (ret) {
376 /* Save cached version */
377 cache = is_enable ? &ctx->cached_enable_password : &ctx->cached_password;
378
379 if (cache->len == 0) {
380 /* Mmap region */
381 #ifdef MAP_NOCORE
382 m = mmap (NULL, password->len, PROT_WRITE,
383 MAP_PRIVATE | MAP_ANON | MAP_NOCORE, -1, 0);
384 #else
385 m = mmap (NULL, password->len, PROT_WRITE,
386 MAP_PRIVATE | MAP_ANON, -1, 0);
387 #endif
388 if (m != MAP_FAILED) {
389 memcpy (m, password->begin, password->len);
390 (void)mprotect (m, password->len, PROT_READ);
391 (void)mlock (m, password->len);
392 cache->begin = m;
393 cache->len = password->len;
394 }
395 else {
396 msg_err_ctx ("cannot store cached password, mmap failed: %s",
397 strerror (errno));
398 }
399 }
400 }
401
402 return ret;
403 }
404
405 /**
406 * Checks for X-Forwarded-For header and update client's address if needed
407 *
408 * This function is intended to be called for a trusted client to ensure that
409 * a request is not proxied through it
410 * @return 0 if no forwarded found, 1 if forwarded found and it is yet trusted
411 * and -1 if forwarded is denied
412 */
413 static gint
rspamd_controller_check_forwarded(struct rspamd_controller_session * session,struct rspamd_http_message * msg,struct rspamd_controller_worker_ctx * ctx)414 rspamd_controller_check_forwarded (struct rspamd_controller_session *session,
415 struct rspamd_http_message *msg,
416 struct rspamd_controller_worker_ctx *ctx)
417 {
418 const rspamd_ftok_t *hdr;
419 const gchar *comma;
420 const char *hdr_name = "X-Forwarded-For", *alt_hdr_name = "X-Real-IP";
421 char ip_buf[INET6_ADDRSTRLEN + 1];
422 rspamd_inet_addr_t *addr = NULL;
423 gint ret = 0;
424
425 hdr = rspamd_http_message_find_header (msg, hdr_name);
426
427 if (hdr) {
428 /*
429 * We need to parse and update the header
430 * X-Forwarded-For: client, proxy1, proxy2
431 */
432 comma = rspamd_memrchr (hdr->begin, ',', hdr->len);
433 if (comma != NULL) {
434 while (comma < hdr->begin + hdr->len &&
435 (*comma == ',' || g_ascii_isspace (*comma))) {
436 comma ++;
437 }
438 }
439 else {
440 comma = hdr->begin;
441 }
442 if (rspamd_parse_inet_address (&addr, comma,
443 (hdr->begin + hdr->len) - comma,
444 RSPAMD_INET_ADDRESS_PARSE_NO_UNIX)) {
445 /* We have addr now, so check if it is still trusted */
446 if (ctx->secure_map &&
447 rspamd_match_radix_map_addr (ctx->secure_map, addr) != NULL) {
448 /* rspamd_inet_address_to_string is not reentrant */
449 rspamd_strlcpy (ip_buf, rspamd_inet_address_to_string (addr),
450 sizeof (ip_buf));
451 msg_info_session ("allow unauthorized proxied connection "
452 "from a trusted IP %s via %s",
453 ip_buf,
454 rspamd_inet_address_to_string (session->from_addr));
455 ret = 1;
456 }
457 else {
458 ret = -1;
459 }
460
461 rspamd_inet_address_free (addr);
462 }
463 else {
464 msg_warn_session ("cannot parse forwarded IP: %T", hdr);
465 ret = -1;
466 }
467 }
468 else {
469 /* Try also X-Real-IP */
470 hdr = rspamd_http_message_find_header (msg, alt_hdr_name);
471
472 if (hdr) {
473 if (rspamd_parse_inet_address (&addr, hdr->begin, hdr->len,
474 RSPAMD_INET_ADDRESS_PARSE_NO_UNIX)) {
475 /* We have addr now, so check if it is still trusted */
476 if (ctx->secure_map &&
477 rspamd_match_radix_map_addr (ctx->secure_map, addr) != NULL) {
478 /* rspamd_inet_address_to_string is not reentrant */
479 rspamd_strlcpy (ip_buf, rspamd_inet_address_to_string (addr),
480 sizeof (ip_buf));
481 msg_info_session ("allow unauthorized proxied connection "
482 "from a trusted IP %s via %s",
483 ip_buf,
484 rspamd_inet_address_to_string (session->from_addr));
485 ret = 1;
486 }
487 else {
488 ret = -1;
489 }
490
491 rspamd_inet_address_free (addr);
492 }
493 else {
494 msg_warn_session ("cannot parse real IP: %T", hdr);
495 ret = -1;
496 }
497 }
498 }
499
500 return ret;
501 }
502
503 /* Check for password if it is required by configuration */
504 static gboolean
rspamd_controller_check_password(struct rspamd_http_connection_entry * entry,struct rspamd_controller_session * session,struct rspamd_http_message * msg,gboolean is_enable)505 rspamd_controller_check_password (struct rspamd_http_connection_entry *entry,
506 struct rspamd_controller_session *session,
507 struct rspamd_http_message *msg, gboolean is_enable)
508 {
509 const gchar *check;
510 const rspamd_ftok_t *password;
511 rspamd_ftok_t lookup;
512 GHashTable *query_args = NULL;
513 struct rspamd_controller_worker_ctx *ctx = session->ctx;
514 gboolean check_normal = TRUE, check_enable = TRUE, ret = TRUE,
515 use_enable = FALSE;
516 const struct rspamd_controller_pbkdf *pbkdf = NULL;
517
518 /* Access list logic */
519 if (rspamd_inet_address_get_af (session->from_addr) == AF_UNIX) {
520 ret = rspamd_controller_check_forwarded (session, msg, ctx);
521
522 if (ret == 1) {
523 session->is_enable = TRUE;
524
525 return TRUE;
526 }
527 else if (ret == 0) {
528 /* No forwarded found */
529 msg_info_session ("allow unauthorized connection from a unix socket");
530 session->is_enable = TRUE;
531
532 return TRUE;
533 }
534 }
535 else if (ctx->secure_map
536 && rspamd_match_radix_map_addr (ctx->secure_map, session->from_addr)
537 != NULL) {
538 ret = rspamd_controller_check_forwarded (session, msg, ctx);
539
540 if (ret == 1) {
541 session->is_enable = TRUE;
542
543 return TRUE;
544 }
545 else if (ret == 0) {
546 /* No forwarded found */
547 msg_info_session ("allow unauthorized connection from a trusted IP %s",
548 rspamd_inet_address_to_string (session->from_addr));
549 session->is_enable = TRUE;
550
551 return TRUE;
552 }
553 }
554
555 /* Password logic */
556 password = rspamd_http_message_find_header (msg, "Password");
557
558 if (password == NULL) {
559 /* Try to get password from query args */
560 query_args = rspamd_http_message_parse_query (msg);
561
562 lookup.begin = (gchar *)"password";
563 lookup.len = sizeof ("password") - 1;
564
565 password = g_hash_table_lookup (query_args, &lookup);
566 }
567
568 if (password == NULL) {
569 if (ctx->secure_map == NULL) {
570 if (ctx->password == NULL && !is_enable) {
571 return TRUE;
572 }
573 else if (is_enable && (ctx->password == NULL &&
574 ctx->enable_password == NULL)) {
575 session->is_enable = TRUE;
576 return TRUE;
577 }
578 }
579
580 msg_info_session ("absent password has been specified; source ip: %s",
581 rspamd_inet_address_to_string_pretty (session->from_addr));
582 ret = FALSE;
583 }
584 else {
585 if (rspamd_ftok_cstr_equal (password, "q1", FALSE) ||
586 rspamd_ftok_cstr_equal (password, "q2", FALSE)) {
587 msg_info_session ("deny default password for remote access; source ip: %s",
588 rspamd_inet_address_to_string_pretty (session->from_addr));
589 ret = FALSE;
590 goto end;
591 }
592
593 if (is_enable) {
594 /* For privileged commands we strictly require enable password */
595 if (ctx->enable_password != NULL) {
596 check = ctx->enable_password;
597 use_enable = TRUE;
598 }
599 else {
600 /* Use just a password (legacy mode) */
601 msg_info(
602 "using password as enable_password for a privileged command");
603 check = ctx->password;
604 }
605
606 if (check != NULL) {
607 if (!rspamd_is_encrypted_password (check, &pbkdf)) {
608 ret = FALSE;
609
610 if (strlen (check) == password->len) {
611 ret = rspamd_constant_memcmp (password->begin, check,
612 password->len);
613 }
614 }
615 else {
616 ret = rspamd_check_encrypted_password (ctx, password, check,
617 pbkdf, use_enable);
618 }
619 }
620 else {
621 msg_warn_session (
622 "no password to check while executing a privileged command; source ip: %s",
623 rspamd_inet_address_to_string_pretty (session->from_addr));
624 ret = FALSE;
625 }
626
627 if (ret) {
628 session->is_enable = TRUE;
629 }
630 }
631 else {
632 /* Accept both normal and enable passwords */
633 if (ctx->password != NULL) {
634 check = ctx->password;
635
636 if (!rspamd_is_encrypted_password (check, &pbkdf)) {
637 check_normal = FALSE;
638
639 if (strlen (check) == password->len) {
640 check_normal = rspamd_constant_memcmp (password->begin,
641 check,
642 password->len);
643 }
644 }
645 else {
646 check_normal = rspamd_check_encrypted_password (ctx,
647 password,
648 check, pbkdf, FALSE);
649 }
650
651 }
652 else {
653 check_normal = FALSE;
654 }
655
656 if (ctx->enable_password != NULL) {
657 check = ctx->enable_password;
658
659 if (!rspamd_is_encrypted_password (check, &pbkdf)) {
660 check_enable = FALSE;
661
662 if (strlen (check) == password->len) {
663 check_enable = rspamd_constant_memcmp (password->begin,
664 check,
665 password->len);
666 }
667 }
668 else {
669 check_enable = rspamd_check_encrypted_password (ctx,
670 password,
671 check, pbkdf, TRUE);
672 }
673
674 if (check_enable) {
675 session->is_enable = TRUE;
676 }
677 }
678 else {
679 check_enable = FALSE;
680
681 if (check_normal) {
682 /*
683 * If no enable password is specified use normal password as
684 * enable password
685 */
686 session->is_enable = TRUE;
687 }
688 }
689 }
690 }
691
692 if (check_normal == FALSE && check_enable == FALSE) {
693 msg_info ("absent or incorrect password has been specified; source ip: %s",
694 rspamd_inet_address_to_string_pretty (session->from_addr));
695 ret = FALSE;
696 }
697
698 end:
699 if (query_args != NULL) {
700 g_hash_table_unref (query_args);
701 }
702
703 if (!ret) {
704 rspamd_controller_send_error (entry, 403, "Unauthorized");
705 }
706
707 return ret;
708 }
709
710 /* Command handlers */
711
712 /*
713 * Auth command handler:
714 * request: /auth
715 * headers: Password
716 * reply: json {"auth": "ok", "version": "0.5.2", "uptime": "some uptime", "error": "none"}
717 */
718 static int
rspamd_controller_handle_auth(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)719 rspamd_controller_handle_auth (struct rspamd_http_connection_entry *conn_ent,
720 struct rspamd_http_message *msg)
721 {
722 struct rspamd_controller_session *session = conn_ent->ud;
723 struct rspamd_stat *st;
724 int64_t uptime;
725 gulong data[5];
726 ucl_object_t *obj;
727
728 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
729 return 0;
730 }
731
732 obj = ucl_object_typed_new (UCL_OBJECT);
733 st = session->ctx->srv->stat;
734 data[0] = st->actions_stat[METRIC_ACTION_NOACTION];
735 data[1] = st->actions_stat[METRIC_ACTION_ADD_HEADER] +
736 st->actions_stat[METRIC_ACTION_REWRITE_SUBJECT];
737 data[2] = st->actions_stat[METRIC_ACTION_GREYLIST];
738 data[3] = st->actions_stat[METRIC_ACTION_REJECT];
739 data[4] = st->actions_stat[METRIC_ACTION_SOFT_REJECT];
740
741 /* Get uptime */
742 uptime = ev_time () - session->ctx->start_time;
743
744 ucl_object_insert_key (obj, ucl_object_fromstring (
745 RVERSION), "version", 0, false);
746 ucl_object_insert_key (obj, ucl_object_fromstring (
747 "ok"), "auth", 0, false);
748 ucl_object_insert_key (obj, ucl_object_fromint (
749 uptime), "uptime", 0, false);
750 ucl_object_insert_key (obj, ucl_object_fromint (
751 data[0]), "clean", 0, false);
752 ucl_object_insert_key (obj, ucl_object_fromint (
753 data[1]), "probable", 0, false);
754 ucl_object_insert_key (obj, ucl_object_fromint (
755 data[2]), "greylist", 0, false);
756 ucl_object_insert_key (obj, ucl_object_fromint (
757 data[3]), "reject", 0, false);
758 ucl_object_insert_key (obj, ucl_object_fromint (
759 data[4]), "soft_reject", 0, false);
760 ucl_object_insert_key (obj, ucl_object_fromint (
761 st->messages_scanned), "scanned", 0, false);
762 ucl_object_insert_key (obj, ucl_object_fromint (
763 st->messages_learned), "learned", 0, false);
764 ucl_object_insert_key (obj, ucl_object_frombool (!session->is_enable),
765 "read_only", 0, false);
766 ucl_object_insert_key (obj, ucl_object_fromstring (session->ctx->cfg->checksum),
767 "config_id", 0, false);
768
769 rspamd_controller_send_ucl (conn_ent, obj);
770 ucl_object_unref (obj);
771
772 return 0;
773 }
774
775 /*
776 * Symbols command handler:
777 * request: /symbols
778 * reply: json [{
779 * "name": "group_name",
780 * "symbols": [
781 * {
782 * "name": "name",
783 * "weight": 0.1,
784 * "description": "description of symbol"
785 * },
786 * {...}
787 * },
788 * {...}]
789 */
790 static int
rspamd_controller_handle_symbols(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)791 rspamd_controller_handle_symbols (struct rspamd_http_connection_entry *conn_ent,
792 struct rspamd_http_message *msg)
793 {
794 struct rspamd_controller_session *session = conn_ent->ud;
795 GHashTableIter it, sit;
796 struct rspamd_symbols_group *gr;
797 struct rspamd_symbol *sym;
798 ucl_object_t *obj, *top, *sym_obj, *group_symbols;
799 gpointer k, v;
800
801 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
802 return 0;
803 }
804
805 top = ucl_object_typed_new (UCL_ARRAY);
806
807 /* Go through all symbols groups in the default metric */
808 g_hash_table_iter_init (&it, session->cfg->groups);
809
810 while (g_hash_table_iter_next (&it, &k, &v)) {
811 gr = v;
812 obj = ucl_object_typed_new (UCL_OBJECT);
813 ucl_object_insert_key (obj, ucl_object_fromstring (
814 gr->name), "group", 0, false);
815 /* Iterate through all symbols */
816
817 g_hash_table_iter_init (&sit, gr->symbols);
818 group_symbols = ucl_object_typed_new (UCL_ARRAY);
819
820 while (g_hash_table_iter_next (&sit, &k, &v)) {
821 gdouble tm = 0.0, freq = 0, freq_dev = 0;
822
823 sym = v;
824 sym_obj = ucl_object_typed_new (UCL_OBJECT);
825
826 ucl_object_insert_key (sym_obj, ucl_object_fromstring (sym->name),
827 "symbol", 0, false);
828 ucl_object_insert_key (sym_obj,
829 ucl_object_fromdouble (*sym->weight_ptr),
830 "weight", 0, false);
831 if (sym->description) {
832 ucl_object_insert_key (sym_obj,
833 ucl_object_fromstring (sym->description),
834 "description", 0, false);
835 }
836
837 if (rspamd_symcache_stat_symbol (session->ctx->cfg->cache,
838 sym->name, &freq, &freq_dev, &tm, NULL)) {
839 ucl_object_insert_key (sym_obj,
840 ucl_object_fromdouble (freq),
841 "frequency", 0, false);
842 ucl_object_insert_key (sym_obj,
843 ucl_object_fromdouble (freq_dev),
844 "frequency_stddev", 0, false);
845 ucl_object_insert_key (sym_obj,
846 ucl_object_fromdouble (tm),
847 "time", 0, false);
848 }
849
850 ucl_array_append (group_symbols, sym_obj);
851 }
852
853 ucl_object_insert_key (obj, group_symbols, "rules", 0, false);
854 ucl_array_append (top, obj);
855 }
856
857 rspamd_controller_send_ucl (conn_ent, top);
858 ucl_object_unref (top);
859
860 return 0;
861 }
862
863 /*
864 * Actions command handler:
865 * request: /actions
866 * reply: json [{
867 * "action": "no action",
868 * "value": 1.1
869 * },
870 * {...}]
871 */
872 static int
rspamd_controller_handle_actions(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)873 rspamd_controller_handle_actions (struct rspamd_http_connection_entry *conn_ent,
874 struct rspamd_http_message *msg)
875 {
876 struct rspamd_controller_session *session = conn_ent->ud;
877 struct rspamd_action *act, *tmp;
878 ucl_object_t *obj, *top;
879
880 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
881 return 0;
882 }
883
884 top = ucl_object_typed_new (UCL_ARRAY);
885
886 HASH_ITER (hh, session->cfg->actions, act, tmp) {
887 obj = ucl_object_typed_new (UCL_OBJECT);
888 ucl_object_insert_key (obj,
889 ucl_object_fromstring (act->name),
890 "action", 0, false);
891 ucl_object_insert_key (obj,
892 ucl_object_fromdouble (act->threshold),
893 "value", 0, false);
894 ucl_array_append (top, obj);
895 }
896
897 rspamd_controller_send_ucl (conn_ent, top);
898 ucl_object_unref (top);
899
900 return 0;
901 }
902
903 static gboolean
rspamd_controller_can_edit_map(struct rspamd_map_backend * bk)904 rspamd_controller_can_edit_map (struct rspamd_map_backend *bk)
905 {
906 gchar *fpath;
907
908 if (access (bk->uri, W_OK) == 0) {
909 return TRUE;
910 }
911 else if (access (bk->uri, R_OK) == -1 && errno == ENOENT) {
912 fpath = g_path_get_dirname (bk->uri);
913
914 if (fpath) {
915 if (access (fpath, W_OK) == 0) {
916 g_free (fpath);
917
918 return TRUE;
919 }
920
921 g_free (fpath);
922 }
923 }
924
925 return FALSE;
926 }
927
928 /*
929 * Maps command handler:
930 * request: /maps
931 * headers: Password
932 * reply: json [
933 * {
934 * "map": "name",
935 * "description": "description",
936 * "editable": true
937 * },
938 * {...}
939 * ]
940 */
941 static int
rspamd_controller_handle_maps(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)942 rspamd_controller_handle_maps (struct rspamd_http_connection_entry *conn_ent,
943 struct rspamd_http_message *msg)
944 {
945 struct rspamd_controller_session *session = conn_ent->ud;
946 GList *cur;
947 struct rspamd_map *map;
948 struct rspamd_map_backend *bk;
949 guint i;
950 gboolean editable;
951 ucl_object_t *obj, *top;
952
953 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
954 return 0;
955 }
956
957 top = ucl_object_typed_new (UCL_ARRAY);
958
959 /* Iterate over all maps */
960 cur = session->ctx->cfg->maps;
961 while (cur) {
962 map = cur->data;
963
964 PTR_ARRAY_FOREACH (map->backends, i, bk) {
965
966 if (bk->protocol == MAP_PROTO_FILE) {
967 editable = rspamd_controller_can_edit_map (bk);
968
969 if (!editable && access (bk->uri, R_OK) == -1) {
970 /* Skip unreadable and non-existing maps */
971 continue;
972 }
973
974 obj = ucl_object_typed_new (UCL_OBJECT);
975 ucl_object_insert_key (obj, ucl_object_fromint (bk->id),
976 "map", 0, false);
977 if (map->description) {
978 ucl_object_insert_key (obj, ucl_object_fromstring (map->description),
979 "description", 0, false);
980 }
981 ucl_object_insert_key (obj, ucl_object_fromstring (bk->uri),
982 "uri", 0, false);
983 ucl_object_insert_key (obj, ucl_object_frombool (editable),
984 "editable", 0, false);
985 ucl_array_append (top, obj);
986 }
987 }
988 cur = g_list_next (cur);
989 }
990
991 rspamd_controller_send_ucl (conn_ent, top);
992 ucl_object_unref (top);
993
994 return 0;
995 }
996
997 /*
998 * Get map command handler:
999 * request: /getmap
1000 * headers: Password, Map
1001 * reply: plain-text
1002 */
1003 static int
rspamd_controller_handle_get_map(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1004 rspamd_controller_handle_get_map (struct rspamd_http_connection_entry *conn_ent,
1005 struct rspamd_http_message *msg)
1006 {
1007 struct rspamd_controller_session *session = conn_ent->ud;
1008 GList *cur;
1009 struct rspamd_map *map;
1010 struct rspamd_map_backend *bk = NULL;
1011 const rspamd_ftok_t *idstr;
1012 struct stat st;
1013 gint fd;
1014 gulong id, i;
1015 gboolean found = FALSE;
1016 struct rspamd_http_message *reply;
1017
1018 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
1019 return 0;
1020 }
1021
1022 idstr = rspamd_http_message_find_header (msg, "Map");
1023
1024 if (idstr == NULL) {
1025 msg_info_session ("absent map id");
1026 rspamd_controller_send_error (conn_ent, 400, "Id header missing");
1027 return 0;
1028 }
1029
1030 if (!rspamd_strtoul (idstr->begin, idstr->len, &id)) {
1031 msg_info_session ("invalid map id");
1032 rspamd_controller_send_error (conn_ent, 400, "Invalid map id");
1033 return 0;
1034 }
1035
1036 /* Now let's be sure that we have map defined in configuration */
1037 cur = session->ctx->cfg->maps;
1038 while (cur && !found) {
1039 map = cur->data;
1040
1041 PTR_ARRAY_FOREACH (map->backends, i, bk) {
1042 if (bk->id == id && bk->protocol == MAP_PROTO_FILE) {
1043 found = TRUE;
1044 break;
1045 }
1046 }
1047
1048 cur = g_list_next (cur);
1049 }
1050
1051 if (!found || bk == NULL) {
1052 msg_info_session ("map not found");
1053 rspamd_controller_send_error (conn_ent, 404, "Map not found");
1054 return 0;
1055 }
1056
1057 if (stat (bk->uri, &st) == -1 || (fd = open (bk->uri, O_RDONLY)) == -1) {
1058 reply = rspamd_http_new_message (HTTP_RESPONSE);
1059 reply->date = time (NULL);
1060 reply->code = 200;
1061 }
1062 else {
1063
1064 reply = rspamd_http_new_message (HTTP_RESPONSE);
1065 reply->date = time (NULL);
1066 reply->code = 200;
1067
1068 if (st.st_size > 0) {
1069 if (!rspamd_http_message_set_body_from_fd (reply, fd)) {
1070 close (fd);
1071 rspamd_http_message_unref (reply);
1072 msg_err_session ("cannot read map %s: %s", bk->uri, strerror (errno));
1073 rspamd_controller_send_error (conn_ent, 500, "Map read error");
1074 return 0;
1075 }
1076 }
1077 else {
1078 rspamd_fstring_t *empty_body = rspamd_fstring_new_init ("", 0);
1079 rspamd_http_message_set_body_from_fstring_steal (reply, empty_body);
1080 }
1081
1082 close (fd);
1083 }
1084
1085 rspamd_http_connection_reset (conn_ent->conn);
1086 rspamd_http_router_insert_headers (conn_ent->rt, reply);
1087 rspamd_http_connection_write_message (conn_ent->conn, reply, NULL,
1088 "text/plain", conn_ent,
1089 conn_ent->rt->timeout);
1090 conn_ent->is_reply = TRUE;
1091
1092 return 0;
1093 }
1094
1095 static ucl_object_t *
rspamd_controller_pie_element(enum rspamd_action_type action,const char * label,gdouble data)1096 rspamd_controller_pie_element (enum rspamd_action_type action,
1097 const char *label, gdouble data)
1098 {
1099 ucl_object_t *res = ucl_object_typed_new (UCL_OBJECT);
1100 const char *colors[METRIC_ACTION_MAX] = {
1101 [METRIC_ACTION_REJECT] = "#FF0000",
1102 [METRIC_ACTION_SOFT_REJECT] = "#cc9966",
1103 [METRIC_ACTION_REWRITE_SUBJECT] = "#ff6600",
1104 [METRIC_ACTION_ADD_HEADER] = "#FFD700",
1105 [METRIC_ACTION_GREYLIST] = "#436EEE",
1106 [METRIC_ACTION_NOACTION] = "#66cc00"
1107 };
1108
1109 ucl_object_insert_key (res, ucl_object_fromstring (colors[action]),
1110 "color", 0, false);
1111 ucl_object_insert_key (res, ucl_object_fromstring (label), "label", 0, false);
1112 ucl_object_insert_key (res, ucl_object_fromdouble (data), "data", 0, false);
1113 ucl_object_insert_key (res, ucl_object_fromdouble (data), "value", 0, false);
1114
1115 return res;
1116 }
1117
1118 /*
1119 * Pie chart command handler:
1120 * request: /pie
1121 * headers: Password
1122 * reply: json [
1123 * { label: "Foo", data: 11 },
1124 * { label: "Bar", data: 20 },
1125 * {...}
1126 * ]
1127 */
1128 static int
rspamd_controller_handle_pie_chart(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1129 rspamd_controller_handle_pie_chart (
1130 struct rspamd_http_connection_entry *conn_ent,
1131 struct rspamd_http_message *msg)
1132 {
1133 struct rspamd_controller_session *session = conn_ent->ud;
1134 struct rspamd_controller_worker_ctx *ctx;
1135 gdouble data[5], total;
1136 ucl_object_t *top;
1137
1138 ctx = session->ctx;
1139
1140 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
1141 return 0;
1142 }
1143
1144 top = ucl_object_typed_new (UCL_ARRAY);
1145 total = ctx->srv->stat->messages_scanned;
1146 if (total != 0) {
1147
1148 data[0] = ctx->srv->stat->actions_stat[METRIC_ACTION_NOACTION];
1149 data[1] = ctx->srv->stat->actions_stat[METRIC_ACTION_SOFT_REJECT];
1150 data[2] = (ctx->srv->stat->actions_stat[METRIC_ACTION_ADD_HEADER] +
1151 ctx->srv->stat->actions_stat[METRIC_ACTION_REWRITE_SUBJECT]);
1152 data[3] = ctx->srv->stat->actions_stat[METRIC_ACTION_GREYLIST];
1153 data[4] = ctx->srv->stat->actions_stat[METRIC_ACTION_REJECT];
1154 }
1155 else {
1156 memset (data, 0, sizeof (data));
1157 }
1158 ucl_array_append (top, rspamd_controller_pie_element (
1159 METRIC_ACTION_NOACTION, "Clean", data[0]));
1160 ucl_array_append (top, rspamd_controller_pie_element (
1161 METRIC_ACTION_SOFT_REJECT, "Temporarily rejected", data[1]));
1162 ucl_array_append (top, rspamd_controller_pie_element (
1163 METRIC_ACTION_ADD_HEADER, "Probable spam", data[2]));
1164 ucl_array_append (top, rspamd_controller_pie_element (
1165 METRIC_ACTION_GREYLIST, "Greylisted", data[3]));
1166 ucl_array_append (top, rspamd_controller_pie_element (
1167 METRIC_ACTION_REJECT, "Rejected", data[4]));
1168
1169 rspamd_controller_send_ucl (conn_ent, top);
1170 ucl_object_unref (top);
1171
1172 return 0;
1173 }
1174
1175 void
rspamd_controller_graph_point(gulong t,gulong step,struct rspamd_rrd_query_result * rrd_result,gdouble * acc,ucl_object_t ** elt)1176 rspamd_controller_graph_point (gulong t, gulong step,
1177 struct rspamd_rrd_query_result* rrd_result,
1178 gdouble *acc,
1179 ucl_object_t **elt)
1180 {
1181 guint nan_cnt;
1182 gdouble sum = 0.0, yval;
1183 ucl_object_t* data_elt;
1184 guint i, j;
1185
1186 for (i = 0; i < rrd_result->ds_count; i++) {
1187 sum = 0.0;
1188 nan_cnt = 0;
1189 data_elt = ucl_object_typed_new (UCL_OBJECT);
1190 ucl_object_insert_key (data_elt, ucl_object_fromint (t), "x", 1, false);
1191
1192 for (j = 0; j < step; j++) {
1193 yval = acc[i + j * rrd_result->ds_count];
1194 if (!isfinite (yval)) {
1195 nan_cnt++;
1196 }
1197 else {
1198 sum += yval;
1199 }
1200 }
1201 if (nan_cnt == step) {
1202 ucl_object_insert_key (data_elt, ucl_object_typed_new (UCL_NULL),
1203 "y", 1, false);
1204 }
1205 else {
1206 ucl_object_insert_key (data_elt,
1207 ucl_object_fromdouble (sum / (gdouble) step), "y", 1,
1208 false);
1209 }
1210 ucl_array_append (elt[i], data_elt);
1211 }
1212 }
1213
1214 /*
1215 * Graph command handler:
1216 * request: /graph?type=<day|week|month|year>
1217 * headers: Password
1218 * reply: json [
1219 * { label: "Foo", data: 11 },
1220 * { label: "Bar", data: 20 },
1221 * {...}
1222 * ]
1223 */
1224 static int
rspamd_controller_handle_graph(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1225 rspamd_controller_handle_graph (
1226 struct rspamd_http_connection_entry *conn_ent,
1227 struct rspamd_http_message *msg)
1228 {
1229 GHashTable *query;
1230 struct rspamd_controller_session *session = conn_ent->ud;
1231 struct rspamd_controller_worker_ctx *ctx;
1232 rspamd_ftok_t srch, *value;
1233 struct rspamd_rrd_query_result *rrd_result;
1234 gulong i, k, start_row, cnt, t, ts, step;
1235 gdouble *acc;
1236 ucl_object_t *res, *elt[METRIC_ACTION_MAX];
1237 enum {
1238 rra_day = 0,
1239 rra_week,
1240 rra_month,
1241 rra_year,
1242 rra_invalid
1243 } rra_num = rra_invalid;
1244 /* How many points are we going to send to display */
1245 static const guint desired_points = 500;
1246
1247 ctx = session->ctx;
1248
1249 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
1250 return 0;
1251 }
1252
1253 if (ctx->rrd == NULL) {
1254 msg_err_session ("no rrd configured");
1255 rspamd_controller_send_error (conn_ent, 404, "No rrd configured for graphs");
1256
1257 return 0;
1258 }
1259
1260 query = rspamd_http_message_parse_query (msg);
1261 srch.begin = (gchar *)"type";
1262 srch.len = 4;
1263
1264 if (query == NULL || (value = g_hash_table_lookup (query, &srch)) == NULL) {
1265 msg_err_session ("absent graph type query");
1266 rspamd_controller_send_error (conn_ent, 400, "Absent graph type");
1267
1268 if (query) {
1269 g_hash_table_unref (query);
1270 }
1271
1272 return 0;
1273 }
1274
1275 if (value->len == 3 && rspamd_lc_cmp (value->begin, "day", value->len) == 0) {
1276 rra_num = rra_day;
1277 }
1278 else if (value->len == 4 && rspamd_lc_cmp (value->begin, "week", value->len) == 0) {
1279 rra_num = rra_week;
1280 }
1281 else if (value->len == 5 && rspamd_lc_cmp (value->begin, "month", value->len) == 0) {
1282 rra_num = rra_month;
1283 }
1284 else if (value->len == 4 && rspamd_lc_cmp (value->begin, "year", value->len) == 0) {
1285 rra_num = rra_year;
1286 }
1287
1288 g_hash_table_unref (query);
1289
1290 if (rra_num == rra_invalid) {
1291 msg_err_session ("invalid graph type query");
1292 rspamd_controller_send_error (conn_ent, 400, "Invalid graph type");
1293
1294 return 0;
1295 }
1296
1297 rrd_result = rspamd_rrd_query (ctx->rrd, rra_num);
1298
1299 if (rrd_result == NULL) {
1300 msg_err_session ("cannot query rrd");
1301 rspamd_controller_send_error (conn_ent, 500, "Cannot query rrd");
1302
1303 return 0;
1304 }
1305
1306 g_assert (rrd_result->ds_count == G_N_ELEMENTS (elt));
1307
1308 res = ucl_object_typed_new (UCL_ARRAY);
1309 /* How much full updates happened since the last update */
1310 ts = rrd_result->last_update / rrd_result->pdp_per_cdp - rrd_result->rra_rows;
1311
1312 for (i = 0; i < rrd_result->ds_count; i ++) {
1313 elt[i] = ucl_object_typed_new (UCL_ARRAY);
1314 }
1315
1316 start_row = rrd_result->cur_row == rrd_result->rra_rows - 1 ?
1317 0 : rrd_result->cur_row;
1318 t = ts * rrd_result->pdp_per_cdp;
1319 k = 0;
1320
1321 /* Create window */
1322 step = ceil (((gdouble)rrd_result->rra_rows) / desired_points);
1323 g_assert (step >= 1);
1324 acc = g_malloc0 (sizeof (double) * rrd_result->ds_count * step);
1325
1326 for (i = start_row, cnt = 0; cnt < rrd_result->rra_rows;
1327 cnt ++) {
1328
1329 memcpy (&acc[k * rrd_result->ds_count],
1330 &rrd_result->data[i * rrd_result->ds_count],
1331 sizeof (gdouble) * rrd_result->ds_count);
1332
1333 if (k < step - 1) {
1334 k ++;
1335 }
1336 else {
1337 t = ts * rrd_result->pdp_per_cdp;
1338
1339 /* Need a fresh point */
1340 rspamd_controller_graph_point (t, step, rrd_result, acc, elt);
1341 k = 0;
1342 }
1343
1344 if (i == rrd_result->rra_rows - 1) {
1345 i = 0;
1346 }
1347 else {
1348 i ++;
1349 }
1350
1351 ts ++;
1352 }
1353
1354 if (k > 0) {
1355 rspamd_controller_graph_point (t, k, rrd_result, acc, elt);
1356 }
1357
1358 for (i = 0; i < rrd_result->ds_count; i++) {
1359 ucl_array_append (res, elt[i]);
1360 }
1361
1362 rspamd_controller_send_ucl (conn_ent, res);
1363 ucl_object_unref (res);
1364 g_free (acc);
1365 g_free (rrd_result);
1366
1367 return 0;
1368 }
1369
1370 static void
rspamd_controller_handle_legacy_history(struct rspamd_controller_session * session,struct rspamd_controller_worker_ctx * ctx,struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1371 rspamd_controller_handle_legacy_history (
1372 struct rspamd_controller_session *session,
1373 struct rspamd_controller_worker_ctx *ctx,
1374 struct rspamd_http_connection_entry *conn_ent,
1375 struct rspamd_http_message *msg)
1376 {
1377 struct roll_history_row *row, *copied_rows;
1378 guint i, rows_proc, row_num;
1379 struct tm tm;
1380 gchar timebuf[32], **syms;
1381 ucl_object_t *top, *obj;
1382
1383 top = ucl_object_typed_new (UCL_ARRAY);
1384
1385 /* Set lock on history */
1386 copied_rows = g_malloc (sizeof (*copied_rows) * ctx->srv->history->nrows);
1387 memcpy (copied_rows, ctx->srv->history->rows,
1388 sizeof (*copied_rows) * ctx->srv->history->nrows);
1389
1390 /* Go through all rows */
1391 row_num = ctx->srv->history->cur_row;
1392
1393 for (i = 0, rows_proc = 0; i < ctx->srv->history->nrows; i++, row_num++) {
1394 if (row_num == ctx->srv->history->nrows) {
1395 row_num = 0;
1396 }
1397 row = &copied_rows[row_num];
1398 /* Get only completed rows */
1399 if (row->completed) {
1400 rspamd_localtime (row->timestamp, &tm);
1401 strftime (timebuf, sizeof (timebuf) - 1, "%Y-%m-%d %H:%M:%S", &tm);
1402 obj = ucl_object_typed_new (UCL_OBJECT);
1403 ucl_object_insert_key (obj, ucl_object_fromstring (
1404 timebuf), "time", 0, false);
1405 ucl_object_insert_key (obj, ucl_object_fromint (
1406 row->timestamp), "unix_time", 0, false);
1407 ucl_object_insert_key (obj, ucl_object_fromstring (
1408 row->message_id), "id", 0, false);
1409 ucl_object_insert_key (obj, ucl_object_fromstring (row->from_addr),
1410 "ip", 0, false);
1411 ucl_object_insert_key (obj,
1412 ucl_object_fromstring (rspamd_action_to_str (
1413 row->action)), "action", 0, false);
1414
1415 if (!isnan (row->score)) {
1416 ucl_object_insert_key (obj, ucl_object_fromdouble (
1417 row->score), "score", 0, false);
1418 }
1419 else {
1420 ucl_object_insert_key (obj,
1421 ucl_object_fromdouble (0.0), "score", 0, false);
1422 }
1423
1424 if (!isnan (row->required_score)) {
1425 ucl_object_insert_key (obj,
1426 ucl_object_fromdouble (
1427 row->required_score), "required_score", 0, false);
1428 }
1429 else {
1430 ucl_object_insert_key (obj,
1431 ucl_object_fromdouble (0.0), "required_score", 0, false);
1432 }
1433
1434 syms = g_strsplit_set (row->symbols, ", ", -1);
1435
1436 if (syms) {
1437 guint nelts = g_strv_length (syms);
1438 ucl_object_t *syms_obj = ucl_object_typed_new (UCL_OBJECT);
1439 ucl_object_reserve (syms_obj, nelts);
1440
1441 for (guint j = 0; j < nelts; j++) {
1442 g_strstrip (syms[j]);
1443
1444 if (strlen (syms[j]) == 0) {
1445 /* Empty garbadge */
1446 continue;
1447 }
1448
1449 ucl_object_t *cur = ucl_object_typed_new (UCL_OBJECT);
1450
1451 ucl_object_insert_key (cur, ucl_object_fromdouble (0.0),
1452 "score", 0, false);
1453 ucl_object_insert_key (syms_obj, cur, syms[j], 0, true);
1454 }
1455
1456 ucl_object_insert_key (obj, syms_obj, "symbols", 0, false);
1457 g_strfreev (syms);
1458 }
1459
1460 ucl_object_insert_key (obj, ucl_object_fromint (row->len),
1461 "size", 0, false);
1462 ucl_object_insert_key (obj,
1463 ucl_object_fromdouble (row->scan_time),
1464 "scan_time", 0, false);
1465
1466 if (row->user[0] != '\0') {
1467 ucl_object_insert_key (obj, ucl_object_fromstring (row->user),
1468 "user", 0, false);
1469 }
1470 if (row->from_addr[0] != '\0') {
1471 ucl_object_insert_key (obj, ucl_object_fromstring (
1472 row->from_addr), "from", 0, false);
1473 }
1474 ucl_array_append (top, obj);
1475 rows_proc++;
1476 }
1477 }
1478
1479 rspamd_controller_send_ucl (conn_ent, top);
1480 ucl_object_unref (top);
1481 g_free (copied_rows);
1482 }
1483
1484 static gboolean
rspamd_controller_history_lua_fin_task(void * ud)1485 rspamd_controller_history_lua_fin_task (void *ud)
1486 {
1487 return TRUE;
1488 }
1489
1490 static void
rspamd_controller_handle_lua_history(lua_State * L,struct rspamd_controller_session * session,struct rspamd_controller_worker_ctx * ctx,struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg,gboolean reset)1491 rspamd_controller_handle_lua_history (lua_State *L,
1492 struct rspamd_controller_session *session,
1493 struct rspamd_controller_worker_ctx *ctx,
1494 struct rspamd_http_connection_entry *conn_ent,
1495 struct rspamd_http_message *msg,
1496 gboolean reset)
1497 {
1498 struct rspamd_task *task, **ptask;
1499 struct rspamd_http_connection_entry **pconn_ent;
1500 GHashTable *params;
1501 rspamd_ftok_t srch, *found;
1502 glong from = 0, to = -1;
1503
1504 params = rspamd_http_message_parse_query (msg);
1505
1506 if (params) {
1507 /* Check from and to */
1508 RSPAMD_FTOK_ASSIGN (&srch, "from");
1509 found = g_hash_table_lookup (params, &srch);
1510
1511 if (found) {
1512 rspamd_strtol (found->begin, found->len, &from);
1513 }
1514 RSPAMD_FTOK_ASSIGN (&srch, "to");
1515 found = g_hash_table_lookup (params, &srch);
1516
1517 if (found) {
1518 rspamd_strtol (found->begin, found->len, &to);
1519 }
1520
1521 g_hash_table_unref (params);
1522 }
1523
1524 lua_getglobal (L, "rspamd_plugins");
1525
1526 if (lua_istable (L, -1)) {
1527 lua_pushstring (L, "history");
1528 lua_gettable (L, -2);
1529
1530 if (lua_istable (L, -1)) {
1531 lua_pushstring (L, "handler");
1532 lua_gettable (L, -2);
1533
1534 if (lua_isfunction (L, -1)) {
1535 task = rspamd_task_new (session->ctx->worker, session->cfg,
1536 session->pool, ctx->lang_det, ctx->event_loop, FALSE);
1537
1538 task->resolver = ctx->resolver;
1539 task->s = rspamd_session_create (session->pool,
1540 rspamd_controller_history_lua_fin_task,
1541 NULL,
1542 (event_finalizer_t )rspamd_task_free,
1543 task);
1544 task->fin_arg = conn_ent;
1545
1546 ptask = lua_newuserdata (L, sizeof (*ptask));
1547 *ptask = task;
1548 rspamd_lua_setclass (L, "rspamd{task}", -1);
1549 pconn_ent = lua_newuserdata (L, sizeof (*pconn_ent));
1550 *pconn_ent = conn_ent;
1551 rspamd_lua_setclass (L, "rspamd{csession}", -1);
1552 lua_pushinteger (L, from);
1553 lua_pushinteger (L, to);
1554 lua_pushboolean (L, reset);
1555
1556 if (lua_pcall (L, 5, 0, 0) != 0) {
1557 msg_err_session ("call to history function failed: %s",
1558 lua_tostring (L, -1));
1559 lua_settop (L, 0);
1560 rspamd_task_free (task);
1561
1562 goto err;
1563 }
1564
1565 task->http_conn = rspamd_http_connection_ref (conn_ent->conn);;
1566 task->sock = -1;
1567 session->task = task;
1568
1569 rspamd_session_pending (task->s);
1570 }
1571 else {
1572 msg_err_session ("rspamd_plugins.history.handler is not a function");
1573 lua_settop (L, 0);
1574 goto err;
1575 }
1576 }
1577 else {
1578 msg_err_session ("rspamd_plugins.history is not a table");
1579 lua_settop (L, 0);
1580 goto err;
1581 }
1582 }
1583 else {
1584 msg_err_session ("rspamd_plugins is absent or has incorrect type");
1585 lua_settop (L, 0);
1586 goto err;
1587 }
1588
1589 lua_settop (L, 0);
1590
1591 return;
1592 err:
1593 rspamd_controller_send_error (conn_ent, 500, "Internal error");
1594 }
1595
1596 /*
1597 * Healthy command handler:
1598 * request: /healthy
1599 * headers: Password
1600 * reply: json {"success":true}
1601 */
1602 static int
rspamd_controller_handle_healthy(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1603 rspamd_controller_handle_healthy (struct rspamd_http_connection_entry *conn_ent,
1604 struct rspamd_http_message *msg)
1605 {
1606 struct rspamd_controller_session *session = conn_ent->ud;
1607
1608 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
1609 return 0;
1610 }
1611
1612 if (session->ctx->workers_hb_lost != 0) {
1613 rspamd_controller_send_error (conn_ent, 500,
1614 "%d workers are not responding", session->ctx->workers_hb_lost);
1615 }
1616 else {
1617 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
1618 }
1619
1620 return 0;
1621 }
1622
1623 /*
1624 * Ready command handler:
1625 * request: /ready
1626 * headers: Password
1627 * reply: json {"success":true} or {"error":"error message"}
1628 */
1629 static int
rspamd_controller_handle_ready(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1630 rspamd_controller_handle_ready (struct rspamd_http_connection_entry *conn_ent,
1631 struct rspamd_http_message *msg)
1632 {
1633 struct rspamd_controller_session *session = conn_ent->ud;
1634
1635 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
1636 return 0;
1637 }
1638
1639 if (session->ctx->scanners_count == 0) {
1640 rspamd_controller_send_error (conn_ent, 500, "no healthy scanner workers are running");
1641 }
1642 else {
1643 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
1644 }
1645
1646 return 0;
1647 }
1648
1649 /*
1650 * History command handler:
1651 * request: /history
1652 * headers: Password
1653 * reply: json [
1654 * { label: "Foo", data: 11 },
1655 * { label: "Bar", data: 20 },
1656 * {...}
1657 * ]
1658 */
1659 static int
rspamd_controller_handle_history(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1660 rspamd_controller_handle_history (struct rspamd_http_connection_entry *conn_ent,
1661 struct rspamd_http_message *msg)
1662 {
1663 struct rspamd_controller_session *session = conn_ent->ud;
1664 struct rspamd_controller_worker_ctx *ctx;
1665 lua_State *L;
1666 ctx = session->ctx;
1667
1668 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
1669 return 0;
1670 }
1671
1672 L = ctx->cfg->lua_state;
1673
1674 if (!ctx->srv->history->disabled) {
1675 rspamd_controller_handle_legacy_history (session, ctx, conn_ent, msg);
1676 }
1677 else {
1678 rspamd_controller_handle_lua_history (L, session, ctx, conn_ent, msg,
1679 FALSE);
1680 }
1681
1682 return 0;
1683 }
1684
1685 /*
1686 * Errors command handler:
1687 * request: /errors
1688 * headers: Password
1689 * reply: json [
1690 * { ts: 100500, type: normal, pid: 100, module: lua, message: bad things },
1691 * {...}
1692 * ]
1693 */
1694 static int
rspamd_controller_handle_errors(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1695 rspamd_controller_handle_errors (struct rspamd_http_connection_entry *conn_ent,
1696 struct rspamd_http_message *msg)
1697 {
1698 struct rspamd_controller_session *session = conn_ent->ud;
1699 struct rspamd_controller_worker_ctx *ctx;
1700 ucl_object_t *top;
1701
1702 ctx = session->ctx;
1703
1704 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
1705 return 0;
1706 }
1707
1708 top = rspamd_log_errorbuf_export (ctx->worker->srv->logger);
1709 rspamd_controller_send_ucl (conn_ent, top);
1710 ucl_object_unref (top);
1711
1712 return 0;
1713 }
1714
1715 /*
1716 * Neighbours command handler:
1717 * request: /neighbours
1718 * headers: Password
1719 * reply: json {name: {url: "http://...", host: "host"}}
1720 */
1721 static int
rspamd_controller_handle_neighbours(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1722 rspamd_controller_handle_neighbours (struct rspamd_http_connection_entry *conn_ent,
1723 struct rspamd_http_message *msg)
1724 {
1725 struct rspamd_controller_session *session = conn_ent->ud;
1726 struct rspamd_controller_worker_ctx *ctx;
1727
1728 ctx = session->ctx;
1729
1730 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
1731 return 0;
1732 }
1733
1734 rspamd_controller_send_ucl (conn_ent, ctx->cfg->neighbours);
1735
1736 return 0;
1737 }
1738
1739
1740 static int
rspamd_controller_handle_history_reset(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1741 rspamd_controller_handle_history_reset (struct rspamd_http_connection_entry *conn_ent,
1742 struct rspamd_http_message *msg)
1743 {
1744 struct rspamd_controller_session *session = conn_ent->ud;
1745 struct rspamd_controller_worker_ctx *ctx;
1746 struct roll_history_row *row;
1747 guint completed_rows, i, t;
1748 lua_State *L;
1749
1750 ctx = session->ctx;
1751 L = ctx->cfg->lua_state;
1752
1753 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
1754 return 0;
1755 }
1756
1757 if (!ctx->srv->history->disabled) {
1758 /* Clean from start to the current row */
1759 completed_rows = g_atomic_int_get (&ctx->srv->history->cur_row);
1760
1761 completed_rows = MIN (completed_rows, ctx->srv->history->nrows - 1);
1762
1763 for (i = 0; i <= completed_rows; i ++) {
1764 t = g_atomic_int_get (&ctx->srv->history->cur_row);
1765
1766 /* We somehow come to the race condition */
1767 if (i > t) {
1768 break;
1769 }
1770
1771 row = &ctx->srv->history->rows[i];
1772 memset (row, 0, sizeof (*row));
1773 }
1774
1775 msg_info_session ("<%s> cleared %d entries from history",
1776 rspamd_inet_address_to_string (session->from_addr),
1777 completed_rows);
1778 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
1779 }
1780 else {
1781 rspamd_controller_handle_lua_history (L, session, ctx, conn_ent, msg,
1782 TRUE);
1783 }
1784
1785 return 0;
1786 }
1787
1788 static gboolean
rspamd_controller_lua_fin_task(void * ud)1789 rspamd_controller_lua_fin_task (void *ud)
1790 {
1791 struct rspamd_task *task = ud;
1792 struct rspamd_http_connection_entry *conn_ent;
1793
1794 conn_ent = task->fin_arg;
1795
1796 if (task->err != NULL) {
1797 rspamd_controller_send_error (conn_ent, task->err->code, "%s",
1798 task->err->message);
1799 }
1800
1801 return TRUE;
1802 }
1803
1804 static int
rspamd_controller_handle_lua(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)1805 rspamd_controller_handle_lua (struct rspamd_http_connection_entry *conn_ent,
1806 struct rspamd_http_message *msg)
1807 {
1808 struct rspamd_controller_session *session = conn_ent->ud;
1809 struct rspamd_task *task, **ptask;
1810 struct rspamd_http_connection_entry **pconn;
1811 struct rspamd_controller_worker_ctx *ctx;
1812 gchar filebuf[PATH_MAX], realbuf[PATH_MAX];
1813 struct http_parser_url u;
1814 rspamd_ftok_t lookup;
1815 struct stat st;
1816 lua_State *L;
1817
1818 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
1819 return 0;
1820 }
1821
1822 ctx = session->ctx;
1823 L = ctx->cfg->lua_state;
1824
1825 /* Find lua script */
1826 if (msg->url != NULL && msg->url->len != 0) {
1827
1828 http_parser_parse_url (RSPAMD_FSTRING_DATA (msg->url),
1829 RSPAMD_FSTRING_LEN (msg->url), TRUE, &u);
1830
1831 if (u.field_set & (1 << UF_PATH)) {
1832 lookup.begin = RSPAMD_FSTRING_DATA (msg->url) +
1833 u.field_data[UF_PATH].off;
1834 lookup.len = u.field_data[UF_PATH].len;
1835 }
1836 else {
1837 lookup.begin = RSPAMD_FSTRING_DATA (msg->url);
1838 lookup.len = RSPAMD_FSTRING_LEN (msg->url);
1839 }
1840
1841 rspamd_snprintf (filebuf, sizeof (filebuf), "%s%c%T",
1842 ctx->static_files_dir, G_DIR_SEPARATOR, &lookup);
1843
1844 if (realpath (filebuf, realbuf) == NULL ||
1845 lstat (realbuf, &st) == -1) {
1846 rspamd_controller_send_error (conn_ent, 404, "Cannot find path: %s",
1847 strerror (errno));
1848
1849 return 0;
1850 }
1851
1852 /* TODO: add caching here, should be trivial */
1853 /* Now we load and execute the code fragment, which should return a function */
1854 if (luaL_loadfile (L, realbuf) != 0) {
1855 rspamd_controller_send_error (conn_ent, 500, "Cannot load path: %s",
1856 lua_tostring (L, -1));
1857 lua_settop (L, 0);
1858
1859 return 0;
1860 }
1861
1862 if (lua_pcall (L, 0, 1, 0) != 0) {
1863 rspamd_controller_send_error (conn_ent, 501, "Cannot run path: %s",
1864 lua_tostring (L, -1));
1865 lua_settop (L, 0);
1866
1867 return 0;
1868 }
1869
1870 if (lua_type (L, -1) != LUA_TFUNCTION) {
1871 rspamd_controller_send_error (conn_ent, 502, "Bad return type: %s",
1872 lua_typename (L, lua_type (L, -1)));
1873 lua_settop (L, 0);
1874
1875 return 0;
1876 }
1877
1878 }
1879 else {
1880 rspamd_controller_send_error (conn_ent, 404, "Empty path is not permitted");
1881
1882 return 0;
1883 }
1884
1885 task = rspamd_task_new (session->ctx->worker, session->cfg, session->pool,
1886 ctx->lang_det, ctx->event_loop, FALSE);
1887
1888 task->resolver = ctx->resolver;
1889 task->s = rspamd_session_create (session->pool,
1890 rspamd_controller_lua_fin_task,
1891 NULL,
1892 (event_finalizer_t )rspamd_task_free,
1893 task);
1894 task->fin_arg = conn_ent;
1895 task->http_conn = rspamd_http_connection_ref (conn_ent->conn);;
1896 task->sock = -1;
1897 session->task = task;
1898
1899 if (msg->body_buf.len > 0) {
1900 if (!rspamd_task_load_message (task, msg, msg->body_buf.begin, msg->body_buf.len)) {
1901 rspamd_controller_send_error (conn_ent, task->err->code, "%s",
1902 task->err->message);
1903 return 0;
1904 }
1905 }
1906
1907 ptask = lua_newuserdata (L, sizeof (*ptask));
1908 rspamd_lua_setclass (L, "rspamd{task}", -1);
1909 *ptask = task;
1910
1911 pconn = lua_newuserdata (L, sizeof (*pconn));
1912 rspamd_lua_setclass (L, "rspamd{csession}", -1);
1913 *pconn = conn_ent;
1914
1915 if (lua_pcall (L, 2, 0, 0) != 0) {
1916 rspamd_controller_send_error (conn_ent, 503, "Cannot run callback: %s",
1917 lua_tostring (L, -1));
1918 lua_settop (L, 0);
1919
1920 return 0;
1921 }
1922
1923 rspamd_session_pending (task->s);
1924
1925 return 0;
1926 }
1927
1928 static gboolean
rspamd_controller_learn_fin_task(void * ud)1929 rspamd_controller_learn_fin_task (void *ud)
1930 {
1931 struct rspamd_task *task = ud;
1932 struct rspamd_controller_session *session;
1933 struct rspamd_http_connection_entry *conn_ent;
1934
1935 conn_ent = task->fin_arg;
1936 session = conn_ent->ud;
1937
1938 if (task->err != NULL) {
1939 msg_info_session ("cannot learn <%s>: %e",
1940 MESSAGE_FIELD (task, message_id), task->err);
1941 rspamd_controller_send_error (conn_ent, task->err->code, "%s",
1942 task->err->message);
1943
1944 return TRUE;
1945 }
1946
1947 if (RSPAMD_TASK_IS_PROCESSED (task)) {
1948 /* Successful learn */
1949 msg_info_task ("<%s> learned message as %s: %s",
1950 rspamd_inet_address_to_string (session->from_addr),
1951 session->is_spam ? "spam" : "ham",
1952 MESSAGE_FIELD (task, message_id));
1953 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
1954 return TRUE;
1955 }
1956
1957 if (!rspamd_task_process (task, RSPAMD_TASK_PROCESS_LEARN)) {
1958 msg_info_task ("cannot learn <%s>: %e",
1959 MESSAGE_FIELD (task, message_id), task->err);
1960
1961 if (task->err) {
1962 rspamd_controller_send_error (conn_ent, task->err->code, "%s",
1963 task->err->message);
1964 }
1965 else {
1966 rspamd_controller_send_error (conn_ent, 500,
1967 "Internal error");
1968 }
1969 }
1970
1971 if (RSPAMD_TASK_IS_PROCESSED (task)) {
1972 if (task->err) {
1973 rspamd_controller_send_error (conn_ent, task->err->code, "%s",
1974 task->err->message);
1975 }
1976 else {
1977 msg_info_task ("<%s> learned message as %s: %s",
1978 rspamd_inet_address_to_string (session->from_addr),
1979 session->is_spam ? "spam" : "ham",
1980 MESSAGE_FIELD (task, message_id));
1981 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
1982 }
1983
1984 return TRUE;
1985 }
1986
1987 /* One more iteration */
1988 return FALSE;
1989 }
1990
1991 static void
rspamd_controller_scan_reply(struct rspamd_task * task)1992 rspamd_controller_scan_reply (struct rspamd_task *task)
1993 {
1994 struct rspamd_http_message *msg;
1995 struct rspamd_http_connection_entry *conn_ent;
1996
1997 conn_ent = task->fin_arg;
1998 msg = rspamd_http_new_message (HTTP_RESPONSE);
1999 msg->date = time (NULL);
2000 msg->code = 200;
2001 rspamd_protocol_http_reply (msg, task, NULL);
2002 rspamd_http_connection_reset (conn_ent->conn);
2003 rspamd_http_router_insert_headers (conn_ent->rt, msg);
2004 rspamd_http_connection_write_message (conn_ent->conn, msg, NULL,
2005 "application/json", conn_ent, conn_ent->rt->timeout);
2006 conn_ent->is_reply = TRUE;
2007 }
2008
2009 static gboolean
rspamd_controller_check_fin_task(void * ud)2010 rspamd_controller_check_fin_task (void *ud)
2011 {
2012 struct rspamd_task *task = ud;
2013 struct rspamd_http_connection_entry *conn_ent;
2014
2015 msg_debug_task ("finish task");
2016 conn_ent = task->fin_arg;
2017
2018 if (task->err) {
2019 msg_info_task ("cannot check <%s>: %e",
2020 MESSAGE_FIELD_CHECK (task, message_id), task->err);
2021 rspamd_controller_send_error (conn_ent, task->err->code, "%s",
2022 task->err->message);
2023 return TRUE;
2024 }
2025
2026 if (RSPAMD_TASK_IS_PROCESSED (task)) {
2027 rspamd_controller_scan_reply (task);
2028 return TRUE;
2029 }
2030
2031 if (!rspamd_task_process (task, RSPAMD_TASK_PROCESS_ALL)) {
2032 rspamd_controller_scan_reply (task);
2033 return TRUE;
2034 }
2035
2036 if (RSPAMD_TASK_IS_PROCESSED (task)) {
2037 rspamd_controller_scan_reply (task);
2038 return TRUE;
2039 }
2040
2041 /* One more iteration */
2042 return FALSE;
2043 }
2044
2045 static int
rspamd_controller_handle_learn_common(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg,gboolean is_spam)2046 rspamd_controller_handle_learn_common (
2047 struct rspamd_http_connection_entry *conn_ent,
2048 struct rspamd_http_message *msg,
2049 gboolean is_spam)
2050 {
2051 struct rspamd_controller_session *session = conn_ent->ud;
2052 struct rspamd_controller_worker_ctx *ctx;
2053 struct rspamd_task *task;
2054 const rspamd_ftok_t *cl_header;
2055
2056 ctx = session->ctx;
2057
2058 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
2059 return 0;
2060 }
2061
2062 if (rspamd_http_message_get_body (msg, NULL) == NULL) {
2063 msg_err_session ("got zero length body, cannot continue");
2064 rspamd_controller_send_error (conn_ent,
2065 400,
2066 "Empty body is not permitted");
2067 return 0;
2068 }
2069
2070 task = rspamd_task_new (session->ctx->worker, session->cfg, session->pool,
2071 session->ctx->lang_det, ctx->event_loop, FALSE);
2072
2073 task->resolver = ctx->resolver;
2074 task->s = rspamd_session_create (session->pool,
2075 rspamd_controller_learn_fin_task,
2076 NULL,
2077 (event_finalizer_t )rspamd_task_free,
2078 task);
2079 task->fin_arg = conn_ent;
2080 task->http_conn = rspamd_http_connection_ref (conn_ent->conn);;
2081 task->sock = -1;
2082 session->task = task;
2083
2084 cl_header = rspamd_http_message_find_header (msg, "classifier");
2085 if (cl_header) {
2086 session->classifier = rspamd_mempool_ftokdup (session->pool, cl_header);
2087 }
2088 else {
2089 session->classifier = NULL;
2090 }
2091
2092 if (!rspamd_task_load_message (task, msg, msg->body_buf.begin, msg->body_buf.len)) {
2093 goto end;
2094 }
2095
2096 rspamd_learn_task_spam (task, is_spam, session->classifier, NULL);
2097
2098 if (!rspamd_task_process (task, RSPAMD_TASK_PROCESS_LEARN)) {
2099 msg_warn_session ("<%s> message cannot be processed",
2100 MESSAGE_FIELD (task, message_id));
2101 goto end;
2102 }
2103
2104 end:
2105 session->is_spam = is_spam;
2106 rspamd_session_pending (task->s);
2107
2108 return 0;
2109 }
2110
2111 /*
2112 * Learn spam command handler:
2113 * request: /learnspam
2114 * headers: Password
2115 * input: plaintext data
2116 * reply: json {"success":true} or {"error":"error message"}
2117 */
2118 static int
rspamd_controller_handle_learnspam(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2119 rspamd_controller_handle_learnspam (
2120 struct rspamd_http_connection_entry *conn_ent,
2121 struct rspamd_http_message *msg)
2122 {
2123 return rspamd_controller_handle_learn_common (conn_ent, msg, TRUE);
2124 }
2125 /*
2126 * Learn ham command handler:
2127 * request: /learnham
2128 * headers: Password
2129 * input: plaintext data
2130 * reply: json {"success":true} or {"error":"error message"}
2131 */
2132 static int
rspamd_controller_handle_learnham(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2133 rspamd_controller_handle_learnham (
2134 struct rspamd_http_connection_entry *conn_ent,
2135 struct rspamd_http_message *msg)
2136 {
2137 return rspamd_controller_handle_learn_common (conn_ent, msg, FALSE);
2138 }
2139
2140 /*
2141 * Scan command handler:
2142 * request: /scan
2143 * headers: Password
2144 * input: plaintext data
2145 * reply: json {scan data} or {"error":"error message"}
2146 */
2147 static int
rspamd_controller_handle_scan(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2148 rspamd_controller_handle_scan (struct rspamd_http_connection_entry *conn_ent,
2149 struct rspamd_http_message *msg)
2150 {
2151 struct rspamd_controller_session *session = conn_ent->ud;
2152 struct rspamd_controller_worker_ctx *ctx;
2153 struct rspamd_task *task;
2154
2155 ctx = session->ctx;
2156
2157 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
2158 return 0;
2159 }
2160
2161 task = rspamd_task_new (session->ctx->worker, session->cfg, session->pool,
2162 ctx->lang_det, ctx->event_loop, FALSE);
2163
2164 task->resolver = ctx->resolver;
2165 task->s = rspamd_session_create (session->pool,
2166 rspamd_controller_check_fin_task,
2167 NULL,
2168 (event_finalizer_t )rspamd_task_free,
2169 task);
2170 task->fin_arg = conn_ent;
2171 task->http_conn = rspamd_http_connection_ref (conn_ent->conn);
2172 task->sock = conn_ent->conn->fd;
2173 task->flags |= RSPAMD_TASK_FLAG_MIME;
2174 task->resolver = ctx->resolver;
2175
2176 if (!rspamd_protocol_handle_request (task, msg)) {
2177 goto end;
2178 }
2179
2180 if (!rspamd_task_load_message (task, msg, msg->body_buf.begin, msg->body_buf.len)) {
2181 goto end;
2182 }
2183
2184 if (!rspamd_task_process (task, RSPAMD_TASK_PROCESS_ALL)) {
2185 goto end;
2186 }
2187
2188 if (ctx->task_timeout > 0.0) {
2189 task->timeout_ev.data = task;
2190 ev_timer_init (&task->timeout_ev, rspamd_task_timeout,
2191 ctx->task_timeout, ctx->task_timeout);
2192 ev_timer_start (task->event_loop, &task->timeout_ev);
2193 ev_set_priority (&task->timeout_ev, EV_MAXPRI);
2194 }
2195
2196 end:
2197 session->task = task;
2198 rspamd_session_pending (task->s);
2199
2200 return 0;
2201 }
2202
2203 /*
2204 * Save actions command handler:
2205 * request: /saveactions
2206 * headers: Password
2207 * input: json array [<spam>,<probable spam>,<greylist>]
2208 * reply: json {"success":true} or {"error":"error message"}
2209 */
2210 static int
rspamd_controller_handle_saveactions(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2211 rspamd_controller_handle_saveactions (
2212 struct rspamd_http_connection_entry *conn_ent,
2213 struct rspamd_http_message *msg)
2214 {
2215 struct rspamd_controller_session *session = conn_ent->ud;
2216 struct ucl_parser *parser;
2217 ucl_object_t *obj;
2218 const ucl_object_t *cur;
2219 struct rspamd_controller_worker_ctx *ctx;
2220 const gchar *error;
2221 gdouble score;
2222 gint i, added = 0;
2223 enum rspamd_action_type act;
2224 ucl_object_iter_t it = NULL;
2225
2226 ctx = session->ctx;
2227
2228 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
2229 return 0;
2230 }
2231
2232 if (rspamd_http_message_get_body (msg, NULL) == NULL) {
2233 msg_err_session ("got zero length body, cannot continue");
2234 rspamd_controller_send_error (conn_ent,
2235 400,
2236 "Empty body is not permitted");
2237 return 0;
2238 }
2239
2240 parser = ucl_parser_new (0);
2241 if (!ucl_parser_add_chunk (parser, msg->body_buf.begin, msg->body_buf.len)) {
2242 if ((error = ucl_parser_get_error (parser)) != NULL) {
2243 msg_err_session ("cannot parse input: %s", error);
2244 rspamd_controller_send_error (conn_ent, 400, "Cannot parse input");
2245 ucl_parser_free (parser);
2246 return 0;
2247 }
2248
2249 msg_err_session ("cannot parse input: unknown error");
2250 rspamd_controller_send_error (conn_ent, 400, "Cannot parse input");
2251 ucl_parser_free (parser);
2252 return 0;
2253 }
2254
2255 obj = ucl_parser_get_object (parser);
2256 ucl_parser_free (parser);
2257
2258 if (obj->type != UCL_ARRAY || obj->len != 4) {
2259 msg_err_session ("input is not an array of 4 elements");
2260 rspamd_controller_send_error (conn_ent, 400, "Cannot parse input");
2261 ucl_object_unref (obj);
2262 return 0;
2263 }
2264
2265 it = ucl_object_iterate_new (obj);
2266
2267 for (i = 0; i < 4; i++) {
2268 cur = ucl_object_iterate_safe (it, TRUE);
2269
2270 switch (i) {
2271 case 0:
2272 default:
2273 act = METRIC_ACTION_REJECT;
2274 break;
2275 case 1:
2276 act = METRIC_ACTION_REWRITE_SUBJECT;
2277 break;
2278 case 2:
2279 act = METRIC_ACTION_ADD_HEADER;
2280 break;
2281 case 3:
2282 act = METRIC_ACTION_GREYLIST;
2283 break;
2284 }
2285
2286 if (ucl_object_type (cur) == UCL_NULL) {
2287 /* Assume NaN */
2288 score = NAN;
2289 }
2290 else {
2291 score = ucl_object_todouble (cur);
2292 }
2293
2294 if ((isnan (session->cfg->actions[act].threshold) != isnan (score)) ||
2295 (session->cfg->actions[act].threshold != score)) {
2296 add_dynamic_action (ctx->cfg, DEFAULT_METRIC, act, score);
2297 added ++;
2298 }
2299 else {
2300 if (remove_dynamic_action (ctx->cfg, DEFAULT_METRIC, act)) {
2301 added ++;
2302 }
2303 }
2304 }
2305
2306 ucl_object_iterate_free (it);
2307
2308 if (dump_dynamic_config (ctx->cfg)) {
2309 msg_info_session ("<%s> modified %d actions",
2310 rspamd_inet_address_to_string (session->from_addr),
2311 added);
2312
2313 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
2314 }
2315 else {
2316 rspamd_controller_send_error (conn_ent, 500, "Save error");
2317 }
2318
2319 ucl_object_unref (obj);
2320
2321 return 0;
2322 }
2323
2324 /*
2325 * Save symbols command handler:
2326 * request: /savesymbols
2327 * headers: Password
2328 * input: json data
2329 * reply: json {"success":true} or {"error":"error message"}
2330 */
2331 static int
rspamd_controller_handle_savesymbols(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2332 rspamd_controller_handle_savesymbols (
2333 struct rspamd_http_connection_entry *conn_ent,
2334 struct rspamd_http_message *msg)
2335 {
2336 struct rspamd_controller_session *session = conn_ent->ud;
2337 struct ucl_parser *parser;
2338 ucl_object_t *obj;
2339 const ucl_object_t *cur, *jname, *jvalue;
2340 ucl_object_iter_t iter = NULL;
2341 struct rspamd_controller_worker_ctx *ctx;
2342 const gchar *error;
2343 gdouble val;
2344 struct rspamd_symbol *sym;
2345 int added = 0;
2346
2347 ctx = session->ctx;
2348
2349 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
2350 return 0;
2351 }
2352
2353 if (rspamd_http_message_get_body (msg, NULL) == NULL) {
2354 msg_err_session ("got zero length body, cannot continue");
2355 rspamd_controller_send_error (conn_ent,
2356 400,
2357 "Empty body is not permitted");
2358 return 0;
2359 }
2360
2361 parser = ucl_parser_new (0);
2362 if (!ucl_parser_add_chunk (parser, msg->body_buf.begin, msg->body_buf.len)) {
2363 if ((error = ucl_parser_get_error (parser)) != NULL) {
2364 msg_err_session ("cannot parse input: %s", error);
2365 rspamd_controller_send_error (conn_ent, 400, "Cannot parse input");
2366 ucl_parser_free (parser);
2367 return 0;
2368 }
2369
2370 msg_err_session ("cannot parse input: unknown error");
2371 rspamd_controller_send_error (conn_ent, 400, "Cannot parse input");
2372 ucl_parser_free (parser);
2373 return 0;
2374 }
2375
2376 obj = ucl_parser_get_object (parser);
2377 ucl_parser_free (parser);
2378
2379 if (obj->type != UCL_ARRAY) {
2380 msg_err_session ("input is not an array");
2381 rspamd_controller_send_error (conn_ent, 400, "Cannot parse input");
2382 ucl_object_unref (obj);
2383 return 0;
2384 }
2385
2386 iter = ucl_object_iterate_new (obj);
2387
2388 while ((cur = ucl_object_iterate_safe (iter, true))) {
2389 if (cur->type != UCL_OBJECT) {
2390 msg_err_session ("json array data error");
2391 rspamd_controller_send_error (conn_ent, 400, "Cannot parse input");
2392 ucl_object_unref (obj);
2393 ucl_object_iterate_free (iter);
2394
2395 return 0;
2396 }
2397
2398 jname = ucl_object_lookup (cur, "name");
2399 jvalue = ucl_object_lookup (cur, "value");
2400 val = ucl_object_todouble (jvalue);
2401 sym = g_hash_table_lookup (session->cfg->symbols, ucl_object_tostring (jname));
2402
2403 if (sym && fabs (*sym->weight_ptr - val) > 0.01) {
2404 if (!add_dynamic_symbol (ctx->cfg, DEFAULT_METRIC,
2405 ucl_object_tostring (jname), val)) {
2406 msg_err_session ("add symbol failed for %s",
2407 ucl_object_tostring (jname));
2408 rspamd_controller_send_error (conn_ent, 506,
2409 "Add symbol failed");
2410 ucl_object_unref (obj);
2411 ucl_object_iterate_free (iter);
2412
2413 return 0;
2414 }
2415 added ++;
2416 }
2417 else if (sym && ctx->cfg->dynamic_conf) {
2418 if (remove_dynamic_symbol (ctx->cfg, DEFAULT_METRIC,
2419 ucl_object_tostring (jname))) {
2420 added ++;
2421 }
2422 }
2423 }
2424
2425 ucl_object_iterate_free (iter);
2426
2427 if (added > 0) {
2428 if (ctx->cfg->dynamic_conf) {
2429 if (dump_dynamic_config (ctx->cfg)) {
2430 msg_info_session ("<%s> modified %d symbols",
2431 rspamd_inet_address_to_string (session->from_addr),
2432 added);
2433
2434 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
2435 }
2436 else {
2437 rspamd_controller_send_error (conn_ent, 500, "Save error");
2438 }
2439 }
2440 else {
2441 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
2442 }
2443 }
2444 else {
2445 msg_err_session ("no symbols to save");
2446 rspamd_controller_send_error (conn_ent, 404, "No symbols to save");
2447 }
2448
2449 ucl_object_unref (obj);
2450
2451 return 0;
2452 }
2453
2454 /*
2455 * Save map command handler:
2456 * request: /savemap
2457 * headers: Password, Map
2458 * input: plaintext data
2459 * reply: json {"success":true} or {"error":"error message"}
2460 */
2461 static int
rspamd_controller_handle_savemap(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2462 rspamd_controller_handle_savemap (struct rspamd_http_connection_entry *conn_ent,
2463 struct rspamd_http_message *msg)
2464 {
2465 struct rspamd_controller_session *session = conn_ent->ud;
2466 GList *cur;
2467 struct rspamd_map *map = NULL;
2468 struct rspamd_map_backend *bk;
2469 struct rspamd_controller_worker_ctx *ctx;
2470 const rspamd_ftok_t *idstr;
2471 gulong id, i;
2472 gboolean found = FALSE;
2473 gchar tempname[PATH_MAX];
2474 gint fd;
2475
2476 ctx = session->ctx;
2477
2478 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
2479 return 0;
2480 }
2481
2482 if (rspamd_http_message_get_body (msg, NULL) == NULL) {
2483 msg_err_session ("got zero length body, cannot continue");
2484 rspamd_controller_send_error (conn_ent,
2485 400,
2486 "Empty body is not permitted");
2487 return 0;
2488 }
2489
2490 idstr = rspamd_http_message_find_header (msg, "Map");
2491
2492 if (idstr == NULL) {
2493 msg_info_session ("absent map id");
2494 rspamd_controller_send_error (conn_ent, 400, "Map id not specified");
2495 return 0;
2496 }
2497
2498 if (!rspamd_strtoul (idstr->begin, idstr->len, &id)) {
2499 msg_info_session ("invalid map id: %T", idstr);
2500 rspamd_controller_send_error (conn_ent, 400, "Map id is invalid");
2501 return 0;
2502 }
2503
2504 /* Now let's be sure that we have map defined in configuration */
2505 cur = ctx->cfg->maps;
2506 while (cur && !found) {
2507 map = cur->data;
2508
2509 PTR_ARRAY_FOREACH (map->backends, i, bk) {
2510 if (bk->id == id && bk->protocol == MAP_PROTO_FILE) {
2511 found = TRUE;
2512 break;
2513 }
2514 }
2515 cur = g_list_next (cur);
2516 }
2517
2518 if (!found) {
2519 msg_info_session ("map not found: %L", id);
2520 rspamd_controller_send_error (conn_ent, 404, "Map id not found");
2521 return 0;
2522 }
2523
2524 rspamd_snprintf (tempname, sizeof (tempname), "%s.newXXXXXX", bk->uri);
2525 fd = g_mkstemp_full (tempname, O_WRONLY, 00644);
2526
2527 if (fd == -1) {
2528 msg_info_session ("map %s open error: %s", tempname, strerror (errno));
2529 rspamd_controller_send_error (conn_ent, 404,
2530 "Cannot open map: %s",
2531 strerror (errno));
2532 return 0;
2533 }
2534
2535 if (write (fd, msg->body_buf.begin, msg->body_buf.len) == -1) {
2536 msg_info_session ("map %s write error: %s", tempname, strerror (errno));
2537 unlink (tempname);
2538 close (fd);
2539 rspamd_controller_send_error (conn_ent, 500, "Map write error: %s",
2540 strerror (errno));
2541 return 0;
2542 }
2543
2544 /* Rename */
2545 if (rename (tempname, bk->uri) == -1) {
2546 msg_info_session ("map %s rename error: %s", tempname, strerror (errno));
2547 unlink (tempname);
2548 close (fd);
2549 rspamd_controller_send_error (conn_ent, 500, "Map rename error: %s",
2550 strerror (errno));
2551 return 0;
2552 }
2553
2554 msg_info_session ("<%s>, map %s saved",
2555 rspamd_inet_address_to_string (session->from_addr),
2556 bk->uri);
2557 close (fd);
2558
2559 rspamd_controller_send_string (conn_ent, "{\"success\":true}");
2560
2561 return 0;
2562 }
2563
2564 struct rspamd_stat_cbdata {
2565 struct rspamd_http_connection_entry *conn_ent;
2566 ucl_object_t *top;
2567 ucl_object_t *stat;
2568 struct rspamd_task *task;
2569 guint64 learned;
2570 };
2571
2572 static gboolean
rspamd_controller_stat_fin_task(void * ud)2573 rspamd_controller_stat_fin_task (void *ud)
2574 {
2575 struct rspamd_stat_cbdata *cbdata = ud;
2576 struct rspamd_http_connection_entry *conn_ent;
2577 ucl_object_t *top, *ar;
2578 GList *fuzzy_elts, *cur;
2579 struct rspamd_fuzzy_stat_entry *entry;
2580
2581 conn_ent = cbdata->conn_ent;
2582 top = cbdata->top;
2583
2584 ucl_object_insert_key (top,
2585 ucl_object_fromint (cbdata->learned), "total_learns", 0, false);
2586
2587 if (cbdata->stat) {
2588 ucl_object_insert_key (top, cbdata->stat, "statfiles", 0, false);
2589 }
2590
2591 fuzzy_elts = rspamd_mempool_get_variable (cbdata->task->task_pool, "fuzzy_stat");
2592
2593 if (fuzzy_elts) {
2594 ar = ucl_object_typed_new (UCL_OBJECT);
2595
2596 for (cur = fuzzy_elts; cur != NULL; cur = g_list_next (cur)) {
2597 entry = cur->data;
2598
2599 if (entry->name) {
2600 ucl_object_insert_key (ar, ucl_object_fromint (entry->fuzzy_cnt),
2601 entry->name, 0, true);
2602 }
2603 }
2604
2605 ucl_object_insert_key (top, ar, "fuzzy_hashes", 0, false);
2606 }
2607
2608 rspamd_controller_send_ucl (conn_ent, top);
2609
2610
2611 return TRUE;
2612 }
2613
2614 static void
rspamd_controller_stat_cleanup_task(void * ud)2615 rspamd_controller_stat_cleanup_task (void *ud)
2616 {
2617 struct rspamd_stat_cbdata *cbdata = ud;
2618
2619 rspamd_task_free (cbdata->task);
2620 ucl_object_unref (cbdata->top);
2621 }
2622
2623 /*
2624 * Stat command handler:
2625 * request: /stat (/resetstat)
2626 * headers: Password
2627 * reply: json data
2628 */
2629 static int
rspamd_controller_handle_stat_common(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg,gboolean do_reset)2630 rspamd_controller_handle_stat_common (
2631 struct rspamd_http_connection_entry *conn_ent,
2632 struct rspamd_http_message *msg,
2633 gboolean do_reset)
2634 {
2635 struct rspamd_controller_session *session = conn_ent->ud;
2636 ucl_object_t *top, *sub;
2637 gint i;
2638 int64_t uptime;
2639 guint64 spam = 0, ham = 0;
2640 rspamd_mempool_stat_t mem_st;
2641 struct rspamd_stat *stat, stat_copy;
2642 struct rspamd_controller_worker_ctx *ctx;
2643 struct rspamd_task *task;
2644 struct rspamd_stat_cbdata *cbdata;
2645
2646 memset (&mem_st, 0, sizeof (mem_st));
2647 rspamd_mempool_stat (&mem_st);
2648 memcpy (&stat_copy, session->ctx->worker->srv->stat, sizeof (stat_copy));
2649 stat = &stat_copy;
2650 ctx = session->ctx;
2651
2652 task = rspamd_task_new (session->ctx->worker, session->cfg, session->pool,
2653 ctx->lang_det, ctx->event_loop, FALSE);
2654 task->resolver = ctx->resolver;
2655 cbdata = rspamd_mempool_alloc0 (session->pool, sizeof (*cbdata));
2656 cbdata->conn_ent = conn_ent;
2657 cbdata->task = task;
2658 top = ucl_object_typed_new (UCL_OBJECT);
2659 cbdata->top = top;
2660
2661 task->s = rspamd_session_create (session->pool,
2662 rspamd_controller_stat_fin_task,
2663 NULL,
2664 rspamd_controller_stat_cleanup_task,
2665 cbdata);
2666 task->fin_arg = cbdata;
2667 task->http_conn = rspamd_http_connection_ref (conn_ent->conn);;
2668 task->sock = conn_ent->conn->fd;
2669
2670 ucl_object_insert_key (top, ucl_object_fromstring (
2671 RVERSION), "version", 0, false);
2672 ucl_object_insert_key (top, ucl_object_fromstring (
2673 session->ctx->cfg->checksum), "config_id", 0, false);
2674 uptime = ev_time () - session->ctx->start_time;
2675 ucl_object_insert_key (top, ucl_object_fromint (
2676 uptime), "uptime", 0, false);
2677 ucl_object_insert_key (top, ucl_object_frombool (!session->is_enable),
2678 "read_only", 0, false);
2679 ucl_object_insert_key (top, ucl_object_fromint (
2680 stat->messages_scanned), "scanned", 0, false);
2681 ucl_object_insert_key (top, ucl_object_fromint (
2682 stat->messages_learned), "learned", 0, false);
2683
2684 if (stat->messages_scanned > 0) {
2685 sub = ucl_object_typed_new (UCL_OBJECT);
2686 for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i++) {
2687 ucl_object_insert_key (sub,
2688 ucl_object_fromint (stat->actions_stat[i]),
2689 rspamd_action_to_str (i), 0, false);
2690 if (i < METRIC_ACTION_GREYLIST) {
2691 spam += stat->actions_stat[i];
2692 }
2693 else {
2694 ham += stat->actions_stat[i];
2695 }
2696 if (do_reset) {
2697 #ifndef HAVE_ATOMIC_BUILTINS
2698 session->ctx->worker->srv->stat->actions_stat[i] = 0;
2699 #else
2700 __atomic_store_n(&session->ctx->worker->srv->stat->actions_stat[i],
2701 0, __ATOMIC_RELEASE);
2702 #endif
2703 }
2704 }
2705 ucl_object_insert_key (top, sub, "actions", 0, false);
2706 }
2707
2708 ucl_object_insert_key (top, ucl_object_fromint (
2709 spam), "spam_count", 0, false);
2710 ucl_object_insert_key (top, ucl_object_fromint (
2711 ham), "ham_count", 0, false);
2712 ucl_object_insert_key (top,
2713 ucl_object_fromint (stat->connections_count), "connections", 0, false);
2714 ucl_object_insert_key (top,
2715 ucl_object_fromint (stat->control_connections_count),
2716 "control_connections", 0, false);
2717
2718 ucl_object_insert_key (top,
2719 ucl_object_fromint (mem_st.pools_allocated), "pools_allocated", 0,
2720 false);
2721 ucl_object_insert_key (top,
2722 ucl_object_fromint (mem_st.pools_freed), "pools_freed", 0, false);
2723 ucl_object_insert_key (top,
2724 ucl_object_fromint (mem_st.bytes_allocated), "bytes_allocated", 0,
2725 false);
2726 ucl_object_insert_key (top,
2727 ucl_object_fromint (
2728 mem_st.chunks_allocated), "chunks_allocated", 0, false);
2729 ucl_object_insert_key (top,
2730 ucl_object_fromint (mem_st.shared_chunks_allocated),
2731 "shared_chunks_allocated", 0, false);
2732 ucl_object_insert_key (top,
2733 ucl_object_fromint (mem_st.chunks_freed), "chunks_freed", 0, false);
2734 ucl_object_insert_key (top,
2735 ucl_object_fromint (
2736 mem_st.oversized_chunks), "chunks_oversized", 0, false);
2737 ucl_object_insert_key (top,
2738 ucl_object_fromint (mem_st.fragmented_size), "fragmented", 0, false);
2739
2740 if (do_reset) {
2741 session->ctx->srv->stat->messages_scanned = 0;
2742 session->ctx->srv->stat->messages_learned = 0;
2743 session->ctx->srv->stat->connections_count = 0;
2744 session->ctx->srv->stat->control_connections_count = 0;
2745 rspamd_mempool_stat_reset ();
2746 }
2747
2748 fuzzy_stat_command (task);
2749
2750 /* Now write statistics for each statfile */
2751 rspamd_stat_statistics (task, session->ctx->cfg, &cbdata->learned,
2752 &cbdata->stat);
2753 session->task = task;
2754 rspamd_session_pending (task->s);
2755
2756 return 0;
2757 }
2758
2759 static int
rspamd_controller_handle_stat(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2760 rspamd_controller_handle_stat (struct rspamd_http_connection_entry *conn_ent,
2761 struct rspamd_http_message *msg)
2762 {
2763 struct rspamd_controller_session *session = conn_ent->ud;
2764
2765 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
2766 return 0;
2767 }
2768
2769 return rspamd_controller_handle_stat_common (conn_ent, msg, FALSE);
2770 }
2771
2772 static int
rspamd_controller_handle_statreset(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)2773 rspamd_controller_handle_statreset (
2774 struct rspamd_http_connection_entry *conn_ent,
2775 struct rspamd_http_message *msg)
2776 {
2777 struct rspamd_controller_session *session = conn_ent->ud;
2778
2779 if (!rspamd_controller_check_password (conn_ent, session, msg, TRUE)) {
2780 return 0;
2781 }
2782
2783 msg_info_session ("<%s> reset stat",
2784 rspamd_inet_address_to_string (session->from_addr));
2785 return rspamd_controller_handle_stat_common (conn_ent, msg, TRUE);
2786 }
2787
2788 /*
2789 * Metrics command handler:
2790 * request: /metrics
2791 * headers: Password
2792 * reply: OpenMetrics
2793 */
2794
2795 static gboolean
rspamd_controller_metrics_fin_task(void * ud)2796 rspamd_controller_metrics_fin_task (void *ud) {
2797 struct rspamd_stat_cbdata *cbdata = ud;
2798 struct rspamd_http_connection_entry *conn_ent;
2799 ucl_object_t *top;
2800 GList *fuzzy_elts, *cur;
2801 struct rspamd_fuzzy_stat_entry *entry;
2802 rspamd_fstring_t *output;
2803 gint i;
2804
2805 conn_ent = cbdata->conn_ent;
2806 top = cbdata->top;
2807
2808 ucl_object_insert_key (top,
2809 ucl_object_fromint (cbdata->learned), "total_learns", 0, false);
2810
2811 output = rspamd_fstring_sized_new (1024);
2812 rspamd_printf_fstring (&output, "# HELP rspamd_build_info A metric with a constant '1' value labeled by version from which rspamd was built.\n");
2813 rspamd_printf_fstring (&output, "# TYPE rspamd_build_info gauge\n");
2814 rspamd_printf_fstring (&output, "rspamd_build_info{version=\"%s\"} 1\n",
2815 ucl_object_tostring (ucl_object_lookup (top, "version")));
2816 rspamd_printf_fstring (&output, "# HELP rspamd_config A metric with a constant '1' value labeled by id of the current config.\n");
2817 rspamd_printf_fstring (&output, "# TYPE rspamd_config gauge\n");
2818 rspamd_printf_fstring (&output, "rspamd_config{id=\"%s\"} 1\n",
2819 ucl_object_tostring (ucl_object_lookup (top, "config_id")));
2820 rspamd_printf_fstring (&output, "# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.\n");
2821 rspamd_printf_fstring (&output, "# TYPE process_start_time_seconds gauge\n");
2822 rspamd_printf_fstring (&output, "process_start_time_seconds %L\n",
2823 ucl_object_toint (ucl_object_lookup (top, "start_time")));
2824 rspamd_printf_fstring (&output, "# HELP rspamd_read_only Whether the rspamd instance is read-only.\n");
2825 rspamd_printf_fstring (&output, "# TYPE rspamd_read_only gauge\n");
2826 rspamd_printf_fstring (&output, "rspamd_read_only %L\n",
2827 ucl_object_toint (ucl_object_lookup (top, "read_only")));
2828 rspamd_printf_fstring (&output, "# HELP rspamd_scanned_total Scanned messages.\n");
2829 rspamd_printf_fstring (&output, "# TYPE rspamd_scanned_total counter\n");
2830 rspamd_printf_fstring (&output, "rspamd_scanned_total %L\n",
2831 ucl_object_toint (ucl_object_lookup (top, "scanned")));
2832 rspamd_printf_fstring (&output, "# HELP rspamd_learned_total Learned messages.\n");
2833 rspamd_printf_fstring (&output, "# TYPE rspamd_learned_total counter\n");
2834 rspamd_printf_fstring (&output, "rspamd_learned_total %L\n",
2835 ucl_object_toint (ucl_object_lookup (top, "learned")));
2836 rspamd_printf_fstring (&output, "# HELP rspamd_spam_total Messages classified as spam.\n");
2837 rspamd_printf_fstring (&output, "# TYPE rspamd_spam_total counter\n");
2838 rspamd_printf_fstring (&output, "rspamd_spam_total %L\n",
2839 ucl_object_toint (ucl_object_lookup (top, "spam_count")));
2840 rspamd_printf_fstring (&output, "# HELP rspamd_ham_total Messages classified as ham.\n");
2841 rspamd_printf_fstring (&output, "# TYPE rspamd_ham_total counter\n");
2842 rspamd_printf_fstring (&output, "rspamd_ham_total %L\n",
2843 ucl_object_toint (ucl_object_lookup (top, "ham_count")));
2844 rspamd_printf_fstring (&output, "# HELP rspamd_connections Active connections.\n");
2845 rspamd_printf_fstring (&output, "# TYPE rspamd_connections gauge\n");
2846 rspamd_printf_fstring (&output, "rspamd_connections %L\n",
2847 ucl_object_toint (ucl_object_lookup (top, "connections")));
2848 rspamd_printf_fstring (&output, "# HELP rspamd_control_connections_total Control connections.\n");
2849 rspamd_printf_fstring (&output, "# TYPE rspamd_control_connections_total counter\n");
2850 rspamd_printf_fstring (&output, "rspamd_control_connections_total %L\n",
2851 ucl_object_toint (ucl_object_lookup (top, "control_connections")));
2852 rspamd_printf_fstring (&output, "# HELP rspamd_pools_allocated Pools allocated.\n");
2853 rspamd_printf_fstring (&output, "# TYPE rspamd_pools_allocated gauge\n");
2854 rspamd_printf_fstring (&output, "rspamd_pools_allocated %L\n",
2855 ucl_object_toint (ucl_object_lookup (top, "pools_allocated")));
2856 rspamd_printf_fstring (&output, "# HELP rspamd_pools_freed Pools freed.\n");
2857 rspamd_printf_fstring (&output, "# TYPE rspamd_pools_freed gauge\n");
2858 rspamd_printf_fstring (&output, "rspamd_pools_freed %L\n",
2859 ucl_object_toint (ucl_object_lookup (top, "pools_freed")));
2860 rspamd_printf_fstring (&output, "# HELP rspamd_allocated_bytes Bytes allocated.\n");
2861 rspamd_printf_fstring (&output, "# TYPE rspamd_allocated_bytes gauge\n");
2862 rspamd_printf_fstring (&output, "rspamd_allocated_bytes %L\n",
2863 ucl_object_toint (ucl_object_lookup (top, "bytes_allocated")));
2864 rspamd_printf_fstring (&output, "# HELP rspamd_chunks_allocated Chunks allocated.\n");
2865 rspamd_printf_fstring (&output, "# TYPE rspamd_chunks_allocated gauge\n");
2866 rspamd_printf_fstring (&output, "rspamd_chunks_allocated %L\n",
2867 ucl_object_toint (ucl_object_lookup (top, "chunks_allocated")));
2868 rspamd_printf_fstring (&output, "# HELP rspamd_shared_chunks_allocated Shared chunks allocated.\n");
2869 rspamd_printf_fstring (&output, "# TYPE rspamd_shared_chunks_allocated gauge\n");
2870 rspamd_printf_fstring (&output, "rspamd_shared_chunks_allocated %L\n",
2871 ucl_object_toint (ucl_object_lookup (top, "shared_chunks_allocated")));
2872 rspamd_printf_fstring (&output, "# HELP rspamd_chunks_freed Chunks freed.\n");
2873 rspamd_printf_fstring (&output, "# TYPE rspamd_chunks_freed gauge\n");
2874 rspamd_printf_fstring (&output, "rspamd_chunks_freed %L\n",
2875 ucl_object_toint (ucl_object_lookup (top, "chunks_freed")));
2876 rspamd_printf_fstring (&output, "# HELP rspamd_chunks_oversized Chunks oversized.\n");
2877 rspamd_printf_fstring (&output, "# TYPE rspamd_chunks_oversized gauge\n");
2878 rspamd_printf_fstring (&output, "rspamd_chunks_oversized %L\n",
2879 ucl_object_toint (ucl_object_lookup (top, "chunks_oversized")));
2880 rspamd_printf_fstring (&output, "# HELP rspamd_fragmented Fragmented.\n");
2881 rspamd_printf_fstring (&output, "# TYPE rspamd_fragmented gauge\n");
2882 rspamd_printf_fstring (&output, "rspamd_fragmented %L\n",
2883 ucl_object_toint (ucl_object_lookup (top, "fragmented")));
2884 rspamd_printf_fstring (&output, "# HELP rspamd_learns_total Total learns.\n");
2885 rspamd_printf_fstring (&output, "# TYPE rspamd_learns_total counter\n");
2886 rspamd_printf_fstring (&output, "rspamd_learns_total %L\n",
2887 ucl_object_toint (ucl_object_lookup (top, "total_learns")));
2888
2889 const ucl_object_t *acts_obj = ucl_object_lookup (top, "actions");
2890
2891 if (acts_obj) {
2892 rspamd_printf_fstring (&output, "# HELP rspamd_actions_total Actions labelled by action type.\n");
2893 rspamd_printf_fstring (&output, "# TYPE rspamd_actions_total counter\n");
2894 for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i++) {
2895 const char *str_act = rspamd_action_to_str (i);
2896 const ucl_object_t *act = ucl_object_lookup (acts_obj, str_act);
2897
2898 if (act) {
2899 rspamd_printf_fstring(&output, "rspamd_actions_total{type=\"%s\"} %L\n",
2900 str_act,
2901 ucl_object_toint(act));
2902 }
2903 else {
2904 rspamd_printf_fstring (&output, "rspamd_actions_total{type=\"%s\"} 0\n",
2905 str_act);
2906 }
2907 }
2908 }
2909
2910 if (cbdata->stat) {
2911 const ucl_object_t *cur_elt;
2912 ucl_object_iter_t it = NULL;
2913 rspamd_fstring_t *revision;
2914 rspamd_fstring_t *used;
2915 rspamd_fstring_t *total;
2916 rspamd_fstring_t *size;
2917 rspamd_fstring_t *languages;
2918 rspamd_fstring_t *users;
2919
2920 revision = rspamd_fstring_sized_new (16);
2921 used = rspamd_fstring_sized_new (16);
2922 total = rspamd_fstring_sized_new (16);
2923 size = rspamd_fstring_sized_new (16);
2924 languages = rspamd_fstring_sized_new (16);
2925 users = rspamd_fstring_sized_new (16);
2926
2927 while ((cur_elt = ucl_object_iterate (cbdata->stat, &it, true))) {
2928 const char *sym = ucl_object_tostring (ucl_object_lookup (cur_elt, "symbol"));
2929 const char *type = ucl_object_tostring (ucl_object_lookup (cur_elt, "type"));
2930
2931 if (sym && type) {
2932 rspamd_printf_fstring (&revision, "rspamd_statfiles_revision{symbol=\"%s\",type=\"%s\"} %L\n",
2933 sym,
2934 type,
2935 ucl_object_toint (ucl_object_lookup (cur_elt, "revision")));
2936 rspamd_printf_fstring (&used, "rspamd_statfiles_used{symbol=\"%s\",type=\"%s\"} %L\n",
2937 sym,
2938 type,
2939 ucl_object_toint (ucl_object_lookup (cur_elt, "used")));
2940 rspamd_printf_fstring (&total, "rspamd_statfiles_totals{symbol=\"%s\",type=\"%s\"} %L\n",
2941 sym,
2942 type,
2943 ucl_object_toint (ucl_object_lookup (cur_elt, "total")));
2944 rspamd_printf_fstring (&size, "rspamd_statfiles_size{symbol=\"%s\",type=\"%s\"} %L\n",
2945 sym,
2946 type,
2947 ucl_object_toint (ucl_object_lookup (cur_elt, "size")));
2948 rspamd_printf_fstring (&languages, "rspamd_statfiles_languages{symbol=\"%s\",type=\"%s\"} %L\n",
2949 sym,
2950 type,
2951 ucl_object_toint (ucl_object_lookup (cur_elt, "languages")));
2952 rspamd_printf_fstring (&users, "rspamd_statfiles_users{symbol=\"%s\",type=\"%s\"} %L\n",
2953 sym,
2954 type,
2955 ucl_object_toint (ucl_object_lookup (cur_elt, "users")));
2956 }
2957 }
2958
2959 if (RSPAMD_FSTRING_LEN(revision) > 0) {
2960 rspamd_printf_fstring (&output, "# HELP rspamd_statfiles_revision Stat files revision.\n");
2961 rspamd_printf_fstring (&output, "# TYPE rspamd_statfiles_revision gauge\n");
2962 output = rspamd_fstring_append (output,
2963 RSPAMD_FSTRING_DATA(revision), RSPAMD_FSTRING_LEN(revision));
2964 }
2965 if (RSPAMD_FSTRING_LEN(used) > 0) {
2966 rspamd_printf_fstring (&output, "# HELP rspamd_statfiles_used Stat files used.\n");
2967 rspamd_printf_fstring (&output, "# TYPE rspamd_statfiles_used gauge\n");
2968 output = rspamd_fstring_append (output,
2969 RSPAMD_FSTRING_DATA(used), RSPAMD_FSTRING_LEN(used));
2970 }
2971 if (RSPAMD_FSTRING_LEN(total) > 0) {
2972 rspamd_printf_fstring (&output, "# HELP rspamd_statfiles_totals Stat files total.\n");
2973 rspamd_printf_fstring (&output, "# TYPE rspamd_statfiles_totals gauge\n");
2974 output = rspamd_fstring_append (output,
2975 RSPAMD_FSTRING_DATA(total), RSPAMD_FSTRING_LEN(total));
2976 }
2977 if (RSPAMD_FSTRING_LEN(size) > 0) {
2978 rspamd_printf_fstring (&output, "# HELP rspamd_statfiles_size Stat files size.\n");
2979 rspamd_printf_fstring (&output, "# TYPE rspamd_statfiles_size gauge\n");
2980 output = rspamd_fstring_append (output,
2981 RSPAMD_FSTRING_DATA(size), RSPAMD_FSTRING_LEN(size));
2982 }
2983 if (RSPAMD_FSTRING_LEN(languages) > 0) {
2984 rspamd_printf_fstring (&output, "# HELP rspamd_statfiles_languages Stat files languages.\n");
2985 rspamd_printf_fstring (&output, "# TYPE rspamd_statfiles_languages gauge\n");
2986 output = rspamd_fstring_append (output,
2987 RSPAMD_FSTRING_DATA(languages), RSPAMD_FSTRING_LEN(languages));
2988 }
2989 if (RSPAMD_FSTRING_LEN(users) > 0) {
2990 rspamd_printf_fstring (&output, "# HELP rspamd_statfiles_users Stat files users.\n");
2991 rspamd_printf_fstring (&output, "# TYPE rspamd_statfiles_users gauge\n");
2992 output = rspamd_fstring_append (output,
2993 RSPAMD_FSTRING_DATA(users), RSPAMD_FSTRING_LEN(users));
2994 }
2995
2996 rspamd_fstring_free (revision);
2997 rspamd_fstring_free (used);
2998 rspamd_fstring_free (total);
2999 rspamd_fstring_free (size);
3000 rspamd_fstring_free (languages);
3001 rspamd_fstring_free (users);
3002 }
3003
3004 fuzzy_elts = rspamd_mempool_get_variable (cbdata->task->task_pool, "fuzzy_stat");
3005
3006 if (fuzzy_elts) {
3007 rspamd_printf_fstring (&output, "# HELP rspamd_fuzzy_stat Fuzzy stat labelled by storage.\n");
3008 rspamd_printf_fstring (&output, "# TYPE rspamd_fuzzy_stat gauge\n");
3009 for (cur = fuzzy_elts; cur != NULL; cur = g_list_next (cur)) {
3010 entry = cur->data;
3011
3012 if (entry->name) {
3013 rspamd_printf_fstring (&output, "rspamd_fuzzy_stat{storage=\"%s\"} %ud\n",
3014 entry->name, entry->fuzzy_cnt);
3015 }
3016 }
3017 }
3018
3019 rspamd_printf_fstring (&output, "# EOF\n");
3020
3021 rspamd_controller_send_openmetrics (conn_ent, output);
3022
3023 return TRUE;
3024 }
3025
3026 static int
rspamd_controller_handle_metrics_common(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg,gboolean do_reset)3027 rspamd_controller_handle_metrics_common (
3028 struct rspamd_http_connection_entry *conn_ent,
3029 struct rspamd_http_message *msg,
3030 gboolean do_reset)
3031 {
3032 struct rspamd_controller_session *session = conn_ent->ud;
3033 ucl_object_t *top, *sub;
3034 gint i;
3035 int64_t uptime;
3036 guint64 spam = 0, ham = 0;
3037 rspamd_mempool_stat_t mem_st;
3038 struct rspamd_stat *stat, stat_copy;
3039 struct rspamd_controller_worker_ctx *ctx;
3040 struct rspamd_task *task;
3041 struct rspamd_stat_cbdata *cbdata;
3042
3043 memset (&mem_st, 0, sizeof (mem_st));
3044 rspamd_mempool_stat (&mem_st);
3045 memcpy (&stat_copy, session->ctx->worker->srv->stat, sizeof (stat_copy));
3046 stat = &stat_copy;
3047 ctx = session->ctx;
3048
3049 task = rspamd_task_new (session->ctx->worker, session->cfg, session->pool,
3050 ctx->lang_det, ctx->event_loop, FALSE);
3051 task->resolver = ctx->resolver;
3052 cbdata = rspamd_mempool_alloc0 (session->pool, sizeof (*cbdata));
3053 cbdata->conn_ent = conn_ent;
3054 cbdata->task = task;
3055 top = ucl_object_typed_new (UCL_OBJECT);
3056 cbdata->top = top;
3057
3058 task->s = rspamd_session_create (session->pool,
3059 rspamd_controller_metrics_fin_task,
3060 NULL,
3061 rspamd_controller_stat_cleanup_task,
3062 cbdata);
3063 task->fin_arg = cbdata;
3064 task->http_conn = rspamd_http_connection_ref (conn_ent->conn);;
3065 task->sock = conn_ent->conn->fd;
3066
3067 ucl_object_insert_key (top, ucl_object_fromstring (
3068 RVERSION), "version", 0, false);
3069 ucl_object_insert_key (top, ucl_object_fromstring (
3070 session->ctx->cfg->checksum), "config_id", 0, false);
3071 uptime = ev_time () - session->ctx->start_time;
3072 ucl_object_insert_key (top, ucl_object_fromint (
3073 uptime), "uptime", 0, false);
3074 ucl_object_insert_key (top, ucl_object_fromint (
3075 session->ctx->start_time), "start_time", 0, false);
3076 ucl_object_insert_key (top, ucl_object_frombool (!session->is_enable),
3077 "read_only", 0, false);
3078 ucl_object_insert_key (top, ucl_object_fromint (
3079 stat->messages_scanned), "scanned", 0, false);
3080 ucl_object_insert_key (top, ucl_object_fromint (
3081 stat->messages_learned), "learned", 0, false);
3082
3083 if (stat->messages_scanned > 0) {
3084 sub = ucl_object_typed_new (UCL_OBJECT);
3085 for (i = METRIC_ACTION_REJECT; i <= METRIC_ACTION_NOACTION; i++) {
3086 ucl_object_insert_key (sub,
3087 ucl_object_fromint (stat->actions_stat[i]),
3088 rspamd_action_to_str (i), 0, false);
3089 if (i < METRIC_ACTION_GREYLIST) {
3090 spam += stat->actions_stat[i];
3091 }
3092 else {
3093 ham += stat->actions_stat[i];
3094 }
3095 if (do_reset) {
3096 #ifndef HAVE_ATOMIC_BUILTINS
3097 session->ctx->worker->srv->stat->actions_stat[i] = 0;
3098 #else
3099 __atomic_store_n(&session->ctx->worker->srv->stat->actions_stat[i],
3100 0, __ATOMIC_RELEASE);
3101 #endif
3102 }
3103 }
3104 ucl_object_insert_key (top, sub, "actions", 0, false);
3105 }
3106
3107 ucl_object_insert_key (top, ucl_object_fromint (
3108 spam), "spam_count", 0, false);
3109 ucl_object_insert_key (top, ucl_object_fromint (
3110 ham), "ham_count", 0, false);
3111 ucl_object_insert_key (top,
3112 ucl_object_fromint (stat->connections_count), "connections", 0, false);
3113 ucl_object_insert_key (top,
3114 ucl_object_fromint (stat->control_connections_count),
3115 "control_connections", 0, false);
3116
3117 ucl_object_insert_key (top,
3118 ucl_object_fromint (mem_st.pools_allocated), "pools_allocated", 0,
3119 false);
3120 ucl_object_insert_key (top,
3121 ucl_object_fromint (mem_st.pools_freed), "pools_freed", 0, false);
3122 ucl_object_insert_key (top,
3123 ucl_object_fromint (mem_st.bytes_allocated), "bytes_allocated", 0,
3124 false);
3125 ucl_object_insert_key (top,
3126 ucl_object_fromint (
3127 mem_st.chunks_allocated), "chunks_allocated", 0, false);
3128 ucl_object_insert_key (top,
3129 ucl_object_fromint (mem_st.shared_chunks_allocated),
3130 "shared_chunks_allocated", 0, false);
3131 ucl_object_insert_key (top,
3132 ucl_object_fromint (mem_st.chunks_freed), "chunks_freed", 0, false);
3133 ucl_object_insert_key (top,
3134 ucl_object_fromint (
3135 mem_st.oversized_chunks), "chunks_oversized", 0, false);
3136 ucl_object_insert_key (top,
3137 ucl_object_fromint (mem_st.fragmented_size), "fragmented", 0, false);
3138
3139 if (do_reset) {
3140 session->ctx->srv->stat->messages_scanned = 0;
3141 session->ctx->srv->stat->messages_learned = 0;
3142 session->ctx->srv->stat->connections_count = 0;
3143 session->ctx->srv->stat->control_connections_count = 0;
3144 rspamd_mempool_stat_reset ();
3145 }
3146
3147 fuzzy_stat_command (task);
3148
3149 /* Now write statistics for each statfile */
3150 rspamd_stat_statistics (task, session->ctx->cfg, &cbdata->learned,
3151 &cbdata->stat);
3152 session->task = task;
3153
3154 rspamd_session_pending (task->s);
3155
3156
3157 return 0;
3158 }
3159
3160
3161 static int
rspamd_controller_handle_metrics(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)3162 rspamd_controller_handle_metrics (struct rspamd_http_connection_entry *conn_ent,
3163 struct rspamd_http_message *msg)
3164 {
3165 struct rspamd_controller_session *session = conn_ent->ud;
3166
3167 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
3168 return 0;
3169 }
3170 return rspamd_controller_handle_metrics_common (conn_ent, msg, FALSE);
3171 }
3172
3173
3174 /*
3175 * Counters command handler:
3176 * request: /counters
3177 * headers: Password
3178 * reply: json array of all counters
3179 */
3180 static int
rspamd_controller_handle_counters(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)3181 rspamd_controller_handle_counters (
3182 struct rspamd_http_connection_entry *conn_ent,
3183 struct rspamd_http_message *msg)
3184 {
3185 struct rspamd_controller_session *session = conn_ent->ud;
3186 ucl_object_t *top;
3187 struct rspamd_symcache *cache;
3188
3189 if (!rspamd_controller_check_password (conn_ent, session, msg, FALSE)) {
3190 return 0;
3191 }
3192
3193 cache = session->ctx->cfg->cache;
3194
3195 if (cache != NULL) {
3196 top = rspamd_symcache_counters (cache);
3197 rspamd_controller_send_ucl (conn_ent, top);
3198 ucl_object_unref (top);
3199 }
3200 else {
3201 rspamd_controller_send_error (conn_ent, 500, "Invalid cache");
3202 }
3203
3204 return 0;
3205 }
3206
3207 static int
rspamd_controller_handle_custom(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)3208 rspamd_controller_handle_custom (struct rspamd_http_connection_entry *conn_ent,
3209 struct rspamd_http_message *msg)
3210 {
3211 struct rspamd_controller_session *session = conn_ent->ud;
3212 struct rspamd_custom_controller_command *cmd;
3213 gchar *url_str;
3214 struct http_parser_url u;
3215 rspamd_ftok_t lookup;
3216
3217 http_parser_parse_url (msg->url->str, msg->url->len, TRUE, &u);
3218
3219 if (u.field_set & (1 << UF_PATH)) {
3220 gsize unnorm_len;
3221 lookup.begin = msg->url->str + u.field_data[UF_PATH].off;
3222 lookup.len = u.field_data[UF_PATH].len;
3223
3224 rspamd_http_normalize_path_inplace ((gchar *)lookup.begin,
3225 lookup.len,
3226 &unnorm_len);
3227 lookup.len = unnorm_len;
3228 }
3229 else {
3230 lookup.begin = msg->url->str;
3231 lookup.len = msg->url->len;
3232 }
3233
3234 url_str = rspamd_ftok_cstr (&lookup);
3235 cmd = g_hash_table_lookup (session->ctx->custom_commands, url_str);
3236 g_free (url_str);
3237
3238 if (cmd == NULL || cmd->handler == NULL) {
3239 msg_err_session ("custom command %T has not been found", &lookup);
3240 rspamd_controller_send_error (conn_ent, 404, "No command associated");
3241 return 0;
3242 }
3243
3244 if (!rspamd_controller_check_password (conn_ent, session, msg,
3245 cmd->privilleged)) {
3246 return 0;
3247 }
3248 if (cmd->require_message && (rspamd_http_message_get_body (msg, NULL) == NULL)) {
3249 msg_err_session ("got zero length body, cannot continue");
3250 rspamd_controller_send_error (conn_ent,
3251 400,
3252 "Empty body is not permitted");
3253 return 0;
3254 }
3255
3256 /* Transfer query arguments to headers */
3257 if (u.field_set & (1u << UF_QUERY)) {
3258 GHashTable *query_args;
3259 GHashTableIter it;
3260 gpointer k, v;
3261 rspamd_ftok_t *key, *value;
3262
3263 /* In case if we have a query, we need to store it somewhere */
3264 query_args = rspamd_http_message_parse_query (msg);
3265
3266 /* Insert the rest of query params as HTTP headers */
3267 g_hash_table_iter_init (&it, query_args);
3268
3269 while (g_hash_table_iter_next (&it, &k, &v)) {
3270 key = k;
3271 value = v;
3272 /* Steal strings */
3273 g_hash_table_iter_steal (&it);
3274 url_str = rspamd_ftok_cstr (key);
3275 rspamd_http_message_add_header_len (msg, url_str,
3276 value->begin, value->len);
3277 g_free (url_str);
3278 }
3279
3280 g_hash_table_unref (query_args);
3281 }
3282
3283 return cmd->handler (conn_ent, msg, cmd->ctx);
3284 }
3285
3286 static int
rspamd_controller_handle_plugins(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)3287 rspamd_controller_handle_plugins (struct rspamd_http_connection_entry *conn_ent,
3288 struct rspamd_http_message *msg)
3289 {
3290 struct rspamd_controller_session *session = conn_ent->ud;
3291 struct rspamd_controller_plugin_cbdata *cbd;
3292 GHashTableIter it;
3293 gpointer k, v;
3294 ucl_object_t *plugins;
3295
3296 if (!rspamd_controller_check_password (conn_ent, session, msg,
3297 FALSE)) {
3298 return 0;
3299 }
3300
3301 plugins = ucl_object_typed_new (UCL_OBJECT);
3302 g_hash_table_iter_init (&it, session->ctx->plugins);
3303
3304 while (g_hash_table_iter_next (&it, &k, &v)) {
3305 ucl_object_t *elt, *npath;
3306
3307 cbd = v;
3308 elt = (ucl_object_t *)ucl_object_lookup (plugins, cbd->plugin);
3309
3310 if (elt == NULL) {
3311 elt = ucl_object_typed_new (UCL_OBJECT);
3312 ucl_object_insert_key (elt, ucl_object_fromint (cbd->version),
3313 "version", 0, false);
3314 npath = ucl_object_typed_new (UCL_ARRAY);
3315 ucl_object_insert_key (elt, npath, "paths", 0, false);
3316 ucl_object_insert_key (plugins, elt, cbd->plugin, 0, false);
3317 }
3318 else {
3319 npath = (ucl_object_t *)ucl_object_lookup (elt, "paths");
3320 }
3321
3322 g_assert (npath != NULL);
3323 rspamd_ftok_t *key_tok = (rspamd_ftok_t *)k;
3324 ucl_array_append (npath, ucl_object_fromlstring (key_tok->begin, key_tok->len));
3325 }
3326
3327 rspamd_controller_send_ucl (conn_ent, plugins);
3328 ucl_object_unref (plugins);
3329
3330 return 0;
3331 }
3332
3333 static int
rspamd_controller_handle_ping(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)3334 rspamd_controller_handle_ping (struct rspamd_http_connection_entry *conn_ent,
3335 struct rspamd_http_message *msg)
3336 {
3337 struct rspamd_http_message *rep_msg;
3338 rspamd_fstring_t *reply;
3339
3340 rep_msg = rspamd_http_new_message (HTTP_RESPONSE);
3341 rep_msg->date = time (NULL);
3342 rep_msg->code = 200;
3343 rep_msg->status = rspamd_fstring_new_init ("OK", 2);
3344 reply = rspamd_fstring_new_init ("pong" CRLF, strlen ("pong" CRLF));
3345 rspamd_http_message_set_body_from_fstring_steal (rep_msg, reply);
3346 rspamd_http_connection_reset (conn_ent->conn);
3347 rspamd_http_router_insert_headers (conn_ent->rt, rep_msg);
3348 rspamd_http_connection_write_message (conn_ent->conn,
3349 rep_msg,
3350 NULL,
3351 "text/plain",
3352 conn_ent,
3353 conn_ent->rt->timeout);
3354 conn_ent->is_reply = TRUE;
3355
3356 return 0;
3357 }
3358
3359 /*
3360 * Called on unknown methods and is used to deal with CORS as per
3361 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
3362 */
3363 static int
rspamd_controller_handle_unknown(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)3364 rspamd_controller_handle_unknown (struct rspamd_http_connection_entry *conn_ent,
3365 struct rspamd_http_message *msg)
3366 {
3367 struct rspamd_http_message *rep;
3368
3369 if (msg->method == HTTP_OPTIONS) {
3370 /* Assume CORS request */
3371
3372 rep = rspamd_http_new_message (HTTP_RESPONSE);
3373 rep->date = time (NULL);
3374 rep->code = 200;
3375 rep->status = rspamd_fstring_new_init ("OK", 2);
3376 rspamd_http_message_add_header (rep, "Access-Control-Allow-Methods",
3377 "POST, GET, OPTIONS");
3378 rspamd_http_message_add_header (rep, "Access-Control-Allow-Headers",
3379 "Content-Type,Password,Map,Weight,Flag");
3380 rspamd_http_connection_reset (conn_ent->conn);
3381 rspamd_http_router_insert_headers (conn_ent->rt, rep);
3382 rspamd_http_connection_write_message (conn_ent->conn,
3383 rep,
3384 NULL,
3385 "text/plain",
3386 conn_ent,
3387 conn_ent->rt->timeout);
3388 conn_ent->is_reply = TRUE;
3389 }
3390 else {
3391 rep = rspamd_http_new_message (HTTP_RESPONSE);
3392 rep->date = time (NULL);
3393 rep->code = 500;
3394 rep->status = rspamd_fstring_new_init ("Invalid method",
3395 strlen ("Invalid method"));
3396 rspamd_http_connection_reset (conn_ent->conn);
3397 rspamd_http_router_insert_headers (conn_ent->rt, rep);
3398 rspamd_http_connection_write_message (conn_ent->conn,
3399 rep,
3400 NULL,
3401 "text/plain",
3402 conn_ent,
3403 conn_ent->rt->timeout);
3404 conn_ent->is_reply = TRUE;
3405 }
3406
3407 return 0;
3408 }
3409
3410 static int
rspamd_controller_handle_lua_plugin(struct rspamd_http_connection_entry * conn_ent,struct rspamd_http_message * msg)3411 rspamd_controller_handle_lua_plugin (struct rspamd_http_connection_entry *conn_ent,
3412 struct rspamd_http_message *msg)
3413 {
3414 struct rspamd_controller_session *session = conn_ent->ud;
3415 struct rspamd_controller_plugin_cbdata *cbd;
3416 struct rspamd_task *task, **ptask;
3417 struct rspamd_http_connection_entry **pconn;
3418 struct rspamd_controller_worker_ctx *ctx;
3419 lua_State *L;
3420 struct http_parser_url u;
3421 rspamd_ftok_t lookup;
3422
3423
3424 http_parser_parse_url (msg->url->str, msg->url->len, TRUE, &u);
3425
3426 if (u.field_set & (1 << UF_PATH)) {
3427 gsize unnorm_len;
3428 lookup.begin = msg->url->str + u.field_data[UF_PATH].off;
3429 lookup.len = u.field_data[UF_PATH].len;
3430
3431 rspamd_http_normalize_path_inplace ((gchar *)lookup.begin,
3432 lookup.len,
3433 &unnorm_len);
3434 lookup.len = unnorm_len;
3435 }
3436 else {
3437 lookup.begin = msg->url->str;
3438 lookup.len = msg->url->len;
3439 }
3440
3441 cbd = g_hash_table_lookup (session->ctx->plugins, &lookup);
3442
3443 if (cbd == NULL || cbd->handler == NULL) {
3444 msg_err_session ("plugin handler %T has not been found", &lookup);
3445 rspamd_controller_send_error (conn_ent, 404, "No command associated");
3446 return 0;
3447 }
3448
3449 L = cbd->L;
3450 ctx = cbd->ctx;
3451
3452 if (!rspamd_controller_check_password (conn_ent, session, msg,
3453 cbd->is_enable)) {
3454 return 0;
3455 }
3456 if (cbd->need_task && (rspamd_http_message_get_body (msg, NULL) == NULL)) {
3457 msg_err_session ("got zero length body, cannot continue");
3458 rspamd_controller_send_error (conn_ent,
3459 400,
3460 "Empty body is not permitted");
3461 return 0;
3462 }
3463
3464 task = rspamd_task_new (session->ctx->worker, session->cfg, session->pool,
3465 ctx->lang_det, ctx->event_loop, FALSE);
3466
3467 task->resolver = ctx->resolver;
3468 task->s = rspamd_session_create (session->pool,
3469 rspamd_controller_lua_fin_task,
3470 NULL,
3471 (event_finalizer_t )rspamd_task_free,
3472 task);
3473 task->fin_arg = conn_ent;
3474 task->http_conn = rspamd_http_connection_ref (conn_ent->conn);;
3475 task->sock = -1;
3476 session->task = task;
3477
3478 if (msg->body_buf.len > 0) {
3479 if (!rspamd_task_load_message (task, msg, msg->body_buf.begin, msg->body_buf.len)) {
3480 rspamd_controller_send_error (conn_ent, task->err->code, "%s",
3481 task->err->message);
3482 return 0;
3483 }
3484 }
3485
3486 /* Callback */
3487 lua_rawgeti (L, LUA_REGISTRYINDEX, cbd->handler->idx);
3488
3489 /* Task */
3490 ptask = lua_newuserdata (L, sizeof (*ptask));
3491 rspamd_lua_setclass (L, "rspamd{task}", -1);
3492 *ptask = task;
3493
3494 /* Connection */
3495 pconn = lua_newuserdata (L, sizeof (*pconn));
3496 rspamd_lua_setclass (L, "rspamd{csession}", -1);
3497 *pconn = conn_ent;
3498
3499 /* Query arguments */
3500 GHashTable *params;
3501 GHashTableIter it;
3502 gpointer k, v;
3503
3504 params = rspamd_http_message_parse_query (msg);
3505 lua_createtable (L, g_hash_table_size (params), 0);
3506 g_hash_table_iter_init (&it, params);
3507
3508 while (g_hash_table_iter_next (&it, &k, &v)) {
3509 rspamd_ftok_t *key_tok = (rspamd_ftok_t *)k,
3510 *value_tok = (rspamd_ftok_t *)v;
3511
3512 lua_pushlstring (L, key_tok->begin, key_tok->len);
3513 /* TODO: consider rspamd_text here */
3514 lua_pushlstring (L, value_tok->begin, value_tok->len);
3515 lua_settable (L, -3);
3516 }
3517
3518 g_hash_table_unref (params);
3519
3520 if (lua_pcall (L, 3, 0, 0) != 0) {
3521 rspamd_controller_send_error (conn_ent, 503, "Cannot run callback: %s",
3522 lua_tostring (L, -1));
3523 lua_settop (L, 0);
3524
3525 return 0;
3526 }
3527
3528 rspamd_session_pending (task->s);
3529
3530 return 0;
3531 }
3532
3533
3534 static void
rspamd_controller_error_handler(struct rspamd_http_connection_entry * conn_ent,GError * err)3535 rspamd_controller_error_handler (struct rspamd_http_connection_entry *conn_ent,
3536 GError *err)
3537 {
3538 struct rspamd_controller_session *session = conn_ent->ud;
3539
3540 msg_err_session ("http error occurred: %s", err->message);
3541 }
3542
3543 static void
rspamd_controller_finish_handler(struct rspamd_http_connection_entry * conn_ent)3544 rspamd_controller_finish_handler (struct rspamd_http_connection_entry *conn_ent)
3545 {
3546 struct rspamd_controller_session *session = conn_ent->ud;
3547
3548 session->ctx->worker->srv->stat->control_connections_count++;
3549
3550 if (session->task != NULL) {
3551 rspamd_session_destroy (session->task->s);
3552 }
3553
3554 session->wrk->nconns --;
3555 rspamd_inet_address_free (session->from_addr);
3556 REF_RELEASE (session->cfg);
3557
3558 if (session->pool) {
3559 msg_debug_session ("destroy session %p", session);
3560 rspamd_mempool_delete (session->pool);
3561 }
3562
3563 g_free (session);
3564 }
3565
3566 static void
rspamd_controller_accept_socket(EV_P_ ev_io * w,int revents)3567 rspamd_controller_accept_socket (EV_P_ ev_io *w, int revents)
3568 {
3569 struct rspamd_worker *worker = (struct rspamd_worker *)w->data;
3570 struct rspamd_controller_worker_ctx *ctx;
3571 struct rspamd_controller_session *session;
3572 rspamd_inet_addr_t *addr = NULL;
3573 gint nfd;
3574
3575 ctx = worker->ctx;
3576
3577 if ((nfd =
3578 rspamd_accept_from_socket (w->fd, &addr,
3579 rspamd_worker_throttle_accept_events, worker->accept_events)) == -1) {
3580 msg_warn_ctx ("accept failed: %s", strerror (errno));
3581 return;
3582 }
3583 /* Check for EAGAIN */
3584 if (nfd == 0) {
3585 rspamd_inet_address_free (addr);
3586 return;
3587 }
3588
3589 session = g_malloc0 (sizeof (struct rspamd_controller_session));
3590 session->pool = rspamd_mempool_new (rspamd_mempool_suggest_size (),
3591 "csession", 0);
3592 session->ctx = ctx;
3593 session->cfg = ctx->cfg;
3594 session->lang_det = ctx->lang_det;
3595 REF_RETAIN (session->cfg);
3596
3597 session->from_addr = addr;
3598 session->wrk = worker;
3599 worker->nconns ++;
3600
3601 rspamd_http_router_handle_socket (ctx->http, nfd, session);
3602 }
3603
3604 static void
rspamd_controller_password_sane(struct rspamd_controller_worker_ctx * ctx,const gchar * password,const gchar * type)3605 rspamd_controller_password_sane (struct rspamd_controller_worker_ctx *ctx,
3606 const gchar *password, const gchar *type)
3607 {
3608 const struct rspamd_controller_pbkdf *pbkdf = &pbkdf_list[0];
3609
3610 if (password == NULL) {
3611 msg_warn_ctx ("%s is not set, so you should filter controller "
3612 "availability "
3613 "by using of firewall or `secure_ip` option", type);
3614 return;
3615 }
3616
3617 g_assert (pbkdf != NULL);
3618
3619 if (!rspamd_is_encrypted_password (password, NULL)) {
3620 /* Suggest encryption to a user */
3621
3622 msg_warn_ctx ("your %s is not encrypted, we strongly "
3623 "recommend to replace it with the encrypted one", type);
3624 }
3625 }
3626
3627 gpointer
init_controller_worker(struct rspamd_config * cfg)3628 init_controller_worker (struct rspamd_config *cfg)
3629 {
3630 struct rspamd_controller_worker_ctx *ctx;
3631 GQuark type;
3632
3633 type = g_quark_try_string ("controller");
3634
3635 ctx = rspamd_mempool_alloc0 (cfg->cfg_pool,
3636 sizeof (struct rspamd_controller_worker_ctx));
3637
3638 ctx->magic = rspamd_controller_ctx_magic;
3639 ctx->timeout = DEFAULT_WORKER_IO_TIMEOUT;
3640 ctx->task_timeout = NAN;
3641
3642 rspamd_rcl_register_worker_option (cfg,
3643 type,
3644 "password",
3645 rspamd_rcl_parse_struct_string,
3646 ctx,
3647 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx, password),
3648 0,
3649 "Password for read-only commands");
3650
3651 rspamd_rcl_register_worker_option (cfg,
3652 type,
3653 "enable_password",
3654 rspamd_rcl_parse_struct_string,
3655 ctx,
3656 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx,
3657 enable_password),
3658 0,
3659 "Password for read and write commands");
3660
3661 rspamd_rcl_register_worker_option (cfg,
3662 type,
3663 "ssl",
3664 rspamd_rcl_parse_struct_boolean,
3665 ctx,
3666 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx, use_ssl),
3667 0,
3668 "Unimplemented");
3669
3670 rspamd_rcl_register_worker_option (cfg,
3671 type,
3672 "ssl_cert",
3673 rspamd_rcl_parse_struct_string,
3674 ctx,
3675 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx, ssl_cert),
3676 0,
3677 "Unimplemented");
3678
3679 rspamd_rcl_register_worker_option (cfg,
3680 type,
3681 "ssl_key",
3682 rspamd_rcl_parse_struct_string,
3683 ctx,
3684 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx, ssl_key),
3685 0,
3686 "Unimplemented");
3687 rspamd_rcl_register_worker_option (cfg,
3688 type,
3689 "timeout",
3690 rspamd_rcl_parse_struct_time,
3691 ctx,
3692 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx,
3693 timeout),
3694 RSPAMD_CL_FLAG_TIME_FLOAT,
3695 "Protocol timeout");
3696
3697 rspamd_rcl_register_worker_option (cfg,
3698 type,
3699 "secure_ip",
3700 rspamd_rcl_parse_struct_ucl,
3701 ctx,
3702 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx, secure_ip),
3703 0,
3704 "List of IP addresses that are allowed for password-less access");
3705
3706 rspamd_rcl_register_worker_option (cfg,
3707 type,
3708 "trusted_ips",
3709 rspamd_rcl_parse_struct_ucl,
3710 ctx,
3711 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx, secure_ip),
3712 0,
3713 "List of IP addresses that are allowed for password-less access");
3714
3715 rspamd_rcl_register_worker_option (cfg,
3716 type,
3717 "static_dir",
3718 rspamd_rcl_parse_struct_string,
3719 ctx,
3720 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx,
3721 static_files_dir),
3722 0,
3723 "Directory for static files served by controller's HTTP server");
3724
3725 rspamd_rcl_register_worker_option (cfg,
3726 type,
3727 "keypair",
3728 rspamd_rcl_parse_struct_keypair,
3729 ctx,
3730 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx,
3731 key),
3732 0,
3733 "Encryption keypair");
3734
3735 rspamd_rcl_register_worker_option (cfg,
3736 type,
3737 "task_timeout",
3738 rspamd_rcl_parse_struct_time,
3739 ctx,
3740 G_STRUCT_OFFSET (struct rspamd_controller_worker_ctx,
3741 task_timeout),
3742 RSPAMD_CL_FLAG_TIME_FLOAT,
3743 "Maximum task processing time, default: 8.0 seconds");
3744
3745 return ctx;
3746 }
3747
3748 /* Lua bindings */
3749 LUA_FUNCTION_DEF (csession, get_ev_base);
3750 LUA_FUNCTION_DEF (csession, get_cfg);
3751 LUA_FUNCTION_DEF (csession, send_ucl);
3752 LUA_FUNCTION_DEF (csession, send_string);
3753 LUA_FUNCTION_DEF (csession, send_error);
3754
3755 static const struct luaL_reg lua_csessionlib_m[] = {
3756 LUA_INTERFACE_DEF (csession, get_ev_base),
3757 LUA_INTERFACE_DEF (csession, get_cfg),
3758 LUA_INTERFACE_DEF (csession, send_ucl),
3759 LUA_INTERFACE_DEF (csession, send_string),
3760 LUA_INTERFACE_DEF (csession, send_error),
3761 {"__tostring", rspamd_lua_class_tostring},
3762 {NULL, NULL}
3763 };
3764
3765 /* Basic functions of LUA API for worker object */
3766 static void
luaopen_controller(lua_State * L)3767 luaopen_controller (lua_State * L)
3768 {
3769 rspamd_lua_new_class (L, "rspamd{csession}", lua_csessionlib_m);
3770 lua_pop (L, 1);
3771 }
3772
3773 struct rspamd_http_connection_entry *
lua_check_controller_entry(lua_State * L,gint pos)3774 lua_check_controller_entry (lua_State * L, gint pos)
3775 {
3776 void *ud = rspamd_lua_check_udata (L, pos, "rspamd{csession}");
3777 luaL_argcheck (L, ud != NULL, pos, "'csession' expected");
3778 return ud ? *((struct rspamd_http_connection_entry **)ud) : NULL;
3779 }
3780
3781 static int
lua_csession_get_ev_base(lua_State * L)3782 lua_csession_get_ev_base (lua_State *L)
3783 {
3784 struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1);
3785 struct ev_loop **pbase;
3786 struct rspamd_controller_session *s;
3787
3788 if (c) {
3789 s = c->ud;
3790 pbase = lua_newuserdata (L, sizeof (struct ev_loop *));
3791 rspamd_lua_setclass (L, "rspamd{ev_base}", -1);
3792 *pbase = s->ctx->event_loop;
3793 }
3794 else {
3795 return luaL_error (L, "invalid arguments");
3796 }
3797
3798 return 1;
3799 }
3800
3801 static int
lua_csession_get_cfg(lua_State * L)3802 lua_csession_get_cfg (lua_State *L)
3803 {
3804 struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1);
3805 struct rspamd_config **pcfg;
3806 struct rspamd_controller_session *s;
3807
3808 if (c) {
3809 s = c->ud;
3810 pcfg = lua_newuserdata (L, sizeof (gpointer));
3811 rspamd_lua_setclass (L, "rspamd{config}", -1);
3812 *pcfg = s->ctx->cfg;
3813 }
3814 else {
3815 return luaL_error (L, "invalid arguments");
3816 }
3817
3818 return 1;
3819 }
3820
3821 static int
lua_csession_send_ucl(lua_State * L)3822 lua_csession_send_ucl (lua_State *L)
3823 {
3824 struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1);
3825 ucl_object_t *obj = ucl_object_lua_import_escape (L, 2);
3826
3827 if (c) {
3828 rspamd_controller_send_ucl (c, obj);
3829 }
3830 else {
3831 ucl_object_unref (obj);
3832 return luaL_error (L, "invalid arguments");
3833 }
3834
3835 ucl_object_unref (obj);
3836
3837 return 0;
3838 }
3839
3840 static int
lua_csession_send_error(lua_State * L)3841 lua_csession_send_error (lua_State *L)
3842 {
3843 struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1);
3844 guint err_code = lua_tonumber (L, 2);
3845 const gchar *err_str = lua_tostring (L, 3);
3846
3847 if (c) {
3848 rspamd_controller_send_error (c, err_code, "%s", err_str);
3849 }
3850 else {
3851 return luaL_error (L, "invalid arguments");
3852 }
3853
3854 return 0;
3855 }
3856
3857 static int
lua_csession_send_string(lua_State * L)3858 lua_csession_send_string (lua_State *L)
3859 {
3860 struct rspamd_http_connection_entry *c = lua_check_controller_entry (L, 1);
3861 const gchar *str = lua_tostring (L, 2);
3862
3863 if (c) {
3864 rspamd_controller_send_string (c, str);
3865 }
3866 else {
3867 return luaL_error (L, "invalid arguments");
3868 }
3869
3870 return 0;
3871 }
3872
3873 static void
rspamd_plugin_cbdata_dtor(gpointer p)3874 rspamd_plugin_cbdata_dtor (gpointer p)
3875 {
3876 struct rspamd_controller_plugin_cbdata *cbd = p;
3877
3878 g_free (cbd->plugin);
3879 ucl_object_unref (cbd->obj); /* This also releases lua references */
3880 g_free (cbd);
3881 }
3882
3883 static void
rspamd_controller_register_plugin_path(lua_State * L,struct rspamd_controller_worker_ctx * ctx,const ucl_object_t * webui_data,const ucl_object_t * handler,const gchar * path,const gchar * plugin_name)3884 rspamd_controller_register_plugin_path (lua_State *L,
3885 struct rspamd_controller_worker_ctx *ctx,
3886 const ucl_object_t *webui_data,
3887 const ucl_object_t *handler,
3888 const gchar *path,
3889 const gchar *plugin_name)
3890 {
3891 struct rspamd_controller_plugin_cbdata *cbd;
3892 const ucl_object_t *elt;
3893 rspamd_fstring_t *full_path;
3894
3895 cbd = g_malloc0 (sizeof (*cbd));
3896 cbd->L = L;
3897 cbd->ctx = ctx;
3898 cbd->handler = ucl_object_toclosure (handler);
3899 cbd->plugin = g_strdup (plugin_name);
3900 cbd->obj = ucl_object_ref (webui_data);
3901
3902 elt = ucl_object_lookup (webui_data, "version");
3903
3904 if (elt) {
3905 cbd->version = ucl_object_toint (elt);
3906 }
3907
3908 elt = ucl_object_lookup (webui_data, "enable");
3909
3910 if (elt && ucl_object_toboolean (elt)) {
3911 cbd->is_enable = TRUE;
3912 }
3913
3914 elt = ucl_object_lookup (webui_data, "need_task");
3915
3916 if (elt && !!ucl_object_toboolean (elt)) {
3917 cbd->need_task = TRUE;
3918 }
3919
3920 full_path = rspamd_fstring_new_init ("/plugins/", sizeof ("/plugins/") - 1);
3921 /* Zero terminated */
3922 rspamd_printf_fstring (&full_path, "%s/%s%c",
3923 plugin_name, path, '\0');
3924
3925 rspamd_http_router_add_path (ctx->http,
3926 full_path->str,
3927 rspamd_controller_handle_lua_plugin);
3928 rspamd_ftok_t *key_tok = rspamd_ftok_map (full_path);
3929 /* Truncate stupid \0 symbol to enable lookup */
3930 key_tok->len --;
3931 g_hash_table_insert (ctx->plugins, key_tok, cbd);
3932 }
3933
3934 static void
rspamd_controller_register_plugins_paths(struct rspamd_controller_worker_ctx * ctx)3935 rspamd_controller_register_plugins_paths (struct rspamd_controller_worker_ctx *ctx)
3936 {
3937 lua_State *L = ctx->cfg->lua_state;
3938 ucl_object_t *webui_data;
3939 const ucl_object_t *handler_obj, *cur;
3940 ucl_object_iter_t it = NULL;
3941
3942 lua_getglobal (L, "rspamd_plugins");
3943
3944 if (lua_istable (L, -1)) {
3945
3946 for (lua_pushnil (L); lua_next (L, -2); lua_pop (L, 2)) {
3947 lua_pushvalue (L, -2); /* Store key */
3948
3949 lua_pushstring (L, "webui");
3950 lua_gettable (L, -3); /* value is at -3 index */
3951
3952 if (lua_istable (L, -1)) {
3953 webui_data = ucl_object_lua_import_escape (L, -1);
3954
3955 while ((cur = ucl_object_iterate (webui_data, &it, true)) != NULL) {
3956 handler_obj = ucl_object_lookup (cur, "handler");
3957
3958 if (handler_obj && ucl_object_key (cur)) {
3959 rspamd_controller_register_plugin_path (L, ctx,
3960 cur, handler_obj, ucl_object_key (cur),
3961 lua_tostring (L, -2));
3962 }
3963 else {
3964 msg_err_ctx ("bad webui definition for plugin: %s",
3965 lua_tostring (L, -2));
3966 }
3967 }
3968
3969 ucl_object_unref (webui_data);
3970 }
3971
3972 lua_pop (L, 1); /* remove table value */
3973 }
3974 }
3975
3976 lua_pop (L, 1); /* rspamd_plugins global */
3977 }
3978
3979 static void
rspamd_controller_health_rep(struct rspamd_worker * worker,struct rspamd_srv_reply * rep,gint rep_fd,gpointer ud)3980 rspamd_controller_health_rep (struct rspamd_worker *worker,
3981 struct rspamd_srv_reply *rep, gint rep_fd,
3982 gpointer ud)
3983 {
3984 struct rspamd_controller_worker_ctx *ctx = (struct rspamd_controller_worker_ctx *)ud;
3985
3986 ctx->workers_count = rep->reply.health.workers_count;
3987 ctx->scanners_count = rep->reply.health.scanners_count;
3988 ctx->workers_hb_lost = rep->reply.health.workers_hb_lost;
3989
3990 ev_timer_again (ctx->event_loop, &ctx->health_check_timer);
3991 }
3992
3993 static void
rspamd_controller_health_timer(EV_P_ ev_timer * w,int revents)3994 rspamd_controller_health_timer (EV_P_ ev_timer *w, int revents)
3995 {
3996 struct rspamd_controller_worker_ctx *ctx = (struct rspamd_controller_worker_ctx *)w->data;
3997 struct rspamd_srv_command srv_cmd;
3998
3999 memset (&srv_cmd, 0, sizeof (srv_cmd));
4000 srv_cmd.type = RSPAMD_SRV_HEALTH;
4001 rspamd_srv_send_command (ctx->worker, ctx->event_loop, &srv_cmd, -1,
4002 rspamd_controller_health_rep, ctx);
4003 ev_timer_stop (EV_A_ w);
4004 }
4005
4006 /*
4007 * Start worker process
4008 */
4009 __attribute__((noreturn))
4010 void
start_controller_worker(struct rspamd_worker * worker)4011 start_controller_worker (struct rspamd_worker *worker)
4012 {
4013 struct rspamd_controller_worker_ctx *ctx = worker->ctx;
4014 struct module_ctx *mctx;
4015 GHashTableIter iter;
4016 gpointer key, value;
4017 guint i;
4018 gpointer m;
4019
4020 g_assert (rspamd_worker_check_context (worker->ctx, rspamd_controller_ctx_magic));
4021 ctx->event_loop = rspamd_prepare_worker (worker,
4022 "controller",
4023 rspamd_controller_accept_socket);
4024
4025 ctx->start_time = ev_time ();
4026 ctx->worker = worker;
4027 ctx->cfg = worker->srv->cfg;
4028 ctx->srv = worker->srv;
4029 ctx->custom_commands = g_hash_table_new (rspamd_strcase_hash,
4030 rspamd_strcase_equal);
4031 ctx->plugins = g_hash_table_new_full (rspamd_ftok_icase_hash,
4032 rspamd_ftok_icase_equal, rspamd_fstring_mapped_ftok_free,
4033 rspamd_plugin_cbdata_dtor);
4034
4035 if (isnan (ctx->task_timeout)) {
4036 if (isnan (ctx->cfg->task_timeout)) {
4037 ctx->task_timeout = 0;
4038 }
4039 else {
4040 ctx->task_timeout = ctx->cfg->task_timeout;
4041 }
4042 }
4043
4044 if (ctx->secure_ip != NULL) {
4045 rspamd_config_radix_from_ucl (ctx->cfg, ctx->secure_ip,
4046 "Allow unauthenticated requests from these addresses",
4047 &ctx->secure_map,
4048 NULL,
4049 worker, "controller secure ip");
4050 }
4051
4052 ctx->lang_det = ctx->cfg->lang_det;
4053
4054 rspamd_controller_password_sane (ctx, ctx->password, "normal password");
4055 rspamd_controller_password_sane (ctx, ctx->enable_password, "enable "
4056 "password");
4057
4058 /* Accept event */
4059 ctx->http_ctx = rspamd_http_context_create (ctx->cfg, ctx->event_loop,
4060 ctx->cfg->ups_ctx);
4061 rspamd_mempool_add_destructor (ctx->cfg->cfg_pool,
4062 (rspamd_mempool_destruct_t)rspamd_http_context_free,
4063 ctx->http_ctx);
4064 ctx->http = rspamd_http_router_new (rspamd_controller_error_handler,
4065 rspamd_controller_finish_handler, ctx->timeout,
4066 ctx->static_files_dir, ctx->http_ctx);
4067
4068 /* Add callbacks for different methods */
4069 rspamd_http_router_add_path (ctx->http,
4070 PATH_AUTH,
4071 rspamd_controller_handle_auth);
4072 rspamd_http_router_add_path (ctx->http,
4073 PATH_SYMBOLS,
4074 rspamd_controller_handle_symbols);
4075 rspamd_http_router_add_path (ctx->http,
4076 PATH_ACTIONS,
4077 rspamd_controller_handle_actions);
4078 rspamd_http_router_add_path (ctx->http,
4079 PATH_MAPS,
4080 rspamd_controller_handle_maps);
4081 rspamd_http_router_add_path (ctx->http,
4082 PATH_GET_MAP,
4083 rspamd_controller_handle_get_map);
4084 rspamd_http_router_add_path (ctx->http,
4085 PATH_PIE_CHART,
4086 rspamd_controller_handle_pie_chart);
4087 rspamd_http_router_add_path (ctx->http,
4088 PATH_GRAPH,
4089 rspamd_controller_handle_graph);
4090 rspamd_http_router_add_path (ctx->http,
4091 PATH_HEALTHY,
4092 rspamd_controller_handle_healthy);
4093 rspamd_http_router_add_path (ctx->http,
4094 PATH_READY,
4095 rspamd_controller_handle_ready);
4096 rspamd_http_router_add_path (ctx->http,
4097 PATH_HISTORY,
4098 rspamd_controller_handle_history);
4099 rspamd_http_router_add_path (ctx->http,
4100 PATH_HISTORY_RESET,
4101 rspamd_controller_handle_history_reset);
4102 rspamd_http_router_add_path (ctx->http,
4103 PATH_LEARN_SPAM,
4104 rspamd_controller_handle_learnspam);
4105 rspamd_http_router_add_path (ctx->http,
4106 PATH_LEARN_HAM,
4107 rspamd_controller_handle_learnham);
4108 rspamd_http_router_add_path (ctx->http,
4109 PATH_METRICS,
4110 rspamd_controller_handle_metrics);
4111 rspamd_http_router_add_path (ctx->http,
4112 PATH_SAVE_ACTIONS,
4113 rspamd_controller_handle_saveactions);
4114 rspamd_http_router_add_path (ctx->http,
4115 PATH_SAVE_SYMBOLS,
4116 rspamd_controller_handle_savesymbols);
4117 rspamd_http_router_add_path (ctx->http,
4118 PATH_SAVE_MAP,
4119 rspamd_controller_handle_savemap);
4120 rspamd_http_router_add_path (ctx->http,
4121 PATH_SCAN,
4122 rspamd_controller_handle_scan);
4123 rspamd_http_router_add_path (ctx->http,
4124 PATH_CHECK,
4125 rspamd_controller_handle_scan);
4126 rspamd_http_router_add_path (ctx->http,
4127 PATH_CHECKV2,
4128 rspamd_controller_handle_scan);
4129 rspamd_http_router_add_path (ctx->http,
4130 PATH_STAT,
4131 rspamd_controller_handle_stat);
4132 rspamd_http_router_add_path (ctx->http,
4133 PATH_STAT_RESET,
4134 rspamd_controller_handle_statreset);
4135 rspamd_http_router_add_path (ctx->http,
4136 PATH_COUNTERS,
4137 rspamd_controller_handle_counters);
4138 rspamd_http_router_add_path (ctx->http,
4139 PATH_ERRORS,
4140 rspamd_controller_handle_errors);
4141 rspamd_http_router_add_path (ctx->http,
4142 PATH_NEIGHBOURS,
4143 rspamd_controller_handle_neighbours);
4144 rspamd_http_router_add_path (ctx->http,
4145 PATH_PLUGINS,
4146 rspamd_controller_handle_plugins);
4147 rspamd_http_router_add_path (ctx->http,
4148 PATH_PING,
4149 rspamd_controller_handle_ping);
4150 rspamd_controller_register_plugins_paths (ctx);
4151
4152 #if 0
4153 rspamd_regexp_t *lua_re = rspamd_regexp_new ("^/.*/.*\\.lua$", NULL, NULL);
4154 rspamd_http_router_add_regexp (ctx->http, lua_re,
4155 rspamd_controller_handle_lua);
4156 rspamd_regexp_unref (lua_re);
4157 #endif
4158 luaopen_controller (ctx->cfg->lua_state);
4159
4160 if (ctx->key) {
4161 rspamd_http_router_set_key (ctx->http, ctx->key);
4162 }
4163
4164 PTR_ARRAY_FOREACH (ctx->cfg->c_modules, i, mctx) {
4165 if (mctx->mod->module_attach_controller_func != NULL) {
4166 mctx->mod->module_attach_controller_func (mctx,
4167 ctx->custom_commands);
4168 }
4169 }
4170
4171 g_hash_table_iter_init (&iter, ctx->custom_commands);
4172 while (g_hash_table_iter_next (&iter, &key, &value)) {
4173 rspamd_http_router_add_path (ctx->http,
4174 key,
4175 rspamd_controller_handle_custom);
4176 }
4177
4178 if (worker->srv->cfg->neighbours && worker->srv->cfg->neighbours->len > 0) {
4179 rspamd_http_router_add_header (ctx->http,
4180 "Access-Control-Allow-Origin", "*");
4181 }
4182
4183 /* Disable all results caching, see #3330 */
4184 rspamd_http_router_add_header (ctx->http,
4185 "Cache-Control", "no-store");
4186
4187 rspamd_http_router_set_unknown_handler (ctx->http,
4188 rspamd_controller_handle_unknown);
4189
4190 ctx->resolver = rspamd_dns_resolver_init (worker->srv->logger,
4191 ctx->event_loop,
4192 worker->srv->cfg);
4193
4194 rspamd_upstreams_library_config (worker->srv->cfg, worker->srv->cfg->ups_ctx,
4195 ctx->event_loop, ctx->resolver->r);
4196 rspamd_symcache_start_refresh (worker->srv->cfg->cache, ctx->event_loop,
4197 worker);
4198 rspamd_stat_init (worker->srv->cfg, ctx->event_loop);
4199 rspamd_worker_init_controller (worker, &ctx->rrd);
4200 rspamd_lua_run_postloads (ctx->cfg->lua_state, ctx->cfg, ctx->event_loop, worker);
4201
4202 /* TODO: maybe make it configurable */
4203 ev_timer_init (&ctx->health_check_timer, rspamd_controller_health_timer,
4204 1.0, 60.0);
4205 ctx->health_check_timer.data = ctx;
4206 ev_timer_start (ctx->event_loop, &ctx->health_check_timer);
4207
4208 #ifdef WITH_HYPERSCAN
4209 rspamd_control_worker_add_cmd_handler (worker,
4210 RSPAMD_CONTROL_HYPERSCAN_LOADED,
4211 rspamd_worker_hyperscan_ready,
4212 NULL);
4213 #endif
4214
4215 /* Start event loop */
4216 ev_loop (ctx->event_loop, 0);
4217 rspamd_worker_block_signals ();
4218 rspamd_controller_on_terminate (worker, ctx->rrd);
4219
4220 rspamd_stat_close ();
4221 rspamd_http_router_free (ctx->http);
4222
4223 if (ctx->cached_password.len > 0) {
4224 m = (gpointer)ctx->cached_password.begin;
4225 munmap (m, ctx->cached_password.len);
4226 }
4227
4228 if (ctx->cached_enable_password.len > 0) {
4229 m = (gpointer) ctx->cached_enable_password.begin;
4230 munmap (m, ctx->cached_enable_password.len);
4231 }
4232
4233 g_hash_table_unref (ctx->plugins);
4234 g_hash_table_unref (ctx->custom_commands);
4235
4236 REF_RELEASE (ctx->cfg);
4237 rspamd_log_close (worker->srv->logger);
4238
4239 exit (EXIT_SUCCESS);
4240 }
4241