metadata_exporter.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. --[[
  2. Copyright (c) 2016, Andrew Lewis <nerf@judo.za.org>
  3. Copyright (c) 2016, Vsevolod Stakhov <vsevolod@highsecure.ru>
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. ]]--
  14. if confighelp then
  15. return
  16. end
  17. -- A plugin that pushes metadata (or whole messages) to external services
  18. local redis_params
  19. local lua_util = require "lua_util"
  20. local rspamd_http = require "rspamd_http"
  21. local rspamd_tcp = require "rspamd_tcp"
  22. local rspamd_util = require "rspamd_util"
  23. local rspamd_logger = require "rspamd_logger"
  24. local ucl = require "ucl"
  25. local E = {}
  26. local N = 'metadata_exporter'
  27. local settings = {
  28. pusher_enabled = {},
  29. pusher_format = {},
  30. pusher_select = {},
  31. mime_type = 'text/plain',
  32. defer = false,
  33. mail_from = '',
  34. mail_to = 'postmaster@localhost',
  35. helo = 'rspamd',
  36. email_template = [[From: "Rspamd" <$mail_from>
  37. To: $mail_to
  38. Subject: Spam alert
  39. Date: $date
  40. MIME-Version: 1.0
  41. Message-ID: <$our_message_id>
  42. Content-type: text/plain; charset=utf-8
  43. Content-Transfer-Encoding: 8bit
  44. Authenticated username: $user
  45. IP: $ip
  46. Queue ID: $qid
  47. SMTP FROM: $from
  48. SMTP RCPT: $rcpt
  49. MIME From: $header_from
  50. MIME To: $header_to
  51. MIME Date: $header_date
  52. Subject: $header_subject
  53. Message-ID: $message_id
  54. Action: $action
  55. Score: $score
  56. Symbols: $symbols]],
  57. }
  58. local function get_general_metadata(task, flatten, no_content)
  59. local r = {}
  60. local ip = task:get_from_ip()
  61. if ip and ip:is_valid() then
  62. r.ip = tostring(ip)
  63. else
  64. r.ip = 'unknown'
  65. end
  66. r.user = task:get_user() or 'unknown'
  67. r.qid = task:get_queue_id() or 'unknown'
  68. r.subject = task:get_subject() or 'unknown'
  69. r.action = task:get_metric_action('default')
  70. local s = task:get_metric_score('default')[1]
  71. r.score = flatten and string.format('%.2f', s) or s
  72. local rcpt = task:get_recipients('smtp')
  73. if rcpt then
  74. local l = {}
  75. for _, a in ipairs(rcpt) do
  76. table.insert(l, a['addr'])
  77. end
  78. if not flatten then
  79. r.rcpt = l
  80. else
  81. r.rcpt = table.concat(l, ', ')
  82. end
  83. else
  84. r.rcpt = 'unknown'
  85. end
  86. local from = task:get_from('smtp')
  87. if ((from or E)[1] or E).addr then
  88. r.from = from[1].addr
  89. else
  90. r.from = 'unknown'
  91. end
  92. local syminf = task:get_symbols_all()
  93. if flatten then
  94. local l = {}
  95. for _, sym in ipairs(syminf) do
  96. local txt
  97. if sym.options then
  98. local topt = table.concat(sym.options, ', ')
  99. txt = sym.name .. '(' .. string.format('%.2f', sym.score) .. ')' .. ' [' .. topt .. ']'
  100. else
  101. txt = sym.name .. '(' .. string.format('%.2f', sym.score) .. ')'
  102. end
  103. table.insert(l, txt)
  104. end
  105. r.symbols = table.concat(l, '\n\t')
  106. else
  107. r.symbols = syminf
  108. end
  109. local function process_header(name)
  110. local hdr = task:get_header_full(name)
  111. if hdr then
  112. local l = {}
  113. for _, h in ipairs(hdr) do
  114. table.insert(l, h.decoded)
  115. end
  116. if not flatten then
  117. return l
  118. else
  119. return table.concat(l, '\n')
  120. end
  121. else
  122. return 'unknown'
  123. end
  124. end
  125. if not no_content then
  126. r.header_from = process_header('from')
  127. r.header_to = process_header('to')
  128. r.header_subject = process_header('subject')
  129. r.header_date = process_header('date')
  130. r.message_id = task:get_message_id()
  131. end
  132. return r
  133. end
  134. local formatters = {
  135. default = function(task)
  136. return task:get_content()
  137. end,
  138. email_alert = function(task, rule, extra)
  139. local meta = get_general_metadata(task, true)
  140. local display_emails = {}
  141. meta.mail_from = rule.mail_from or settings.mail_from
  142. local mail_targets = rule.mail_to or settings.mail_to
  143. if type(mail_targets) ~= 'table' then
  144. table.insert(display_emails, string.format('<%s>', mail_targets))
  145. mail_targets = {[mail_targets] = true}
  146. else
  147. for _, e in ipairs(mail_targets) do
  148. table.insert(display_emails, string.format('<%s>', e))
  149. end
  150. end
  151. if rule.email_alert_sender then
  152. local x = task:get_from('smtp')
  153. if x and string.len(x[1].addr) > 0 then
  154. mail_targets[x] = true
  155. table.insert(display_emails, string.format('<%s>', x[1].addr))
  156. end
  157. end
  158. if rule.email_alert_user then
  159. local x = task:get_user()
  160. if x then
  161. mail_targets[x] = true
  162. table.insert(display_emails, string.format('<%s>', x))
  163. end
  164. end
  165. if rule.email_alert_recipients then
  166. local x = task:get_recipients('smtp')
  167. if x then
  168. for _, e in ipairs(x) do
  169. if string.len(e.addr) > 0 then
  170. mail_targets[e.addr] = true
  171. table.insert(display_emails, string.format('<%s>', e.addr))
  172. end
  173. end
  174. end
  175. end
  176. meta.mail_to = table.concat(display_emails, ', ')
  177. meta.our_message_id = rspamd_util.random_hex(12) .. '@rspamd'
  178. meta.date = rspamd_util.time_to_string(rspamd_util.get_time())
  179. return lua_util.template(rule.email_template or settings.email_template, meta), { mail_targets = mail_targets}
  180. end,
  181. json = function(task)
  182. return ucl.to_format(get_general_metadata(task), 'json-compact')
  183. end
  184. }
  185. local function is_spam(action)
  186. return (action == 'reject' or action == 'add header' or action == 'rewrite subject')
  187. end
  188. local selectors = {
  189. default = function(task)
  190. return true
  191. end,
  192. is_spam = function(task)
  193. local action = task:get_metric_action('default')
  194. return is_spam(action)
  195. end,
  196. is_spam_authed = function(task)
  197. if not task:get_user() then
  198. return false
  199. end
  200. local action = task:get_metric_action('default')
  201. return is_spam(action)
  202. end,
  203. is_reject = function(task)
  204. local action = task:get_metric_action('default')
  205. return (action == 'reject')
  206. end,
  207. is_reject_authed = function(task)
  208. if not task:get_user() then
  209. return false
  210. end
  211. local action = task:get_metric_action('default')
  212. return (action == 'reject')
  213. end,
  214. }
  215. local function maybe_defer(task, rule)
  216. if rule.defer then
  217. rspamd_logger.warnx(task, 'deferring message')
  218. task:set_pre_result('soft reject', 'deferred', N)
  219. end
  220. end
  221. local pushers = {
  222. redis_pubsub = function(task, formatted, rule)
  223. local _,ret,upstream
  224. local function redis_pub_cb(err)
  225. if err then
  226. rspamd_logger.errx(task, 'got error %s when publishing on server %s',
  227. err, upstream:get_addr())
  228. return maybe_defer(task, rule)
  229. end
  230. return true
  231. end
  232. ret,_,upstream = rspamd_redis_make_request(task,
  233. redis_params, -- connect params
  234. nil, -- hash key
  235. true, -- is write
  236. redis_pub_cb, --callback
  237. 'PUBLISH', -- command
  238. {rule.channel, formatted} -- arguments
  239. )
  240. if not ret then
  241. rspamd_logger.errx(task, 'error connecting to redis')
  242. maybe_defer(task, rule)
  243. end
  244. end,
  245. http = function(task, formatted, rule)
  246. local function http_callback(err, code)
  247. if err then
  248. rspamd_logger.errx(task, 'got error %s in http callback', err)
  249. return maybe_defer(task, rule)
  250. end
  251. if code ~= 200 then
  252. rspamd_logger.errx(task, 'got unexpected http status: %s', code)
  253. return maybe_defer(task, rule)
  254. end
  255. return true
  256. end
  257. local hdrs = {}
  258. if rule.meta_headers then
  259. local gm = get_general_metadata(task, false, true)
  260. local pfx = rule.meta_header_prefix or 'X-Rspamd-'
  261. for k, v in pairs(gm) do
  262. if type(v) == 'table' then
  263. hdrs[pfx .. k] = ucl.to_format(v, 'json-compact')
  264. else
  265. hdrs[pfx .. k] = v
  266. end
  267. end
  268. end
  269. rspamd_http.request({
  270. task=task,
  271. url=rule.url,
  272. body=formatted,
  273. callback=http_callback,
  274. mime_type=rule.mime_type or settings.mime_type,
  275. headers=hdrs,
  276. })
  277. end,
  278. send_mail = function(task, formatted, rule, extra)
  279. local function mail_cb(err, data, conn)
  280. local function no_error(merr, mdata, wantcode)
  281. wantcode = wantcode or '2'
  282. if merr then
  283. rspamd_logger.errx(task, 'got error in tcp callback: %s', merr)
  284. if conn then
  285. conn:close()
  286. end
  287. maybe_defer(task, rule)
  288. return false
  289. end
  290. if mdata then
  291. if type(mdata) ~= 'string' then
  292. mdata = tostring(mdata)
  293. end
  294. if string.sub(mdata, 1, 1) ~= wantcode then
  295. rspamd_logger.errx(task, 'got bad smtp response: %s', mdata)
  296. if conn then
  297. conn:close()
  298. end
  299. maybe_defer(task, rule)
  300. return false
  301. end
  302. else
  303. rspamd_logger.errx(task, 'no data')
  304. if conn then
  305. conn:close()
  306. end
  307. maybe_defer(task, rule)
  308. return false
  309. end
  310. return true
  311. end
  312. local function all_done_cb(merr, mdata)
  313. if conn then
  314. conn:close()
  315. end
  316. return true
  317. end
  318. local function quit_done_cb(merr, mdata)
  319. conn:add_read(all_done_cb, '\r\n')
  320. end
  321. local function quit_cb(merr, mdata)
  322. if no_error(merr, mdata) then
  323. conn:add_write(quit_done_cb, 'QUIT\r\n')
  324. end
  325. end
  326. local function pre_quit_cb(merr, mdata)
  327. if no_error(merr, '2') then
  328. conn:add_read(quit_cb, '\r\n')
  329. end
  330. end
  331. local function data_done_cb(merr, mdata)
  332. if no_error(merr, mdata, '3') then
  333. conn:add_write(pre_quit_cb, {formatted, '\r\n.\r\n'})
  334. end
  335. end
  336. local function data_cb(merr, mdata)
  337. if no_error(merr, '2') then
  338. conn:add_read(data_done_cb, '\r\n')
  339. end
  340. end
  341. local from_done_cb
  342. local function rcpt_done_cb(merr, mdata)
  343. if no_error(merr, mdata) then
  344. local k = next(extra.mail_targets)
  345. if not k then
  346. conn:add_write(data_cb, 'DATA\r\n')
  347. else
  348. from_done_cb('2', '2')
  349. end
  350. end
  351. end
  352. local function rcpt_cb(merr, mdata)
  353. if no_error(merr, '2') then
  354. conn:add_read(rcpt_done_cb, '\r\n')
  355. end
  356. end
  357. from_done_cb = function(merr, mdata)
  358. local k
  359. if extra then
  360. k = next(extra.mail_targets)
  361. else
  362. extra = {mail_targets = {}}
  363. if type(rule.mail_to) == 'string' then
  364. extra = {mail_targets = {}}
  365. k = rule.mail_to
  366. elseif type(rule.mail_to) == 'table' then
  367. for _, r in ipairs(rule.mail_to) do
  368. extra.mail_targets[r] = true
  369. end
  370. k = next(extra.mail_targets)
  371. end
  372. end
  373. extra.mail_targets[k] = nil
  374. conn:add_write(rcpt_cb, {'RCPT TO: <', k, '>\r\n'})
  375. end
  376. local function from_cb(merr, mdata)
  377. if no_error(merr, '2') then
  378. conn:add_read(from_done_cb, '\r\n')
  379. end
  380. end
  381. local function hello_done_cb(merr, mdata)
  382. if no_error(merr, mdata) then
  383. conn:add_write(from_cb, {'MAIL FROM: <', rule.mail_from or settings.mail_from, '>\r\n'})
  384. end
  385. end
  386. local function hello_cb(merr)
  387. if no_error(merr, '2') then
  388. conn:add_read(hello_done_cb, '\r\n')
  389. end
  390. end
  391. if no_error(err, data) then
  392. conn:add_write(hello_cb, {'HELO ', rule.helo or settings.helo, '\r\n'})
  393. end
  394. end
  395. rspamd_tcp.request({
  396. task = task,
  397. callback = mail_cb,
  398. stop_pattern = '\r\n',
  399. host = rule.smtp,
  400. port = rule.smtp_port or settings.smtp_port or 25,
  401. })
  402. end,
  403. }
  404. local opts = rspamd_config:get_all_opt(N)
  405. if not opts then return end
  406. local process_settings = {
  407. select = function(val)
  408. selectors.custom = assert(load(val))()
  409. end,
  410. format = function(val)
  411. formatters.custom = assert(load(val))()
  412. end,
  413. push = function(val)
  414. pushers.custom = assert(load(val))()
  415. end,
  416. custom_push = function(val)
  417. if type(val) == 'table' then
  418. for k, v in pairs(val) do
  419. pushers[k] = assert(load(v))()
  420. end
  421. end
  422. end,
  423. custom_select = function(val)
  424. if type(val) == 'table' then
  425. for k, v in pairs(val) do
  426. selectors[k] = assert(load(v))()
  427. end
  428. end
  429. end,
  430. custom_format = function(val)
  431. if type(val) == 'table' then
  432. for k, v in pairs(val) do
  433. formatters[k] = assert(load(v))()
  434. end
  435. end
  436. end,
  437. pusher_enabled = function(val)
  438. if type(val) == 'string' then
  439. if pushers[val] then
  440. settings.pusher_enabled[val] = true
  441. else
  442. rspamd_logger.errx(rspamd_config, 'Pusher type: %s is invalid', val)
  443. end
  444. elseif type(val) == 'table' then
  445. for _, v in ipairs(val) do
  446. if pushers[v] then
  447. settings.pusher_enabled[v] = true
  448. else
  449. rspamd_logger.errx(rspamd_config, 'Pusher type: %s is invalid', val)
  450. end
  451. end
  452. end
  453. end,
  454. }
  455. for k, v in pairs(opts) do
  456. local f = process_settings[k]
  457. if f then
  458. f(opts[k])
  459. else
  460. settings[k] = v
  461. end
  462. end
  463. if type(settings.rules) ~= 'table' then
  464. -- Legacy config
  465. settings.rules = {}
  466. if not next(settings.pusher_enabled) then
  467. if pushers.custom then
  468. rspamd_logger.infox(rspamd_config, 'Custom pusher implicitly enabled')
  469. settings.pusher_enabled.custom = true
  470. else
  471. -- Check legacy options
  472. if settings.url then
  473. rspamd_logger.warnx(rspamd_config, 'HTTP pusher implicitly enabled')
  474. settings.pusher_enabled.http = true
  475. end
  476. if settings.channel then
  477. rspamd_logger.warnx(rspamd_config, 'Redis Pubsub pusher implicitly enabled')
  478. settings.pusher_enabled.redis_pubsub = true
  479. end
  480. if settings.smtp and settings.mail_to then
  481. rspamd_logger.warnx(rspamd_config, 'SMTP pusher implicitly enabled')
  482. settings.pusher_enabled.send_mail = true
  483. end
  484. end
  485. end
  486. if not next(settings.pusher_enabled) then
  487. rspamd_logger.errx(rspamd_config, 'No push backend enabled')
  488. return
  489. end
  490. if settings.formatter then
  491. settings.format = formatters[settings.formatter]
  492. if not settings.format then
  493. rspamd_logger.errx(rspamd_config, 'No such formatter: %s', settings.formatter)
  494. return
  495. end
  496. end
  497. if settings.selector then
  498. settings.select = selectors[settings.selector]
  499. if not settings.select then
  500. rspamd_logger.errx(rspamd_config, 'No such selector: %s', settings.selector)
  501. return
  502. end
  503. end
  504. for k in pairs(settings.pusher_enabled) do
  505. local formatter = settings.pusher_format[k]
  506. local selector = settings.pusher_select[k]
  507. if not formatter then
  508. settings.pusher_format[k] = settings.formatter or 'default'
  509. rspamd_logger.infox(rspamd_config, 'Using default formatter for %s pusher', k)
  510. else
  511. if not formatters[formatter] then
  512. rspamd_logger.errx(rspamd_config, 'No such formatter: %s - disabling %s', formatter, k)
  513. settings.pusher_enabled.k = nil
  514. end
  515. end
  516. if not selector then
  517. settings.pusher_select[k] = settings.selector or 'default'
  518. rspamd_logger.infox(rspamd_config, 'Using default selector for %s pusher', k)
  519. else
  520. if not selectors[selector] then
  521. rspamd_logger.errx(rspamd_config, 'No such selector: %s - disabling %s', selector, k)
  522. settings.pusher_enabled.k = nil
  523. end
  524. end
  525. end
  526. if settings.pusher_enabled.redis_pubsub then
  527. redis_params = rspamd_parse_redis_server(N)
  528. if not redis_params then
  529. rspamd_logger.errx(rspamd_config, 'No redis servers are specified')
  530. settings.pusher_enabled.redis_pubsub = nil
  531. else
  532. local r = {}
  533. r.backend = 'redis_pubsub'
  534. r.channel = settings.channel
  535. r.defer = settings.defer
  536. r.selector = settings.pusher_select.redis_pubsub
  537. r.formatter = settings.pusher_format.redis_pubsub
  538. settings.rules[r.backend:upper()] = r
  539. end
  540. end
  541. if settings.pusher_enabled.http then
  542. if not settings.url then
  543. rspamd_logger.errx(rspamd_config, 'No URL is specified')
  544. settings.pusher_enabled.http = nil
  545. else
  546. local r = {}
  547. r.backend = 'http'
  548. r.url = settings.url
  549. r.mime_type = settings.mime_type
  550. r.defer = settings.defer
  551. r.selector = settings.pusher_select.http
  552. r.formatter = settings.pusher_format.http
  553. settings.rules[r.backend:upper()] = r
  554. end
  555. end
  556. if settings.pusher_enabled.send_mail then
  557. if not (settings.mail_to and settings.smtp) then
  558. rspamd_logger.errx(rspamd_config, 'No mail_to and/or smtp setting is specified')
  559. settings.pusher_enabled.send_mail = nil
  560. else
  561. local r = {}
  562. r.backend = 'send_mail'
  563. r.mail_to = settings.mail_to
  564. r.mail_from = settings.mail_from
  565. r.helo = settings.hello
  566. r.smtp = settings.smtp
  567. r.smtp_port = settings.smtp_port
  568. r.email_template = settings.email_template
  569. r.defer = settings.defer
  570. r.selector = settings.pusher_select.send_mail
  571. r.formatter = settings.pusher_format.send_mail
  572. settings.rules[r.backend:upper()] = r
  573. end
  574. end
  575. if not next(settings.pusher_enabled) then
  576. rspamd_logger.errx(rspamd_config, 'No push backend enabled')
  577. return
  578. end
  579. elseif not next(settings.rules) then
  580. lua_util.debugm(N, rspamd_config, 'No rules enabled')
  581. return
  582. end
  583. if not settings.rules or not next(settings.rules) then
  584. rspamd_logger.errx(rspamd_config, 'No rules enabled')
  585. return
  586. end
  587. local backend_required_elements = {
  588. http = {
  589. 'url',
  590. },
  591. smtp = {
  592. 'mail_to',
  593. 'smtp',
  594. },
  595. redis_pubsub = {
  596. 'channel',
  597. },
  598. }
  599. local check_element = {
  600. selector = function(k, v)
  601. if not selectors[v] then
  602. rspamd_logger.errx(rspamd_config, 'Rule %s has invalid selector %s', k, v)
  603. return false
  604. else
  605. return true
  606. end
  607. end,
  608. formatter = function(k, v)
  609. if not formatters[v] then
  610. rspamd_logger.errx(rspamd_config, 'Rule %s has invalid formatter %s', k, v)
  611. return false
  612. else
  613. return true
  614. end
  615. end,
  616. }
  617. local backend_check = {
  618. default = function(k, rule)
  619. local reqset = backend_required_elements[rule.backend]
  620. if reqset then
  621. for _, e in ipairs(reqset) do
  622. if not rule[e] then
  623. rspamd_logger.errx(rspamd_config, 'Rule %s misses required setting %s', k, e)
  624. settings.rules[k] = nil
  625. end
  626. end
  627. end
  628. for sett, v in pairs(rule) do
  629. local f = check_element[sett]
  630. if f then
  631. if not f(sett, v) then
  632. settings.rules[k] = nil
  633. end
  634. end
  635. end
  636. end,
  637. }
  638. backend_check.redis_pubsub = function(k, rule)
  639. if not redis_params then
  640. redis_params = rspamd_parse_redis_server(N)
  641. end
  642. if not redis_params then
  643. rspamd_logger.errx(rspamd_config, 'No redis servers are specified')
  644. settings.rules[k] = nil
  645. else
  646. backend_check.default(k, rule)
  647. end
  648. end
  649. setmetatable(backend_check, {
  650. __index = function()
  651. return backend_check.default
  652. end,
  653. })
  654. for k, v in pairs(settings.rules) do
  655. if type(v) == 'table' then
  656. local backend = v.backend
  657. if not backend then
  658. rspamd_logger.errx(rspamd_config, 'Rule %s has no backend', k)
  659. settings.rules[k] = nil
  660. elseif not pushers[backend] then
  661. rspamd_logger.errx(rspamd_config, 'Rule %s has invalid backend %s', k, backend)
  662. settings.rules[k] = nil
  663. else
  664. local f = backend_check[backend]
  665. f(k, v)
  666. end
  667. else
  668. rspamd_logger.errx(rspamd_config, 'Rule %s has bad type: %s', k, type(v))
  669. settings.rules[k] = nil
  670. end
  671. end
  672. local function gen_exporter(rule)
  673. return function (task)
  674. if task:has_flag('skip') then return end
  675. local selector = rule.selector or 'default'
  676. local selected = selectors[selector](task)
  677. if selected then
  678. lua_util.debugm(N, task, 'Message selected for processing')
  679. local formatter = rule.formatter or 'default'
  680. local formatted, extra = formatters[formatter](task, rule)
  681. if formatted then
  682. pushers[rule.backend](task, formatted, rule, extra)
  683. else
  684. lua_util.debugm(N, task, 'Formatter [%s] returned non-truthy value [%s]', formatter, formatted)
  685. end
  686. else
  687. lua_util.debugm(N, task, 'Selector [%s] returned non-truthy value [%s]', selector, selected)
  688. end
  689. end
  690. end
  691. if not next(settings.rules) then
  692. rspamd_logger.errx(rspamd_config, 'No rules enabled')
  693. lua_util.disable_module(N, "config")
  694. end
  695. for k, r in pairs(settings.rules) do
  696. rspamd_config:register_symbol({
  697. name = 'EXPORT_METADATA_' .. k,
  698. type = 'postfilter,idempotent',
  699. callback = gen_exporter(r),
  700. priority = 10,
  701. flags = 'empty',
  702. })
  703. end