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.

dynamic_cfg.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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 "rspamd.h"
  18. #include "map.h"
  19. #include "filter.h"
  20. #include "dynamic_cfg.h"
  21. #include "unix-std.h"
  22. struct config_json_buf {
  23. GString *buf;
  24. struct rspamd_config *cfg;
  25. };
  26. /**
  27. * Apply configuration to the specified configuration
  28. * @param conf_metrics
  29. * @param cfg
  30. */
  31. static void
  32. apply_dynamic_conf (const ucl_object_t *top, struct rspamd_config *cfg)
  33. {
  34. gint test_act;
  35. const ucl_object_t *cur_elt, *cur_nm, *it_val;
  36. ucl_object_iter_t it = NULL;
  37. struct metric *real_metric;
  38. struct metric_action *cur_action;
  39. struct rspamd_symbol_def *s;
  40. while ((cur_elt = ucl_iterate_object (top, &it, true))) {
  41. if (ucl_object_type (cur_elt) != UCL_OBJECT) {
  42. msg_err ("loaded json array element is not an object");
  43. continue;
  44. }
  45. cur_nm = ucl_object_find_key (cur_elt, "metric");
  46. if (!cur_nm || ucl_object_type (cur_nm) != UCL_STRING) {
  47. msg_err (
  48. "loaded json metric object element has no 'metric' attribute");
  49. continue;
  50. }
  51. real_metric = g_hash_table_lookup (cfg->metrics,
  52. ucl_object_tostring (cur_nm));
  53. if (real_metric == NULL) {
  54. msg_warn ("cannot find metric %s", ucl_object_tostring (cur_nm));
  55. continue;
  56. }
  57. cur_nm = ucl_object_find_key (cur_elt, "symbols");
  58. /* Parse symbols */
  59. if (cur_nm && ucl_object_type (cur_nm) == UCL_ARRAY) {
  60. ucl_object_iter_t nit = NULL;
  61. while ((it_val = ucl_iterate_object (cur_nm, &nit, true))) {
  62. if (ucl_object_find_key (it_val, "name") &&
  63. ucl_object_find_key (it_val, "value")) {
  64. const ucl_object_t *n =
  65. ucl_object_find_key (it_val, "name");
  66. const ucl_object_t *v =
  67. ucl_object_find_key (it_val, "value");
  68. if((s = g_hash_table_lookup (real_metric->symbols,
  69. ucl_object_tostring (n))) != NULL) {
  70. *s->weight_ptr = ucl_object_todouble (v);
  71. }
  72. }
  73. else {
  74. msg_info (
  75. "json symbol object has no mandatory 'name' and 'value' attributes");
  76. }
  77. }
  78. }
  79. else {
  80. ucl_object_t *arr;
  81. arr = ucl_object_typed_new (UCL_ARRAY);
  82. ucl_object_insert_key ((ucl_object_t *)cur_elt, arr, "symbols",
  83. sizeof ("symbols") - 1, false);
  84. }
  85. cur_nm = ucl_object_find_key (cur_elt, "actions");
  86. /* Parse actions */
  87. if (cur_nm && ucl_object_type (cur_nm) == UCL_ARRAY) {
  88. ucl_object_iter_t nit = NULL;
  89. while ((it_val = ucl_iterate_object (cur_nm, &nit, true))) {
  90. if (ucl_object_find_key (it_val, "name") &&
  91. ucl_object_find_key (it_val, "value")) {
  92. if (!rspamd_action_from_str (ucl_object_tostring (
  93. ucl_object_find_key (it_val, "name")), &test_act)) {
  94. msg_err ("unknown action: %s",
  95. ucl_object_tostring (ucl_object_find_key (it_val,
  96. "name")));
  97. continue;
  98. }
  99. cur_action = &real_metric->actions[test_act];
  100. cur_action->action = test_act;
  101. cur_action->score =
  102. ucl_object_todouble (ucl_object_find_key (it_val,
  103. "value"));
  104. }
  105. else {
  106. msg_info (
  107. "json action object has no mandatory 'name' and 'value' attributes");
  108. }
  109. }
  110. }
  111. else {
  112. ucl_object_t *arr;
  113. arr = ucl_object_typed_new (UCL_ARRAY);
  114. ucl_object_insert_key ((ucl_object_t *)cur_elt, arr, "actions",
  115. sizeof ("actions") - 1, false);
  116. }
  117. }
  118. }
  119. /* Callbacks for reading json dynamic rules */
  120. gchar *
  121. json_config_read_cb (rspamd_mempool_t * pool,
  122. gchar * chunk,
  123. gint len,
  124. struct map_cb_data *data)
  125. {
  126. struct config_json_buf *jb, *pd;
  127. pd = data->prev_data;
  128. g_assert (pd != NULL);
  129. if (data->cur_data == NULL) {
  130. jb = g_slice_alloc (sizeof (*jb));
  131. jb->cfg = pd->cfg;
  132. jb->buf = pd->buf;
  133. data->cur_data = jb;
  134. }
  135. else {
  136. jb = data->cur_data;
  137. }
  138. if (jb->buf == NULL) {
  139. /* Allocate memory for buffer */
  140. jb->buf = g_string_sized_new (BUFSIZ);
  141. }
  142. g_string_append_len (jb->buf, chunk, len);
  143. return NULL;
  144. }
  145. void
  146. json_config_fin_cb (rspamd_mempool_t * pool, struct map_cb_data *data)
  147. {
  148. struct config_json_buf *jb;
  149. ucl_object_t *top;
  150. struct ucl_parser *parser;
  151. if (data->prev_data) {
  152. jb = data->prev_data;
  153. /* Clean prev data */
  154. g_slice_free1 (sizeof (*jb), jb);
  155. }
  156. /* Now parse json */
  157. if (data->cur_data) {
  158. jb = data->cur_data;
  159. }
  160. else {
  161. msg_err ("no data read");
  162. return;
  163. }
  164. if (jb->buf == NULL) {
  165. msg_err ("no data read");
  166. return;
  167. }
  168. parser = ucl_parser_new (0);
  169. if (!ucl_parser_add_chunk (parser, jb->buf->str, jb->buf->len)) {
  170. msg_err ("cannot load json data: parse error %s",
  171. ucl_parser_get_error (parser));
  172. ucl_parser_free (parser);
  173. return;
  174. }
  175. top = ucl_parser_get_object (parser);
  176. ucl_parser_free (parser);
  177. if (ucl_object_type (top) != UCL_ARRAY) {
  178. ucl_object_unref (top);
  179. msg_err ("loaded json is not an array");
  180. return;
  181. }
  182. ucl_object_unref (jb->cfg->current_dynamic_conf);
  183. apply_dynamic_conf (top, jb->cfg);
  184. jb->cfg->current_dynamic_conf = top;
  185. }
  186. /**
  187. * Init dynamic configuration using map logic and specific configuration
  188. * @param cfg config file
  189. */
  190. void
  191. init_dynamic_config (struct rspamd_config *cfg)
  192. {
  193. struct config_json_buf *jb, **pjb;
  194. if (cfg->dynamic_conf == NULL) {
  195. /* No dynamic conf has been specified, so do not try to load it */
  196. return;
  197. }
  198. /* Now try to add map with json data */
  199. jb = g_slice_alloc (sizeof (struct config_json_buf));
  200. pjb = g_malloc (sizeof (struct config_json_buf *));
  201. jb->buf = NULL;
  202. jb->cfg = cfg;
  203. *pjb = jb;
  204. cfg->current_dynamic_conf = ucl_object_typed_new (UCL_ARRAY);
  205. if (!rspamd_map_add (cfg, cfg->dynamic_conf, "Dynamic configuration map",
  206. json_config_read_cb, json_config_fin_cb, (void **)pjb)) {
  207. msg_err ("cannot add map for configuration %s", cfg->dynamic_conf);
  208. }
  209. }
  210. /**
  211. * Dump dynamic configuration to the disk
  212. * @param cfg
  213. * @return
  214. */
  215. gboolean
  216. dump_dynamic_config (struct rspamd_config *cfg)
  217. {
  218. struct stat st;
  219. gchar *dir, pathbuf[PATH_MAX];
  220. gint fd;
  221. if (cfg->dynamic_conf == NULL || cfg->current_dynamic_conf == NULL) {
  222. /* No dynamic conf has been specified, so do not try to dump it */
  223. msg_err ("cannot save dynamic conf as it is not specified");
  224. return FALSE;
  225. }
  226. dir = g_path_get_dirname (cfg->dynamic_conf);
  227. if (dir == NULL) {
  228. msg_err ("invalid path: %s", cfg->dynamic_conf);
  229. return FALSE;
  230. }
  231. if (stat (cfg->dynamic_conf, &st) == -1) {
  232. msg_debug ("%s is unavailable: %s", cfg->dynamic_conf,
  233. strerror (errno));
  234. st.st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
  235. }
  236. if (access (dir, W_OK | R_OK) == -1) {
  237. msg_warn ("%s is inaccessible: %s", dir, strerror (errno));
  238. g_free (dir);
  239. return FALSE;
  240. }
  241. rspamd_snprintf (pathbuf,
  242. sizeof (pathbuf),
  243. "%s%crconf-XXXXXX",
  244. dir,
  245. G_DIR_SEPARATOR);
  246. g_free (dir);
  247. #ifdef HAVE_MKSTEMP
  248. /* Umask is set before */
  249. fd = mkstemp (pathbuf);
  250. #else
  251. fd = g_mkstemp_full (pathbuf, O_RDWR, S_IWUSR | S_IRUSR);
  252. #endif
  253. if (fd == -1) {
  254. msg_err ("mkstemp error: %s", strerror (errno));
  255. return FALSE;
  256. }
  257. if (!ucl_object_emit_full (cfg->current_dynamic_conf, UCL_EMIT_JSON,
  258. ucl_object_emit_fd_funcs (fd))) {
  259. msg_err ("cannot emit ucl object: %s", strerror (errno));
  260. close (fd);
  261. return FALSE;
  262. }
  263. (void)unlink (cfg->dynamic_conf);
  264. /* Rename old config */
  265. if (rename (pathbuf, cfg->dynamic_conf) == -1) {
  266. msg_err ("rename error: %s", strerror (errno));
  267. close (fd);
  268. unlink (pathbuf);
  269. return FALSE;
  270. }
  271. /* Set permissions */
  272. if (chmod (cfg->dynamic_conf, st.st_mode) == -1) {
  273. msg_warn ("chmod failed: %s", strerror (errno));
  274. }
  275. close (fd);
  276. return TRUE;
  277. }
  278. static ucl_object_t*
  279. new_dynamic_metric (const gchar *metric_name, ucl_object_t *top)
  280. {
  281. ucl_object_t *metric;
  282. metric = ucl_object_typed_new (UCL_OBJECT);
  283. ucl_object_insert_key (metric, ucl_object_fromstring (metric_name),
  284. "metric", sizeof ("metric") - 1, true);
  285. ucl_object_insert_key (metric, ucl_object_typed_new (UCL_ARRAY),
  286. "actions", sizeof ("actions") - 1, false);
  287. ucl_object_insert_key (metric, ucl_object_typed_new (UCL_ARRAY),
  288. "symbols", sizeof ("symbols") - 1, false);
  289. ucl_array_append (top, metric);
  290. return metric;
  291. }
  292. static ucl_object_t *
  293. dynamic_metric_find_elt (const ucl_object_t *arr, const gchar *name)
  294. {
  295. ucl_object_iter_t it = NULL;
  296. const ucl_object_t *cur, *n;
  297. while ((cur = ucl_iterate_object (arr, &it, true)) != NULL) {
  298. if (cur->type == UCL_OBJECT) {
  299. n = ucl_object_find_key (cur, "name");
  300. if (n && n->type == UCL_STRING &&
  301. strcmp (name, ucl_object_tostring (n)) == 0) {
  302. return (ucl_object_t *)ucl_object_find_key (cur, "value");
  303. }
  304. }
  305. }
  306. return NULL;
  307. }
  308. static ucl_object_t *
  309. dynamic_metric_find_metric (const ucl_object_t *arr, const gchar *metric)
  310. {
  311. ucl_object_iter_t it = NULL;
  312. const ucl_object_t *cur, *n;
  313. while ((cur = ucl_iterate_object (arr, &it, true)) != NULL) {
  314. if (cur->type == UCL_OBJECT) {
  315. n = ucl_object_find_key (cur, "metric");
  316. if (n && n->type == UCL_STRING &&
  317. strcmp (metric, ucl_object_tostring (n)) == 0) {
  318. return (ucl_object_t *)cur;
  319. }
  320. }
  321. }
  322. return NULL;
  323. }
  324. static ucl_object_t *
  325. new_dynamic_elt (ucl_object_t *arr, const gchar *name, gdouble value)
  326. {
  327. ucl_object_t *n;
  328. n = ucl_object_typed_new (UCL_OBJECT);
  329. ucl_object_insert_key (n, ucl_object_fromstring (name), "name",
  330. sizeof ("name") - 1, false);
  331. ucl_object_insert_key (n, ucl_object_fromdouble (value), "value",
  332. sizeof ("value") - 1, false);
  333. ucl_array_append (arr, n);
  334. return n;
  335. }
  336. /**
  337. * Add symbol for specified metric
  338. * @param cfg config file object
  339. * @param metric metric's name
  340. * @param symbol symbol's name
  341. * @param value value of symbol
  342. * @return
  343. */
  344. gboolean
  345. add_dynamic_symbol (struct rspamd_config *cfg,
  346. const gchar *metric_name,
  347. const gchar *symbol,
  348. gdouble value)
  349. {
  350. ucl_object_t *metric, *syms;
  351. if (cfg->dynamic_conf == NULL) {
  352. msg_info ("dynamic conf is disabled");
  353. return FALSE;
  354. }
  355. metric = dynamic_metric_find_metric (cfg->current_dynamic_conf,
  356. metric_name);
  357. if (metric == NULL) {
  358. metric = new_dynamic_metric (metric_name, cfg->current_dynamic_conf);
  359. }
  360. syms = (ucl_object_t *)ucl_object_find_key (metric, "symbols");
  361. if (syms != NULL) {
  362. ucl_object_t *sym;
  363. sym = dynamic_metric_find_elt (syms, symbol);
  364. if (sym) {
  365. sym->value.dv = value;
  366. }
  367. else {
  368. new_dynamic_elt (syms, symbol, value);
  369. }
  370. }
  371. apply_dynamic_conf (cfg->current_dynamic_conf, cfg);
  372. return TRUE;
  373. }
  374. /**
  375. * Add action for specified metric
  376. * @param cfg config file object
  377. * @param metric metric's name
  378. * @param action action's name
  379. * @param value value of symbol
  380. * @return
  381. */
  382. gboolean
  383. add_dynamic_action (struct rspamd_config *cfg,
  384. const gchar *metric_name,
  385. guint action,
  386. gdouble value)
  387. {
  388. ucl_object_t *metric, *acts;
  389. const gchar *action_name = rspamd_action_to_str (action);
  390. if (cfg->dynamic_conf == NULL) {
  391. msg_info ("dynamic conf is disabled");
  392. return FALSE;
  393. }
  394. metric = dynamic_metric_find_metric (cfg->current_dynamic_conf,
  395. metric_name);
  396. if (metric == NULL) {
  397. metric = new_dynamic_metric (metric_name, cfg->current_dynamic_conf);
  398. }
  399. acts = (ucl_object_t *)ucl_object_find_key (metric, "actions");
  400. if (acts != NULL) {
  401. ucl_object_t *act;
  402. act = dynamic_metric_find_elt (acts, action_name);
  403. if (act) {
  404. act->value.dv = value;
  405. }
  406. else {
  407. new_dynamic_elt (acts, action_name, value);
  408. }
  409. }
  410. apply_dynamic_conf (cfg->current_dynamic_conf, cfg);
  411. return TRUE;
  412. }