rspamd.local.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. -- Load sendgrid ID validator, thanks to https://github.com/fatalbanana
  2. local rspamd_util = require 'rspamd_util'
  3. local f = '/etc/rspamd/lua/ivm-sg.lua'
  4. if rspamd_util.file_exists(f) then
  5. dofile(f)
  6. end
  7. rspamd_config.MAILCOW_AUTH = {
  8. callback = function(task)
  9. local uname = task:get_user()
  10. if uname then
  11. return 1
  12. end
  13. end
  14. }
  15. local monitoring_hosts = rspamd_config:add_map{
  16. url = "/etc/rspamd/custom/monitoring_nolog.map",
  17. description = "Monitoring hosts",
  18. type = "regexp"
  19. }
  20. rspamd_config:register_symbol({
  21. name = 'SMTP_ACCESS',
  22. type = 'postfilter',
  23. callback = function(task)
  24. local util = require("rspamd_util")
  25. local rspamd_logger = require "rspamd_logger"
  26. local rspamd_ip = require 'rspamd_ip'
  27. local uname = task:get_user()
  28. local limited_access = task:get_symbol("SMTP_LIMITED_ACCESS")
  29. if not uname then
  30. return false
  31. end
  32. if not limited_access then
  33. return false
  34. end
  35. local hash_key = 'SMTP_ALLOW_NETS_' .. uname
  36. local redis_params = rspamd_parse_redis_server('smtp_access')
  37. local ip = task:get_from_ip()
  38. if ip == nil or not ip:is_valid() then
  39. return false
  40. end
  41. local from_ip_string = tostring(ip)
  42. smtp_access_table = {from_ip_string}
  43. local maxbits = 128
  44. local minbits = 32
  45. if ip:get_version() == 4 then
  46. maxbits = 32
  47. minbits = 8
  48. end
  49. for i=maxbits,minbits,-1 do
  50. local nip = ip:apply_mask(i):to_string() .. "/" .. i
  51. table.insert(smtp_access_table, nip)
  52. end
  53. local function smtp_access_cb(err, data)
  54. if err then
  55. rspamd_logger.infox(rspamd_config, "smtp_access query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
  56. return false
  57. else
  58. rspamd_logger.infox(rspamd_config, "checking ip %s for smtp_access in %s", from_ip_string, hash_key)
  59. for k,v in pairs(data) do
  60. if (v and v ~= userdata and v == '1') then
  61. rspamd_logger.infox(rspamd_config, "found ip in smtp_access map")
  62. task:insert_result(true, 'SMTP_ACCESS', 0.0, from_ip_string)
  63. return true
  64. end
  65. end
  66. rspamd_logger.infox(rspamd_config, "couldnt find ip in smtp_access map")
  67. task:insert_result(true, 'SMTP_ACCESS', 999.0, from_ip_string)
  68. return true
  69. end
  70. end
  71. table.insert(smtp_access_table, 1, hash_key)
  72. local redis_ret_user = rspamd_redis_make_request(task,
  73. redis_params, -- connect params
  74. hash_key, -- hash key
  75. false, -- is write
  76. smtp_access_cb, --callback
  77. 'HMGET', -- command
  78. smtp_access_table -- arguments
  79. )
  80. if not redis_ret_user then
  81. rspamd_logger.infox(rspamd_config, "cannot check smtp_access redis map")
  82. end
  83. end,
  84. priority = 10
  85. })
  86. rspamd_config:register_symbol({
  87. name = 'POSTMASTER_HANDLER',
  88. type = 'prefilter',
  89. callback = function(task)
  90. local rcpts = task:get_recipients('smtp')
  91. local rspamd_logger = require "rspamd_logger"
  92. local lua_util = require "lua_util"
  93. local from = task:get_from(1)
  94. -- not applying to mails with more than one rcpt to avoid bypassing filters by addressing postmaster
  95. if rcpts and #rcpts == 1 then
  96. for _,rcpt in ipairs(rcpts) do
  97. local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
  98. if #rcpt_split == 2 then
  99. if rcpt_split[1] == 'postmaster' then
  100. task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt')
  101. return
  102. end
  103. end
  104. end
  105. end
  106. if from then
  107. for _,fr in ipairs(from) do
  108. local fr_split = rspamd_str_split(fr['addr'], '@')
  109. if #fr_split == 2 then
  110. if fr_split[1] == 'postmaster' and task:get_user() then
  111. -- no whitelist, keep signatures
  112. task:insert_result(true, 'POSTMASTER_FROM', -2500.0)
  113. return
  114. end
  115. end
  116. end
  117. end
  118. end,
  119. priority = 10
  120. })
  121. rspamd_config:register_symbol({
  122. name = 'KEEP_SPAM',
  123. type = 'prefilter',
  124. callback = function(task)
  125. local util = require("rspamd_util")
  126. local rspamd_logger = require "rspamd_logger"
  127. local rspamd_ip = require 'rspamd_ip'
  128. local uname = task:get_user()
  129. if uname then
  130. return false
  131. end
  132. local redis_params = rspamd_parse_redis_server('keep_spam')
  133. local ip = task:get_from_ip()
  134. if ip == nil or not ip:is_valid() then
  135. return false
  136. end
  137. local from_ip_string = tostring(ip)
  138. ip_check_table = {from_ip_string}
  139. local maxbits = 128
  140. local minbits = 32
  141. if ip:get_version() == 4 then
  142. maxbits = 32
  143. minbits = 8
  144. end
  145. for i=maxbits,minbits,-1 do
  146. local nip = ip:apply_mask(i):to_string() .. "/" .. i
  147. table.insert(ip_check_table, nip)
  148. end
  149. local function keep_spam_cb(err, data)
  150. if err then
  151. rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
  152. return false
  153. else
  154. for k,v in pairs(data) do
  155. if (v and v ~= userdata and v == '1') then
  156. rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
  157. task:set_pre_result('accept', 'ip matched with forward hosts')
  158. end
  159. end
  160. end
  161. end
  162. table.insert(ip_check_table, 1, 'KEEP_SPAM')
  163. local redis_ret_user = rspamd_redis_make_request(task,
  164. redis_params, -- connect params
  165. 'KEEP_SPAM', -- hash key
  166. false, -- is write
  167. keep_spam_cb, --callback
  168. 'HMGET', -- command
  169. ip_check_table -- arguments
  170. )
  171. if not redis_ret_user then
  172. rspamd_logger.infox(rspamd_config, "cannot check keep_spam redis map")
  173. end
  174. end,
  175. priority = 19
  176. })
  177. rspamd_config:register_symbol({
  178. name = 'TLS_HEADER',
  179. type = 'postfilter',
  180. callback = function(task)
  181. local rspamd_logger = require "rspamd_logger"
  182. local tls_tag = task:get_request_header('TLS-Version')
  183. if type(tls_tag) == 'nil' then
  184. task:set_milter_reply({
  185. add_headers = {['X-Last-TLS-Session-Version'] = 'None'}
  186. })
  187. else
  188. task:set_milter_reply({
  189. add_headers = {['X-Last-TLS-Session-Version'] = tostring(tls_tag)}
  190. })
  191. end
  192. end,
  193. priority = 12
  194. })
  195. rspamd_config:register_symbol({
  196. name = 'TAG_MOO',
  197. type = 'postfilter',
  198. callback = function(task)
  199. local util = require("rspamd_util")
  200. local rspamd_logger = require "rspamd_logger"
  201. local redis_params = rspamd_parse_redis_server('taghandler')
  202. local rspamd_http = require "rspamd_http"
  203. local rcpts = task:get_recipients('smtp')
  204. local lua_util = require "lua_util"
  205. local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
  206. local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
  207. if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
  208. local tag = tagged_rcpt[1].options[1]
  209. rspamd_logger.infox("found tag: %s", tag)
  210. local action = task:get_metric_action('default')
  211. rspamd_logger.infox("metric action now: %s", action)
  212. if action ~= 'no action' and action ~= 'greylist' then
  213. rspamd_logger.infox("skipping tag handler for action: %s", action)
  214. return true
  215. end
  216. local function http_callback(err_message, code, body, headers)
  217. if body ~= nil and body ~= "" then
  218. rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body)
  219. local function tag_callback_subject(err, data)
  220. if err or type(data) ~= 'string' then
  221. rspamd_logger.infox(rspamd_config, "subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
  222. local function tag_callback_subfolder(err, data)
  223. if err or type(data) ~= 'string' then
  224. rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
  225. else
  226. rspamd_logger.infox("Add X-Moo-Tag header")
  227. task:set_milter_reply({
  228. add_headers = {['X-Moo-Tag'] = 'YES'}
  229. })
  230. end
  231. end
  232. local redis_ret_subfolder = rspamd_redis_make_request(task,
  233. redis_params, -- connect params
  234. body, -- hash key
  235. false, -- is write
  236. tag_callback_subfolder, --callback
  237. 'HGET', -- command
  238. {'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
  239. )
  240. if not redis_ret_subfolder then
  241. rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
  242. end
  243. else
  244. rspamd_logger.infox("user wants subject modified for tagged mail")
  245. local sbj = task:get_header('Subject')
  246. new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
  247. task:set_milter_reply({
  248. remove_headers = {['Subject'] = 1},
  249. add_headers = {['Subject'] = new_sbj}
  250. })
  251. end
  252. end
  253. local redis_ret_subject = rspamd_redis_make_request(task,
  254. redis_params, -- connect params
  255. body, -- hash key
  256. false, -- is write
  257. tag_callback_subject, --callback
  258. 'HGET', -- command
  259. {'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
  260. )
  261. if not redis_ret_subject then
  262. rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
  263. end
  264. end
  265. end
  266. if rcpts and #rcpts == 1 then
  267. for _,rcpt in ipairs(rcpts) do
  268. local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
  269. if #rcpt_split == 2 then
  270. if rcpt_split[1] == 'postmaster' then
  271. rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
  272. else
  273. rspamd_http.request({
  274. task=task,
  275. url='http://nginx:8081/aliasexp.php',
  276. body='',
  277. callback=http_callback,
  278. headers={Rcpt=rcpt['addr']},
  279. })
  280. end
  281. end
  282. end
  283. end
  284. end
  285. end,
  286. priority = 19
  287. })
  288. rspamd_config:register_symbol({
  289. name = 'BCC',
  290. type = 'postfilter',
  291. callback = function(task)
  292. local util = require("rspamd_util")
  293. local rspamd_http = require "rspamd_http"
  294. local rspamd_logger = require "rspamd_logger"
  295. local from_table = {}
  296. local rcpt_table = {}
  297. if task:has_symbol('ENCRYPTED_CHAT') then
  298. return -- stop
  299. end
  300. local send_mail = function(task, bcc_dest)
  301. local lua_smtp = require "lua_smtp"
  302. local function sendmail_cb(ret, err)
  303. if not ret then
  304. rspamd_logger.errx(task, 'BCC SMTP ERROR: %s', err)
  305. else
  306. rspamd_logger.infox(rspamd_config, "BCC SMTP SUCCESS TO %s", bcc_dest)
  307. end
  308. end
  309. if not bcc_dest then
  310. return -- stop
  311. end
  312. lua_smtp.sendmail({
  313. task = task,
  314. host = os.getenv("IPV4_NETWORK") .. '.253',
  315. port = 591,
  316. from = task:get_from(stp)[1].addr,
  317. recipients = bcc_dest,
  318. helo = 'bcc',
  319. timeout = 10,
  320. }, task:get_content(), sendmail_cb)
  321. end
  322. -- determine from
  323. local from = task:get_from('smtp')
  324. if from then
  325. for _, a in ipairs(from) do
  326. table.insert(from_table, a['addr']) -- add this rcpt to table
  327. table.insert(from_table, '@' .. a['domain']) -- add this rcpts domain to table
  328. end
  329. else
  330. return -- stop
  331. end
  332. -- determine rcpts
  333. local rcpts = task:get_recipients('smtp')
  334. if rcpts then
  335. for _, a in ipairs(rcpts) do
  336. table.insert(rcpt_table, a['addr']) -- add this rcpt to table
  337. table.insert(rcpt_table, '@' .. a['domain']) -- add this rcpts domain to table
  338. end
  339. else
  340. return -- stop
  341. end
  342. local action = task:get_metric_action('default')
  343. rspamd_logger.infox("metric action now: %s", action)
  344. local function rcpt_callback(err_message, code, body, headers)
  345. if err_message == nil and code == 201 and body ~= nil then
  346. if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
  347. send_mail(task, body)
  348. end
  349. end
  350. end
  351. local function from_callback(err_message, code, body, headers)
  352. if err_message == nil and code == 201 and body ~= nil then
  353. if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
  354. send_mail(task, body)
  355. end
  356. end
  357. end
  358. if rcpt_table then
  359. for _,e in ipairs(rcpt_table) do
  360. rspamd_logger.infox(rspamd_config, "checking bcc for rcpt address %s", e)
  361. rspamd_http.request({
  362. task=task,
  363. url='http://nginx:8081/bcc.php',
  364. body='',
  365. callback=rcpt_callback,
  366. headers={Rcpt=e}
  367. })
  368. end
  369. end
  370. if from_table then
  371. for _,e in ipairs(from_table) do
  372. rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e)
  373. rspamd_http.request({
  374. task=task,
  375. url='http://nginx:8081/bcc.php',
  376. body='',
  377. callback=from_callback,
  378. headers={From=e}
  379. })
  380. end
  381. end
  382. return true
  383. end,
  384. priority = 20
  385. })
  386. rspamd_config:register_symbol({
  387. name = 'DYN_RL_CHECK',
  388. type = 'prefilter',
  389. callback = function(task)
  390. local util = require("rspamd_util")
  391. local redis_params = rspamd_parse_redis_server('dyn_rl')
  392. local rspamd_logger = require "rspamd_logger"
  393. local envfrom = task:get_from(1)
  394. local uname = task:get_user()
  395. if not envfrom or not uname then
  396. return false
  397. end
  398. local uname = uname:lower()
  399. local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
  400. local function redis_cb_user(err, data)
  401. if err or type(data) ~= 'string' then
  402. rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", uname, data, err)
  403. local function redis_key_cb_domain(err, data)
  404. if err or type(data) ~= 'string' then
  405. rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err)
  406. else
  407. rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data)
  408. task:insert_result('DYN_RL', 0.0, data, env_from_domain)
  409. end
  410. end
  411. local redis_ret_domain = rspamd_redis_make_request(task,
  412. redis_params, -- connect params
  413. env_from_domain, -- hash key
  414. false, -- is write
  415. redis_key_cb_domain, --callback
  416. 'HGET', -- command
  417. {'RL_VALUE', env_from_domain} -- arguments
  418. )
  419. if not redis_ret_domain then
  420. rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain")
  421. end
  422. else
  423. rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", uname, data)
  424. task:insert_result('DYN_RL', 0.0, data, uname)
  425. end
  426. end
  427. local redis_ret_user = rspamd_redis_make_request(task,
  428. redis_params, -- connect params
  429. uname, -- hash key
  430. false, -- is write
  431. redis_cb_user, --callback
  432. 'HGET', -- command
  433. {'RL_VALUE', uname} -- arguments
  434. )
  435. if not redis_ret_user then
  436. rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user")
  437. end
  438. return true
  439. end,
  440. flags = 'empty',
  441. priority = 20
  442. })
  443. rspamd_config:register_symbol({
  444. name = 'NO_LOG_STAT',
  445. type = 'postfilter',
  446. callback = function(task)
  447. local from = task:get_header('From')
  448. if from and monitoring_hosts:get_key(from) then
  449. task:set_flag('no_log')
  450. task:set_flag('no_stat')
  451. end
  452. end
  453. })