You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

scan_result.c 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  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 "mem_pool.h"
  18. #include "scan_result.h"
  19. #include "rspamd.h"
  20. #include "message.h"
  21. #include "lua/lua_common.h"
  22. #include "libserver/cfg_file_private.h"
  23. #include "libmime/scan_result_private.h"
  24. #include "contrib/fastutf8/fastutf8.h"
  25. #include <math.h>
  26. #include "contrib/uthash/utlist.h"
  27. #define msg_debug_metric(...) rspamd_conditional_debug_fast (NULL, NULL, \
  28. rspamd_metric_log_id, "metric", task->task_pool->tag.uid, \
  29. G_STRFUNC, \
  30. __VA_ARGS__)
  31. INIT_LOG_MODULE(metric)
  32. /* Average symbols count to optimize hash allocation */
  33. static struct rspamd_counter_data symbols_count;
  34. static void
  35. rspamd_scan_result_dtor (gpointer d)
  36. {
  37. struct rspamd_scan_result *r = (struct rspamd_scan_result *)d;
  38. struct rspamd_symbol_result *sres;
  39. rspamd_set_counter_ema (&symbols_count, kh_size (r->symbols), 0.5);
  40. if (r->symbol_cbref != -1) {
  41. luaL_unref (r->task->cfg->lua_state, LUA_REGISTRYINDEX, r->symbol_cbref);
  42. }
  43. kh_foreach_value (r->symbols, sres, {
  44. if (sres->options) {
  45. kh_destroy (rspamd_options_hash, sres->options);
  46. }
  47. });
  48. kh_destroy (rspamd_symbols_hash, r->symbols);
  49. kh_destroy (rspamd_symbols_group_hash, r->sym_groups);
  50. }
  51. struct rspamd_scan_result *
  52. rspamd_create_metric_result (struct rspamd_task *task,
  53. const gchar *name, gint lua_sym_cbref)
  54. {
  55. struct rspamd_scan_result *metric_res;
  56. guint i;
  57. metric_res = rspamd_mempool_alloc0 (task->task_pool,
  58. sizeof (struct rspamd_scan_result));
  59. metric_res->symbols = kh_init (rspamd_symbols_hash);
  60. metric_res->sym_groups = kh_init (rspamd_symbols_group_hash);
  61. if (name) {
  62. metric_res->name = rspamd_mempool_strdup (task->task_pool, name);
  63. }
  64. else {
  65. metric_res->name = NULL;
  66. }
  67. metric_res->symbol_cbref = lua_sym_cbref;
  68. metric_res->task = task;
  69. /* Optimize allocation */
  70. kh_resize (rspamd_symbols_group_hash, metric_res->sym_groups, 4);
  71. if (symbols_count.mean > 4) {
  72. kh_resize (rspamd_symbols_hash, metric_res->symbols, symbols_count.mean);
  73. }
  74. else {
  75. kh_resize (rspamd_symbols_hash, metric_res->symbols, 4);
  76. }
  77. if (task->cfg) {
  78. struct rspamd_action *act, *tmp;
  79. metric_res->actions_limits = rspamd_mempool_alloc0 (task->task_pool,
  80. sizeof (struct rspamd_action_result) * HASH_COUNT (task->cfg->actions));
  81. i = 0;
  82. HASH_ITER (hh, task->cfg->actions, act, tmp) {
  83. if (!(act->flags & RSPAMD_ACTION_NO_THRESHOLD)) {
  84. metric_res->actions_limits[i].cur_limit = act->threshold;
  85. }
  86. metric_res->actions_limits[i].action = act;
  87. i ++;
  88. }
  89. metric_res->nactions = i;
  90. }
  91. rspamd_mempool_add_destructor (task->task_pool,
  92. rspamd_scan_result_dtor,
  93. metric_res);
  94. DL_APPEND (task->result, metric_res);
  95. return metric_res;
  96. }
  97. static inline int
  98. rspamd_pr_sort (const struct rspamd_passthrough_result *pra,
  99. const struct rspamd_passthrough_result *prb)
  100. {
  101. return prb->priority - pra->priority;
  102. }
  103. void
  104. rspamd_add_passthrough_result (struct rspamd_task *task, struct rspamd_action *action, guint priority,
  105. double target_score, const gchar *message, const gchar *module, guint flags,
  106. struct rspamd_scan_result *scan_result)
  107. {
  108. struct rspamd_passthrough_result *pr;
  109. if (scan_result == NULL) {
  110. scan_result = task->result;
  111. }
  112. pr = rspamd_mempool_alloc (task->task_pool, sizeof (*pr));
  113. pr->action = action;
  114. pr->priority = priority;
  115. pr->message = message;
  116. pr->module = module;
  117. pr->target_score = target_score;
  118. pr->flags = flags;
  119. DL_APPEND (scan_result->passthrough_result, pr);
  120. DL_SORT (scan_result->passthrough_result, rspamd_pr_sort);
  121. if (!isnan (target_score)) {
  122. msg_info_task ("<%s>: set pre-result to '%s' %s(%.2f): '%s' from %s(%d)",
  123. MESSAGE_FIELD_CHECK (task, message_id), action->name,
  124. flags & RSPAMD_PASSTHROUGH_LEAST ? "*least " : "",
  125. target_score,
  126. message, module, priority);
  127. }
  128. else {
  129. msg_info_task ("<%s>: set pre-result to '%s' %s(no score): '%s' from %s(%d)",
  130. MESSAGE_FIELD_CHECK (task, message_id), action->name,
  131. flags & RSPAMD_PASSTHROUGH_LEAST ? "*least " : "",
  132. message, module, priority);
  133. }
  134. scan_result->nresults ++;
  135. }
  136. static inline gdouble
  137. rspamd_check_group_score (struct rspamd_task *task,
  138. const gchar *symbol,
  139. struct rspamd_symbols_group *gr,
  140. gdouble *group_score,
  141. gdouble w)
  142. {
  143. if (gr != NULL && group_score && gr->max_score > 0.0 && w > 0.0) {
  144. if (*group_score >= gr->max_score && w > 0) {
  145. msg_info_task ("maximum group score %.2f for group %s has been reached,"
  146. " ignoring symbol %s with weight %.2f", gr->max_score,
  147. gr->name, symbol, w);
  148. return NAN;
  149. }
  150. else if (*group_score + w > gr->max_score) {
  151. w = gr->max_score - *group_score;
  152. }
  153. }
  154. return w;
  155. }
  156. #ifndef DBL_EPSILON
  157. #define DBL_EPSILON 2.2204460492503131e-16
  158. #endif
  159. static struct rspamd_symbol_result *
  160. insert_metric_result (struct rspamd_task *task,
  161. const gchar *symbol,
  162. double weight,
  163. const gchar *opt,
  164. struct rspamd_scan_result *metric_res,
  165. enum rspamd_symbol_insert_flags flags,
  166. bool *new_sym)
  167. {
  168. struct rspamd_symbol_result *s = NULL;
  169. gdouble final_score, *gr_score = NULL, next_gf = 1.0, diff;
  170. struct rspamd_symbol *sdef;
  171. struct rspamd_symbols_group *gr = NULL;
  172. const ucl_object_t *mobj, *sobj;
  173. gint max_shots, ret;
  174. guint i;
  175. khiter_t k;
  176. gboolean single = !!(flags & RSPAMD_SYMBOL_INSERT_SINGLE);
  177. gchar *sym_cpy;
  178. if (!isfinite (weight)) {
  179. msg_warn_task ("detected %s score for symbol %s, replace it with zero",
  180. isnan (weight) ? "NaN" : "infinity", symbol);
  181. weight = 0.0;
  182. }
  183. msg_debug_metric ("want to insert symbol %s, initial weight %.2f",
  184. symbol, weight);
  185. sdef = g_hash_table_lookup (task->cfg->symbols, symbol);
  186. if (sdef == NULL) {
  187. if (flags & RSPAMD_SYMBOL_INSERT_ENFORCE) {
  188. final_score = 1.0 * weight; /* Enforce static weight to 1.0 */
  189. }
  190. else {
  191. final_score = 0.0;
  192. }
  193. msg_debug_metric ("no symbol definition for %s; final multiplier %.2f",
  194. symbol, final_score);
  195. }
  196. else {
  197. if (sdef->cache_item) {
  198. /* Check if we can insert this symbol at all */
  199. if (!rspamd_symcache_is_item_allowed (task, sdef->cache_item, FALSE)) {
  200. msg_debug_metric ("symbol %s is not allowed to be inserted due to settings",
  201. symbol);
  202. return NULL;
  203. }
  204. }
  205. final_score = (*sdef->weight_ptr) * weight;
  206. PTR_ARRAY_FOREACH (sdef->groups, i, gr) {
  207. k = kh_get (rspamd_symbols_group_hash, metric_res->sym_groups, gr);
  208. if (k == kh_end (metric_res->sym_groups)) {
  209. k = kh_put (rspamd_symbols_group_hash, metric_res->sym_groups,
  210. gr, &ret);
  211. kh_value (metric_res->sym_groups, k) = 0;
  212. }
  213. }
  214. msg_debug_metric ("metric multiplier for %s is %.2f",
  215. symbol, *sdef->weight_ptr);
  216. }
  217. if (task->settings) {
  218. gdouble corr;
  219. mobj = ucl_object_lookup (task->settings, "scores");
  220. if (!mobj) {
  221. /* Legacy */
  222. mobj = task->settings;
  223. }
  224. else {
  225. msg_debug_metric ("found scores in the settings");
  226. }
  227. sobj = ucl_object_lookup (mobj, symbol);
  228. if (sobj != NULL && ucl_object_todouble_safe (sobj, &corr)) {
  229. msg_debug_metric ("settings: changed weight of symbol %s from %.2f "
  230. "to %.2f * %.2f",
  231. symbol, final_score, corr, weight);
  232. final_score = corr * weight;
  233. }
  234. }
  235. k = kh_get (rspamd_symbols_hash, metric_res->symbols, symbol);
  236. if (k != kh_end (metric_res->symbols)) {
  237. /* Existing metric score */
  238. s = kh_value (metric_res->symbols, k);
  239. if (single) {
  240. max_shots = 1;
  241. }
  242. else {
  243. if (sdef) {
  244. max_shots = sdef->nshots;
  245. }
  246. else {
  247. max_shots = task->cfg->default_max_shots;
  248. }
  249. }
  250. msg_debug_metric ("nshots: %d for symbol %s", max_shots, symbol);
  251. if (!single && (max_shots > 0 && (s->nshots >= max_shots))) {
  252. single = TRUE;
  253. }
  254. s->nshots ++;
  255. if (opt) {
  256. rspamd_task_add_result_option (task, s, opt, strlen (opt));
  257. }
  258. /* Adjust diff */
  259. if (!single) {
  260. diff = final_score;
  261. msg_debug_metric ("symbol %s can be inserted multiple times: %.2f weight",
  262. symbol, diff);
  263. }
  264. else {
  265. if (fabs (s->score) < fabs (final_score) &&
  266. signbit (s->score) == signbit (final_score)) {
  267. /* Replace less significant weight with a more significant one */
  268. diff = final_score - s->score;
  269. msg_debug_metric ("symbol %s can be inserted single time;"
  270. " weight adjusted %.2f + %.2f",
  271. symbol, s->score, diff);
  272. }
  273. else {
  274. diff = 0;
  275. }
  276. }
  277. if (diff) {
  278. /* Handle grow factor */
  279. if (metric_res->grow_factor && diff > 0) {
  280. diff *= metric_res->grow_factor;
  281. next_gf *= task->cfg->grow_factor;
  282. }
  283. else if (diff > 0) {
  284. next_gf = task->cfg->grow_factor;
  285. }
  286. msg_debug_metric ("adjust grow factor to %.2f for symbol %s (%.2f final)",
  287. next_gf, symbol, diff);
  288. if (sdef) {
  289. PTR_ARRAY_FOREACH (sdef->groups, i, gr) {
  290. gdouble cur_diff;
  291. k = kh_get (rspamd_symbols_group_hash,
  292. metric_res->sym_groups, gr);
  293. g_assert (k != kh_end (metric_res->sym_groups));
  294. gr_score = &kh_value (metric_res->sym_groups, k);
  295. cur_diff = rspamd_check_group_score (task, symbol, gr,
  296. gr_score, diff);
  297. if (isnan (cur_diff)) {
  298. /* Limit reached, do not add result */
  299. msg_debug_metric (
  300. "group limit %.2f is reached for %s when inserting symbol %s;"
  301. " drop score %.2f",
  302. *gr_score, gr->name, symbol, diff);
  303. diff = NAN;
  304. break;
  305. }
  306. else if (gr_score) {
  307. *gr_score += cur_diff;
  308. if (cur_diff < diff) {
  309. /* Reduce */
  310. msg_debug_metric (
  311. "group limit %.2f is reached for %s when inserting symbol %s;"
  312. " reduce score %.2f - %.2f",
  313. *gr_score, gr->name, symbol, diff, cur_diff);
  314. diff = cur_diff;
  315. }
  316. }
  317. }
  318. }
  319. if (!isnan (diff)) {
  320. metric_res->score += diff;
  321. metric_res->grow_factor = next_gf;
  322. if (single) {
  323. msg_debug_metric ("final score for single symbol %s = %.2f; %.2f diff",
  324. symbol, final_score, diff);
  325. s->score = final_score;
  326. } else {
  327. msg_debug_metric ("increase final score for multiple symbol %s += %.2f = %.2f",
  328. symbol, s->score, diff);
  329. s->score += diff;
  330. }
  331. }
  332. }
  333. }
  334. else {
  335. /* New result */
  336. if (new_sym) {
  337. *new_sym = true;
  338. }
  339. sym_cpy = rspamd_mempool_strdup (task->task_pool, symbol);
  340. k = kh_put (rspamd_symbols_hash, metric_res->symbols,
  341. sym_cpy, &ret);
  342. g_assert (ret > 0);
  343. s = rspamd_mempool_alloc0 (task->task_pool, sizeof (*s));
  344. kh_value (metric_res->symbols, k) = s;
  345. /* Handle grow factor */
  346. if (metric_res->grow_factor && final_score > 0) {
  347. final_score *= metric_res->grow_factor;
  348. next_gf *= task->cfg->grow_factor;
  349. }
  350. else if (final_score > 0) {
  351. next_gf = task->cfg->grow_factor;
  352. }
  353. msg_debug_metric ("adjust grow factor to %.2f for symbol %s (%.2f final)",
  354. next_gf, symbol, final_score);
  355. s->name = sym_cpy;
  356. s->sym = sdef;
  357. s->nshots = 1;
  358. if (sdef) {
  359. /* Check group limits */
  360. PTR_ARRAY_FOREACH (sdef->groups, i, gr) {
  361. gdouble cur_score;
  362. k = kh_get (rspamd_symbols_group_hash, metric_res->sym_groups, gr);
  363. g_assert (k != kh_end (metric_res->sym_groups));
  364. gr_score = &kh_value (metric_res->sym_groups, k);
  365. cur_score = rspamd_check_group_score (task, symbol, gr,
  366. gr_score, final_score);
  367. if (isnan (cur_score)) {
  368. /* Limit reached, do not add result */
  369. msg_debug_metric (
  370. "group limit %.2f is reached for %s when inserting symbol %s;"
  371. " drop score %.2f",
  372. *gr_score, gr->name, symbol, final_score);
  373. final_score = NAN;
  374. break;
  375. } else if (gr_score) {
  376. *gr_score += cur_score;
  377. if (cur_score < final_score) {
  378. /* Reduce */
  379. msg_debug_metric (
  380. "group limit %.2f is reached for %s when inserting symbol %s;"
  381. " reduce score %.2f - %.2f",
  382. *gr_score, gr->name, symbol, final_score, cur_score);
  383. final_score = cur_score;
  384. }
  385. }
  386. }
  387. }
  388. if (!isnan (final_score)) {
  389. const double epsilon = DBL_EPSILON;
  390. metric_res->score += final_score;
  391. metric_res->grow_factor = next_gf;
  392. s->score = final_score;
  393. if (final_score > epsilon) {
  394. metric_res->npositive ++;
  395. metric_res->positive_score += final_score;
  396. }
  397. else if (final_score < -epsilon) {
  398. metric_res->nnegative ++;
  399. metric_res->negative_score += fabs (final_score);
  400. }
  401. }
  402. else {
  403. s->score = 0;
  404. }
  405. if (opt) {
  406. rspamd_task_add_result_option (task, s, opt, strlen (opt));
  407. }
  408. }
  409. msg_debug_metric ("final insertion for symbol %s, score %.2f, factor: %f",
  410. symbol,
  411. s->score,
  412. final_score);
  413. metric_res->nresults ++;
  414. return s;
  415. }
  416. struct rspamd_symbol_result *
  417. rspamd_task_insert_result_full (struct rspamd_task *task,
  418. const gchar *symbol,
  419. double weight,
  420. const gchar *opt,
  421. enum rspamd_symbol_insert_flags flags,
  422. struct rspamd_scan_result *result)
  423. {
  424. struct rspamd_symbol_result *s = NULL, *ret = NULL;
  425. struct rspamd_scan_result *mres;
  426. if (task->processed_stages & (RSPAMD_TASK_STAGE_IDEMPOTENT >> 1)) {
  427. msg_err_task ("cannot insert symbol %s on idempotent phase",
  428. symbol);
  429. return NULL;
  430. }
  431. if (result == NULL) {
  432. /* Insert everywhere */
  433. DL_FOREACH (task->result, mres) {
  434. if (mres->symbol_cbref != -1) {
  435. /* Check if we can insert this symbol to this symbol result */
  436. GError *err = NULL;
  437. lua_State *L = (lua_State *) task->cfg->lua_state;
  438. if (!rspamd_lua_universal_pcall (L, mres->symbol_cbref,
  439. G_STRLOC, 1, "uss", &err,
  440. "rspamd{task}", task, symbol, mres->name ? mres->name : "default")) {
  441. msg_warn_task ("cannot call for symbol_cbref for result %s: %e",
  442. mres->name ? mres->name : "default", err);
  443. g_error_free (err);
  444. continue;
  445. }
  446. else {
  447. if (!lua_toboolean (L, -1)) {
  448. /* Skip symbol */
  449. msg_debug_metric ("skip symbol %s for result %s due to Lua return value",
  450. symbol, mres->name);
  451. lua_pop (L, 1); /* Remove result */
  452. continue;
  453. }
  454. lua_pop (L, 1); /* Remove result */
  455. }
  456. }
  457. bool new_symbol = false;
  458. s = insert_metric_result (task,
  459. symbol,
  460. weight,
  461. opt,
  462. mres,
  463. flags,
  464. &new_symbol);
  465. if (mres->name == NULL) {
  466. /* Default result */
  467. ret = s;
  468. /* Process cache item */
  469. if (s && task->cfg->cache && s->sym) {
  470. rspamd_symcache_inc_frequency (task->cfg->cache,
  471. s->sym->cache_item);
  472. }
  473. }
  474. else if (new_symbol) {
  475. /* O(N) but we normally don't have any shadow results */
  476. LL_APPEND (ret, s);
  477. }
  478. }
  479. }
  480. else {
  481. /* Specific insertion */
  482. s = insert_metric_result (task,
  483. symbol,
  484. weight,
  485. opt,
  486. result,
  487. flags,
  488. NULL);
  489. ret = s;
  490. if (result->name == NULL) {
  491. /* Process cache item */
  492. if (s && task->cfg->cache && s->sym) {
  493. rspamd_symcache_inc_frequency (task->cfg->cache,
  494. s->sym->cache_item);
  495. }
  496. }
  497. }
  498. return ret;
  499. }
  500. static gchar *
  501. rspamd_task_option_safe_copy (struct rspamd_task *task,
  502. const gchar *val,
  503. gsize vlen,
  504. gsize *outlen)
  505. {
  506. const gchar *p, *end;
  507. p = val;
  508. end = val + vlen;
  509. vlen = 0; /* Reuse */
  510. while (p < end) {
  511. if (*p & 0x80) {
  512. UChar32 uc;
  513. gint off = 0;
  514. U8_NEXT (p, off, end - p, uc);
  515. if (uc > 0) {
  516. if (u_isprint (uc)) {
  517. vlen += off;
  518. }
  519. else {
  520. /* We will replace it with 0xFFFD */
  521. vlen += MAX (off, 3);
  522. }
  523. }
  524. else {
  525. vlen += MAX (off, 3);
  526. }
  527. p += off;
  528. }
  529. else if (!g_ascii_isprint (*p)) {
  530. /* Another 0xFFFD */
  531. vlen += 3;
  532. p ++;
  533. }
  534. else {
  535. p ++;
  536. vlen ++;
  537. }
  538. }
  539. gchar *dest, *d;
  540. dest = rspamd_mempool_alloc (task->task_pool, vlen + 1);
  541. d = dest;
  542. p = val;
  543. while (p < end) {
  544. if (*p & 0x80) {
  545. UChar32 uc;
  546. gint off = 0;
  547. U8_NEXT (p, off, end - p, uc);
  548. if (uc > 0) {
  549. if (u_isprint (uc)) {
  550. memcpy (d, p, off);
  551. d += off;
  552. }
  553. else {
  554. /* We will replace it with 0xFFFD */
  555. *d++ = '\357';
  556. *d++ = '\277';
  557. *d++ = '\275';
  558. }
  559. }
  560. else {
  561. *d++ = '\357';
  562. *d++ = '\277';
  563. *d++ = '\275';
  564. }
  565. p += off;
  566. }
  567. else if (!g_ascii_isprint (*p)) {
  568. /* Another 0xFFFD */
  569. *d++ = '\357';
  570. *d++ = '\277';
  571. *d++ = '\275';
  572. p ++;
  573. }
  574. else {
  575. *d++ = *p++;
  576. }
  577. }
  578. *d = '\0';
  579. *(outlen) = d - dest;
  580. return dest;
  581. }
  582. gboolean
  583. rspamd_task_add_result_option (struct rspamd_task *task,
  584. struct rspamd_symbol_result *s,
  585. const gchar *val,
  586. gsize vlen)
  587. {
  588. struct rspamd_symbol_option *opt, srch;
  589. gboolean ret = FALSE;
  590. gchar *opt_cpy = NULL;
  591. gsize cpy_len;
  592. khiter_t k;
  593. gint r;
  594. struct rspamd_symbol_result *cur;
  595. if (s && val) {
  596. /*
  597. * Here we assume that this function is all the time called with the
  598. * symbol from the default result, not some shadow result, or
  599. * the option insertion will be wrong
  600. */
  601. LL_FOREACH (s, cur) {
  602. if (cur->opts_len < 0) {
  603. /* Cannot add more options, give up */
  604. msg_debug_task ("cannot add more options to symbol %s when adding option %s",
  605. cur->name, val);
  606. ret = FALSE;
  607. continue;
  608. }
  609. if (!cur->options) {
  610. cur->options = kh_init (rspamd_options_hash);
  611. }
  612. if (vlen + cur->opts_len > task->cfg->max_opts_len) {
  613. /* Add truncated option */
  614. msg_info_task ("cannot add more options to symbol %s when adding option %s",
  615. cur->name, val);
  616. val = "...";
  617. vlen = 3;
  618. cur->opts_len = -1;
  619. }
  620. if (!(cur->sym && (cur->sym->flags & RSPAMD_SYMBOL_FLAG_ONEPARAM)) &&
  621. kh_size (cur->options) < task->cfg->default_max_shots) {
  622. srch.option = (gchar *) val;
  623. srch.optlen = vlen;
  624. k = kh_get (rspamd_options_hash, cur->options, &srch);
  625. if (k == kh_end (cur->options)) {
  626. opt_cpy = rspamd_task_option_safe_copy (task, val, vlen, &cpy_len);
  627. if (cpy_len != vlen) {
  628. srch.option = (gchar *) opt_cpy;
  629. srch.optlen = cpy_len;
  630. k = kh_get (rspamd_options_hash, cur->options, &srch);
  631. }
  632. /* Append new options */
  633. if (k == kh_end (cur->options)) {
  634. opt = rspamd_mempool_alloc0 (task->task_pool, sizeof(*opt));
  635. opt->optlen = cpy_len;
  636. opt->option = opt_cpy;
  637. kh_put (rspamd_options_hash, cur->options, opt, &r);
  638. DL_APPEND (cur->opts_head, opt);
  639. if (s == cur) {
  640. ret = TRUE;
  641. }
  642. }
  643. }
  644. }
  645. else {
  646. /* Skip addition */
  647. if (s == cur) {
  648. ret = FALSE;
  649. }
  650. }
  651. if (ret && cur->opts_len >= 0) {
  652. cur->opts_len += vlen;
  653. }
  654. }
  655. }
  656. else if (!val) {
  657. ret = TRUE;
  658. }
  659. task->result->nresults ++;
  660. return ret;
  661. }
  662. struct rspamd_action *
  663. rspamd_check_action_metric (struct rspamd_task *task,
  664. struct rspamd_passthrough_result **ppr,
  665. struct rspamd_scan_result *scan_result)
  666. {
  667. struct rspamd_action_result *action_lim,
  668. *noaction = NULL;
  669. struct rspamd_action *selected_action = NULL, *least_action = NULL;
  670. struct rspamd_passthrough_result *pr, *sel_pr = NULL;
  671. double max_score = -(G_MAXDOUBLE), sc;
  672. int i;
  673. gboolean seen_least = FALSE;
  674. if (scan_result == NULL) {
  675. scan_result = task->result;
  676. }
  677. if (scan_result->passthrough_result != NULL) {
  678. DL_FOREACH (scan_result->passthrough_result, pr) {
  679. if (!seen_least || !(pr->flags & RSPAMD_PASSTHROUGH_LEAST)) {
  680. sc = pr->target_score;
  681. selected_action = pr->action;
  682. if (!(pr->flags & RSPAMD_PASSTHROUGH_LEAST)) {
  683. if (!isnan (sc)) {
  684. if (pr->action->action_type == METRIC_ACTION_NOACTION) {
  685. scan_result->score = MIN (sc, scan_result->score);
  686. }
  687. else {
  688. scan_result->score = sc;
  689. }
  690. }
  691. if (ppr) {
  692. *ppr = pr;
  693. }
  694. return selected_action;
  695. }
  696. else {
  697. seen_least = true;
  698. least_action = selected_action;
  699. if (isnan (sc)) {
  700. if (selected_action->flags & RSPAMD_ACTION_NO_THRESHOLD) {
  701. /*
  702. * In this case, we have a passthrough action that
  703. * is `least` action, however, there is no threshold
  704. * on it.
  705. *
  706. * Hence, we imply the following logic:
  707. *
  708. * - we leave score unchanged
  709. * - we apply passthrough no threshold action unless
  710. * score based action *is not* reject, otherwise
  711. * we apply reject action
  712. */
  713. }
  714. else {
  715. sc = selected_action->threshold;
  716. max_score = sc;
  717. sel_pr = pr;
  718. }
  719. }
  720. else {
  721. max_score = sc;
  722. sel_pr = pr;
  723. }
  724. }
  725. }
  726. }
  727. }
  728. /*
  729. * Select result by score
  730. */
  731. for (i = scan_result->nactions - 1; i >= 0; i--) {
  732. action_lim = &scan_result->actions_limits[i];
  733. sc = action_lim->cur_limit;
  734. if (action_lim->action->action_type == METRIC_ACTION_NOACTION) {
  735. noaction = action_lim;
  736. }
  737. if (isnan (sc) ||
  738. (action_lim->action->flags & (RSPAMD_ACTION_NO_THRESHOLD|RSPAMD_ACTION_HAM))) {
  739. continue;
  740. }
  741. if (scan_result->score >= sc && sc > max_score) {
  742. selected_action = action_lim->action;
  743. max_score = sc;
  744. }
  745. }
  746. if (selected_action == NULL) {
  747. selected_action = noaction->action;
  748. }
  749. if (selected_action) {
  750. if (seen_least) {
  751. /* Adjust least action */
  752. if (least_action->flags & RSPAMD_ACTION_NO_THRESHOLD) {
  753. if (selected_action->action_type != METRIC_ACTION_REJECT &&
  754. selected_action->action_type != METRIC_ACTION_DISCARD) {
  755. /* Override score based action with least action */
  756. selected_action = least_action;
  757. if (ppr) {
  758. *ppr = sel_pr;
  759. }
  760. }
  761. }
  762. else {
  763. /* Adjust score if needed */
  764. if (max_score > scan_result->score) {
  765. if (ppr) {
  766. *ppr = sel_pr;
  767. }
  768. scan_result->score = max_score;
  769. }
  770. }
  771. }
  772. return selected_action;
  773. }
  774. if (ppr) {
  775. *ppr = sel_pr;
  776. }
  777. return noaction->action;
  778. }
  779. struct rspamd_symbol_result *
  780. rspamd_task_find_symbol_result (struct rspamd_task *task, const char *sym,
  781. struct rspamd_scan_result *result)
  782. {
  783. struct rspamd_symbol_result *res = NULL;
  784. khiter_t k;
  785. if (result == NULL) {
  786. /* Use default result */
  787. result = task->result;
  788. }
  789. k = kh_get(rspamd_symbols_hash, result->symbols, sym);
  790. if (k != kh_end (result->symbols)) {
  791. res = kh_value (result->symbols, k);
  792. }
  793. return res;
  794. }
  795. struct rspamd_symbol_result* rspamd_task_remove_symbol_result (
  796. struct rspamd_task *task,
  797. const gchar *symbol,
  798. struct rspamd_scan_result *result)
  799. {
  800. struct rspamd_symbol_result *res = NULL;
  801. khiter_t k;
  802. if (result == NULL) {
  803. /* Use default result */
  804. result = task->result;
  805. }
  806. k = kh_get (rspamd_symbols_hash, result->symbols, symbol);
  807. if (k != kh_end (result->symbols)) {
  808. res = kh_value (result->symbols, k);
  809. if (!isnan (res->score)) {
  810. /* Remove score from the result */
  811. result->score -= res->score;
  812. /* Also check the group limit */
  813. if (result->sym_groups && res->sym) {
  814. struct rspamd_symbol_group *gr;
  815. gint i;
  816. PTR_ARRAY_FOREACH (res->sym->groups, i, gr) {
  817. gdouble *gr_score;
  818. k = kh_get (rspamd_symbols_group_hash,
  819. result->sym_groups, gr);
  820. if (k != kh_end (result->sym_groups)) {
  821. gr_score = &kh_value (result->sym_groups, k);
  822. if (gr_score) {
  823. *gr_score -= res->score;
  824. }
  825. }
  826. }
  827. }
  828. }
  829. kh_del (rspamd_symbols_hash, result->symbols, k);
  830. }
  831. else {
  832. return NULL;
  833. }
  834. return res;
  835. }
  836. void
  837. rspamd_task_symbol_result_foreach (struct rspamd_task *task,
  838. struct rspamd_scan_result *result, GHFunc func,
  839. gpointer ud)
  840. {
  841. const gchar *kk;
  842. struct rspamd_symbol_result *res;
  843. if (result == NULL) {
  844. /* Use default result */
  845. result = task->result;
  846. }
  847. if (func) {
  848. kh_foreach (result->symbols, kk, res, {
  849. func ((gpointer)kk, (gpointer)res, ud);
  850. });
  851. }
  852. }
  853. struct rspamd_scan_result *
  854. rspamd_find_metric_result (struct rspamd_task *task,
  855. const gchar *name)
  856. {
  857. struct rspamd_scan_result *res;
  858. if (name == NULL) {
  859. return task->result;
  860. }
  861. else if (strcmp (name, "default") == 0) {
  862. return task->result;
  863. }
  864. DL_FOREACH (task->result, res) {
  865. if (res->name && strcmp (res->name, name) == 0) {
  866. return res;
  867. }
  868. }
  869. return NULL;
  870. }