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.

hs_helper.c 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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 "libutil/util.h"
  18. #include "libserver/cfg_file.h"
  19. #include "libserver/cfg_rcl.h"
  20. #include "libserver/worker_util.h"
  21. #include "libserver/rspamd_control.h"
  22. #include "unix-std.h"
  23. #ifdef HAVE_GLOB_H
  24. #include <glob.h>
  25. #endif
  26. static gpointer init_hs_helper (struct rspamd_config *cfg);
  27. static void start_hs_helper (struct rspamd_worker *worker);
  28. worker_t hs_helper_worker = {
  29. "hs_helper", /* Name */
  30. init_hs_helper, /* Init function */
  31. start_hs_helper, /* Start function */
  32. RSPAMD_WORKER_UNIQUE|RSPAMD_WORKER_KILLABLE|RSPAMD_WORKER_ALWAYS_START|RSPAMD_WORKER_NO_TERMINATE_DELAY,
  33. RSPAMD_WORKER_SOCKET_NONE,
  34. RSPAMD_WORKER_VER /* Version info */
  35. };
  36. static const gdouble default_max_time = 1.0;
  37. static const gdouble default_recompile_time = 60.0;
  38. static const guint64 rspamd_hs_helper_magic = 0x22d310157a2288a0ULL;
  39. /*
  40. * Worker's context
  41. */
  42. struct hs_helper_ctx {
  43. guint64 magic;
  44. /* Events base */
  45. struct ev_loop *event_loop;
  46. /* DNS resolver */
  47. struct rspamd_dns_resolver *resolver;
  48. /* Config */
  49. struct rspamd_config *cfg;
  50. /* END OF COMMON PART */
  51. gchar *hs_dir;
  52. gboolean loaded;
  53. gdouble max_time;
  54. gdouble recompile_time;
  55. ev_timer recompile_timer;
  56. };
  57. static gpointer
  58. init_hs_helper (struct rspamd_config *cfg)
  59. {
  60. struct hs_helper_ctx *ctx;
  61. GQuark type;
  62. type = g_quark_try_string ("hs_helper");
  63. ctx = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (*ctx));
  64. ctx->magic = rspamd_hs_helper_magic;
  65. ctx->cfg = cfg;
  66. ctx->hs_dir = NULL;
  67. ctx->max_time = default_max_time;
  68. ctx->recompile_time = default_recompile_time;
  69. rspamd_rcl_register_worker_option (cfg,
  70. type,
  71. "cache_dir",
  72. rspamd_rcl_parse_struct_string,
  73. ctx,
  74. G_STRUCT_OFFSET (struct hs_helper_ctx, hs_dir),
  75. 0,
  76. "Directory where to save hyperscan compiled expressions");
  77. rspamd_rcl_register_worker_option (cfg,
  78. type,
  79. "max_time",
  80. rspamd_rcl_parse_struct_time,
  81. ctx,
  82. G_STRUCT_OFFSET (struct hs_helper_ctx, max_time),
  83. RSPAMD_CL_FLAG_TIME_FLOAT,
  84. "Maximum time to wait for compilation of a single expression");
  85. rspamd_rcl_register_worker_option (cfg,
  86. type,
  87. "recompile",
  88. rspamd_rcl_parse_struct_time,
  89. ctx,
  90. G_STRUCT_OFFSET (struct hs_helper_ctx, recompile_time),
  91. RSPAMD_CL_FLAG_TIME_FLOAT,
  92. "Time between recompilation checks");
  93. rspamd_rcl_register_worker_option (cfg,
  94. type,
  95. "timeout",
  96. rspamd_rcl_parse_struct_time,
  97. ctx,
  98. G_STRUCT_OFFSET (struct hs_helper_ctx, max_time),
  99. RSPAMD_CL_FLAG_TIME_FLOAT,
  100. "Maximum time to wait for compilation of a single expression");
  101. return ctx;
  102. }
  103. /**
  104. * Clean
  105. */
  106. static gboolean
  107. rspamd_hs_helper_cleanup_dir (struct hs_helper_ctx *ctx, gboolean forced)
  108. {
  109. struct stat st;
  110. glob_t globbuf;
  111. guint len, i;
  112. gint rc;
  113. gchar *pattern;
  114. gboolean ret = TRUE;
  115. if (stat (ctx->hs_dir, &st) == -1) {
  116. msg_err ("cannot stat path %s, %s",
  117. ctx->hs_dir,
  118. strerror (errno));
  119. return FALSE;
  120. }
  121. globbuf.gl_offs = 0;
  122. len = strlen (ctx->hs_dir) + 1 + sizeof ("*.hs.new") + 2;
  123. pattern = g_malloc (len);
  124. rspamd_snprintf (pattern, len, "%s%c%s", ctx->hs_dir, G_DIR_SEPARATOR, "*.hs");
  125. if ((rc = glob (pattern, 0, NULL, &globbuf)) == 0) {
  126. for (i = 0; i < globbuf.gl_pathc; i++) {
  127. if (forced ||
  128. !rspamd_re_cache_is_valid_hyperscan_file (ctx->cfg->re_cache,
  129. globbuf.gl_pathv[i], TRUE, TRUE)) {
  130. if (unlink (globbuf.gl_pathv[i]) == -1) {
  131. msg_err ("cannot unlink %s: %s", globbuf.gl_pathv[i],
  132. strerror (errno));
  133. ret = FALSE;
  134. }
  135. }
  136. }
  137. }
  138. else if (rc != GLOB_NOMATCH) {
  139. msg_err ("glob %s failed: %s", pattern, strerror (errno));
  140. ret = FALSE;
  141. }
  142. globfree (&globbuf);
  143. memset (&globbuf, 0, sizeof (globbuf));
  144. rspamd_snprintf (pattern, len, "%s%c%s", ctx->hs_dir, G_DIR_SEPARATOR, "*.hs.new");
  145. if ((rc = glob (pattern, 0, NULL, &globbuf)) == 0) {
  146. for (i = 0; i < globbuf.gl_pathc; i++) {
  147. if (unlink (globbuf.gl_pathv[i]) == -1) {
  148. msg_err ("cannot unlink %s: %s", globbuf.gl_pathv[i],
  149. strerror (errno));
  150. ret = FALSE;
  151. }
  152. }
  153. }
  154. else if (rc != GLOB_NOMATCH) {
  155. msg_err ("glob %s failed: %s", pattern, strerror (errno));
  156. ret = FALSE;
  157. }
  158. globfree (&globbuf);
  159. g_free (pattern);
  160. return ret;
  161. }
  162. /* Bad hack, but who cares */
  163. static gboolean hack_global_forced;
  164. static void
  165. rspamd_rs_delayed_cb (EV_P_ ev_timer *w, int revents)
  166. {
  167. struct rspamd_worker *worker = (struct rspamd_worker *)w->data;
  168. static struct rspamd_srv_command srv_cmd;
  169. struct hs_helper_ctx *ctx;
  170. ctx = (struct hs_helper_ctx *)worker->ctx;
  171. memset (&srv_cmd, 0, sizeof (srv_cmd));
  172. srv_cmd.type = RSPAMD_SRV_HYPERSCAN_LOADED;
  173. rspamd_strlcpy (srv_cmd.cmd.hs_loaded.cache_dir, ctx->hs_dir,
  174. sizeof (srv_cmd.cmd.hs_loaded.cache_dir));
  175. srv_cmd.cmd.hs_loaded.forced = hack_global_forced;
  176. hack_global_forced = FALSE;
  177. rspamd_srv_send_command (worker,
  178. ctx->event_loop, &srv_cmd, -1, NULL, NULL);
  179. ev_timer_stop (EV_A_ w);
  180. g_free (w);
  181. ev_timer_again (EV_A_ &ctx->recompile_timer);
  182. }
  183. static void
  184. rspamd_rs_compile_cb (guint ncompiled, GError *err, void *cbd)
  185. {
  186. struct rspamd_worker *worker = (struct rspamd_worker *)cbd;
  187. ev_timer *tm;
  188. ev_tstamp when = 0.0;
  189. struct hs_helper_ctx *ctx;
  190. ctx = (struct hs_helper_ctx *)worker->ctx;
  191. if (ncompiled > 0) {
  192. /* Enforce update for other workers */
  193. hack_global_forced = TRUE;
  194. }
  195. /*
  196. * Do not send notification unless all other workers are started
  197. * XXX: now we just sleep for 1 seconds to ensure that
  198. */
  199. if (!ctx->loaded) {
  200. when = 1.0; /* Postpone */
  201. ctx->loaded = TRUE;
  202. msg_info ("compiled %d regular expressions to the hyperscan tree, "
  203. "postpone loaded notification for %.0f seconds to avoid races",
  204. ncompiled,
  205. when);
  206. }
  207. else {
  208. msg_info ("compiled %d regular expressions to the hyperscan tree, "
  209. "send loaded notification",
  210. ncompiled);
  211. }
  212. tm = g_malloc0 (sizeof (*tm));
  213. tm->data = (void *)worker;
  214. ev_timer_init (tm, rspamd_rs_delayed_cb, when, 0);
  215. ev_timer_start (ctx->event_loop, tm);
  216. }
  217. static gboolean
  218. rspamd_rs_compile (struct hs_helper_ctx *ctx, struct rspamd_worker *worker,
  219. gboolean forced)
  220. {
  221. if (!(ctx->cfg->libs_ctx->crypto_ctx->cpu_config & CPUID_SSSE3)) {
  222. msg_warn ("CPU doesn't have SSSE3 instructions set "
  223. "required for hyperscan, disable hyperscan compilation");
  224. return FALSE;
  225. }
  226. if (!rspamd_hs_helper_cleanup_dir (ctx, forced)) {
  227. msg_warn ("cannot cleanup cache dir '%s'", ctx->hs_dir);
  228. }
  229. hack_global_forced = forced; /* killmeplease */
  230. rspamd_re_cache_compile_hyperscan (ctx->cfg->re_cache,
  231. ctx->hs_dir, ctx->max_time, !forced,
  232. ctx->event_loop,
  233. rspamd_rs_compile_cb,
  234. (void *)worker);
  235. return TRUE;
  236. }
  237. static gboolean
  238. rspamd_hs_helper_reload (struct rspamd_main *rspamd_main,
  239. struct rspamd_worker *worker, gint fd,
  240. gint attached_fd,
  241. struct rspamd_control_command *cmd,
  242. gpointer ud)
  243. {
  244. struct rspamd_control_reply rep;
  245. struct hs_helper_ctx *ctx = ud;
  246. msg_info ("recompiling hyperscan expressions after receiving reload command");
  247. memset (&rep, 0, sizeof (rep));
  248. rep.type = RSPAMD_CONTROL_RECOMPILE;
  249. rep.reply.recompile.status = 0;
  250. /* We write reply before actual recompilation as it takes a lot of time */
  251. if (write (fd, &rep, sizeof (rep)) != sizeof (rep)) {
  252. msg_err ("cannot write reply to the control socket: %s",
  253. strerror (errno));
  254. }
  255. /* Stop recompile */
  256. ev_timer_stop (ctx->event_loop, &ctx->recompile_timer);
  257. rspamd_rs_compile (ctx, worker, TRUE);
  258. return TRUE;
  259. }
  260. static void
  261. rspamd_hs_helper_timer (EV_P_ ev_timer *w, int revents)
  262. {
  263. struct rspamd_worker *worker = (struct rspamd_worker *)w->data;
  264. struct hs_helper_ctx *ctx;
  265. double tim;
  266. ctx = worker->ctx;
  267. tim = rspamd_time_jitter (ctx->recompile_time, 0);
  268. w->repeat = tim;
  269. rspamd_rs_compile (ctx, worker, FALSE);
  270. }
  271. static void
  272. start_hs_helper (struct rspamd_worker *worker)
  273. {
  274. struct hs_helper_ctx *ctx = worker->ctx;
  275. double tim;
  276. g_assert (rspamd_worker_check_context (worker->ctx, rspamd_hs_helper_magic));
  277. ctx->cfg = worker->srv->cfg;
  278. if (ctx->hs_dir == NULL) {
  279. ctx->hs_dir = ctx->cfg->hs_cache_dir;
  280. }
  281. if (ctx->hs_dir == NULL) {
  282. ctx->hs_dir = RSPAMD_DBDIR "/";
  283. }
  284. ctx->event_loop = rspamd_prepare_worker (worker,
  285. "hs_helper",
  286. NULL);
  287. if (!rspamd_rs_compile (ctx, worker, FALSE)) {
  288. /* Tell main not to respawn more workers */
  289. exit (EXIT_SUCCESS);
  290. }
  291. rspamd_control_worker_add_cmd_handler (worker, RSPAMD_CONTROL_RECOMPILE,
  292. rspamd_hs_helper_reload, ctx);
  293. ctx->recompile_timer.data = worker;
  294. tim = rspamd_time_jitter (ctx->recompile_time, 0);
  295. ev_timer_init (&ctx->recompile_timer, rspamd_hs_helper_timer, tim, 0.0);
  296. ev_timer_start (ctx->event_loop, &ctx->recompile_timer);
  297. ev_loop (ctx->event_loop, 0);
  298. rspamd_worker_block_signals ();
  299. rspamd_log_close (worker->srv->logger);
  300. REF_RELEASE (ctx->cfg);
  301. exit (EXIT_SUCCESS);
  302. }