選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

misc.lua 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. --[[
  2. Copyright (c) 2011-2017, Vsevolod Stakhov <vsevolod@highsecure.ru>
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ]]--
  13. -- This is main lua config file for rspamd
  14. local E = {}
  15. local fun = require "fun"
  16. local util = require "rspamd_util"
  17. local rspamd_regexp = require "rspamd_regexp"
  18. local rspamd_lua_utils = require "lua_util"
  19. -- Different text parts
  20. rspamd_config.R_PARTS_DIFFER = {
  21. callback = function(task)
  22. local distance = task:get_mempool():get_variable('parts_distance', 'double')
  23. if distance then
  24. local nd = tonumber(distance)
  25. -- ND is relation of different words to total words
  26. if nd >= 0.5 then
  27. local tw = task:get_mempool():get_variable('total_words', 'int')
  28. if tw then
  29. local score
  30. if tw > 30 then
  31. -- We are confident about difference
  32. score = (nd - 0.5) * 2.0
  33. else
  34. -- We are not so confident about difference
  35. score = (nd - 0.5)
  36. end
  37. task:insert_result('R_PARTS_DIFFER', score,
  38. string.format('%.1f%%', tostring(100.0 * nd)))
  39. end
  40. end
  41. end
  42. return false
  43. end,
  44. score = 1.0,
  45. description = 'Text and HTML parts differ',
  46. group = 'body'
  47. }
  48. -- Date issues
  49. rspamd_config.MISSING_DATE = {
  50. callback = function(task)
  51. local date = task:get_header_raw('Date')
  52. if date == nil or date == '' then
  53. return true
  54. end
  55. return false
  56. end,
  57. score = 1.0,
  58. description = 'Message date is missing',
  59. group = 'headers'
  60. }
  61. rspamd_config.DATE_IN_FUTURE = {
  62. callback = function(task)
  63. local dm = task:get_date{format = 'message', gmt = true}
  64. local dt = task:get_date{format = 'connect', gmt = true}
  65. -- 2 hours
  66. if dm > 0 and dm - dt > 7200 then
  67. return true
  68. end
  69. return false
  70. end,
  71. score = 4.0,
  72. description = 'Message date is in future',
  73. group = 'headers'
  74. }
  75. rspamd_config.DATE_IN_PAST = {
  76. callback = function(task)
  77. local dm = task:get_date{format = 'message', gmt = true}
  78. local dt = task:get_date{format = 'connect', gmt = true}
  79. -- A day
  80. if dm > 0 and dt - dm > 86400 then
  81. return true
  82. end
  83. return false
  84. end,
  85. score = 1.0,
  86. description = 'Message date is in past',
  87. group = 'headers'
  88. }
  89. rspamd_config.R_SUSPICIOUS_URL = {
  90. callback = function(task)
  91. local urls = task:get_urls()
  92. if urls then
  93. for _,u in ipairs(urls) do
  94. if u:is_obscured() then
  95. task:insert_result('R_SUSPICIOUS_URL', 1.0, u:get_host())
  96. end
  97. end
  98. end
  99. return false
  100. end,
  101. score = 5.0,
  102. one_shot = true,
  103. description = 'Obfusicated or suspicious URL has been found in a message',
  104. group = 'url'
  105. }
  106. rspamd_config.ENVFROM_PRVS = {
  107. callback = function (task)
  108. --[[
  109. Detect PRVS/BATV addresses to avoid FORGED_SENDER
  110. https://en.wikipedia.org/wiki/Bounce_Address_Tag_Validation
  111. Signature syntax:
  112. prvs=TAG=USER@example.com BATV draft (https://tools.ietf.org/html/draft-levine-smtp-batv-01)
  113. prvs=USER=TAG@example.com
  114. btv1==TAG==USER@example.com Barracuda appliance
  115. msprvs1=TAG=USER@example.com Sparkpost email delivery service
  116. ]]--
  117. if not (task:has_from(1) and task:has_from(2)) then
  118. return false
  119. end
  120. local envfrom = task:get_from(1)
  121. local re_text = '^(?:(prvs|msprvs1)=([^=]+)=|btv1==[^=]+==)(.+@(.+))$'
  122. local re = rspamd_regexp.create_cached(re_text)
  123. local c = re:search(envfrom[1].addr:lower(), false, true)
  124. if not c then return false end
  125. local ef = c[1][4]
  126. -- See if it matches the From header
  127. local from = task:get_from(2)
  128. if ef == from[1].addr:lower() then
  129. return true
  130. end
  131. -- Check for prvs=USER=TAG@example.com
  132. local t = c[1][2]
  133. if t == 'prvs' then
  134. local efr = c[1][3] .. '@' .. c[1][5]
  135. if efr == from[1].addr:lower() then
  136. return true
  137. end
  138. end
  139. return false
  140. end,
  141. score = 0.0,
  142. description = "Envelope From is a PRVS address that matches the From address",
  143. group = 'headers'
  144. }
  145. rspamd_config.ENVFROM_VERP = {
  146. callback = function (task)
  147. if not (task:has_from(1) and task:has_recipients(1)) then
  148. return false
  149. end
  150. local envfrom = task:get_from(1)
  151. local envrcpts = task:get_recipients(1)
  152. -- VERP only works for single recipient messages
  153. if #envrcpts > 1 then return false end
  154. -- Get recipient and compute VERP address
  155. local rcpt = envrcpts[1].addr:lower()
  156. local verp = rcpt:gsub('@','=')
  157. -- Get the user portion of the envfrom
  158. local ef_user = envfrom[1].user:lower()
  159. -- See if the VERP representation of the recipient appears in it
  160. if ef_user:find(verp, 1, true)
  161. and not ef_user:find('+caf_=' .. verp, 1, true) -- Google Forwarding
  162. and not ef_user:find('^srs[01]=') -- SRS
  163. then
  164. return true
  165. end
  166. return false
  167. end,
  168. score = 0.0,
  169. description = "Envelope From is a VERP address",
  170. group = "headers"
  171. }
  172. local check_rcvd = rspamd_config:register_symbol{
  173. name = 'CHECK_RCVD',
  174. group = 'headers',
  175. callback = function (task)
  176. local rcvds = task:get_received_headers()
  177. if not rcvds then return false end
  178. local all_tls = fun.all(function(rc)
  179. return rc.flags and rc.flags['ssl']
  180. end, fun.filter(function(rc)
  181. return rc.by_hostname and rc.by_hostname ~= 'localhost'
  182. end, rcvds))
  183. -- See if only the last hop was encrypted
  184. if all_tls then
  185. task:insert_result('RCVD_TLS_ALL', 1.0)
  186. else
  187. local rcvd = rcvds[1]
  188. if rcvd.by_hostname and rcvd.by_hostname == 'localhost' then
  189. -- Ignore artificial header from Rmilter
  190. rcvd = rcvds[2] or {}
  191. end
  192. if rcvd.flags and rcvd.flags['ssl'] then
  193. task:insert_result('RCVD_TLS_LAST', 1.0)
  194. else
  195. task:insert_result('RCVD_NO_TLS_LAST', 1.0)
  196. end
  197. end
  198. local auth = fun.any(function(rc)
  199. return rc.flags and rc.flags['authenticated']
  200. end, rcvds)
  201. if auth then
  202. task:insert_result('RCVD_VIA_SMTP_AUTH', 1.0)
  203. end
  204. end
  205. }
  206. rspamd_config:register_symbol{
  207. type = 'virtual',
  208. parent = check_rcvd,
  209. name = 'RCVD_TLS_ALL',
  210. description = 'All hops used encrypted transports',
  211. score = 0.0,
  212. group = 'headers'
  213. }
  214. rspamd_config:register_symbol{
  215. type = 'virtual',
  216. parent = check_rcvd,
  217. name = 'RCVD_TLS_LAST',
  218. description = 'Last hop used encrypted transports',
  219. score = 0.0,
  220. group = 'headers'
  221. }
  222. rspamd_config:register_symbol{
  223. type = 'virtual',
  224. parent = check_rcvd,
  225. name = 'RCVD_NO_TLS_LAST',
  226. description = 'Last hop did not use encrypted transports',
  227. score = 0.1,
  228. group = 'headers'
  229. }
  230. rspamd_config:register_symbol{
  231. type = 'virtual',
  232. parent = check_rcvd,
  233. name = 'RCVD_VIA_SMTP_AUTH',
  234. -- NB This does not mean sender was authenticated; see task:get_user()
  235. description = 'Authenticated hand-off was seen in Received headers',
  236. score = 0.0,
  237. group = 'headers'
  238. }
  239. rspamd_config.RCVD_HELO_USER = {
  240. callback = function (task)
  241. -- Check HELO argument from MTA
  242. local helo = task:get_helo()
  243. if (helo and helo:lower():find('^user$')) then
  244. return true
  245. end
  246. -- Check Received headers
  247. local rcvds = task:get_header_full('Received')
  248. if not rcvds then return false end
  249. for _, rcvd in ipairs(rcvds) do
  250. local r = rcvd['decoded']:lower()
  251. if (r:find("^%s*from%suser%s")) then return true end
  252. if (r:find("helo[%s=]user[%s%)]")) then return true end
  253. end
  254. end,
  255. description = 'HELO User spam pattern',
  256. group = 'headers',
  257. score = 3.0
  258. }
  259. rspamd_config.URI_COUNT_ODD = {
  260. callback = function (task)
  261. local ct = task:get_header('Content-Type')
  262. if (ct and ct:lower():find('^multipart/alternative')) then
  263. local urls = task:get_urls() or {}
  264. local nurls = fun.filter(function(url)
  265. return not url:is_html_displayed()
  266. end, urls):foldl(function(acc, val) return acc + val:get_count() end, 0)
  267. if nurls % 2 == 1 then
  268. return true, 1.0, tostring(nurls)
  269. end
  270. end
  271. end,
  272. description = 'Odd number of URIs in multipart/alternative message',
  273. score = 1.0,
  274. group = 'url',
  275. }
  276. rspamd_config.HAS_ATTACHMENT = {
  277. callback = function (task)
  278. local parts = task:get_parts()
  279. if parts and #parts > 1 then
  280. for _, p in ipairs(parts) do
  281. local cd = p:get_header('Content-Disposition')
  282. if (cd and cd:lower():match('^attachment')) then
  283. return true
  284. end
  285. end
  286. end
  287. end,
  288. description = 'Message contains attachments',
  289. group = 'body',
  290. }
  291. -- Requires freemail maps loaded in multimap
  292. local function freemail_reply_neq_from(task)
  293. local frt = task:get_symbol('FREEMAIL_REPLYTO')
  294. local ff = task:get_symbol('FREEMAIL_FROM')
  295. if (frt and ff and frt['options'] and ff['options'] and
  296. frt['options'][1] ~= ff['options'][1])
  297. then
  298. return true
  299. end
  300. return false
  301. end
  302. rspamd_config:register_symbol({
  303. name = 'FREEMAIL_REPLYTO_NEQ_FROM_DOM',
  304. callback = freemail_reply_neq_from,
  305. description = 'Freemail From and Reply-To, but to different Freemail services',
  306. score = 3.0,
  307. group = 'headers',
  308. })
  309. rspamd_config:register_dependency('FREEMAIL_REPLYTO_NEQ_FROM_DOM', 'FREEMAIL_REPLYTO')
  310. rspamd_config:register_dependency('FREEMAIL_REPLYTO_NEQ_FROM_DOM', 'FREEMAIL_FROM')
  311. rspamd_config.OMOGRAPH_URL = {
  312. callback = function(task)
  313. local urls = task:get_urls()
  314. if urls then
  315. local bad_omographs = 0
  316. local single_bad_omograps = 0
  317. local bad_urls = {}
  318. fun.each(function(u)
  319. if u:is_phished() then
  320. local h1 = u:get_host()
  321. local h2 = u:get_phished():get_host()
  322. if h1 and h2 then
  323. if util.is_utf_spoofed(h1, h2) then
  324. table.insert(bad_urls, string.format('%s->%s', h1, h2))
  325. bad_omographs = bad_omographs + 1
  326. end
  327. end
  328. end
  329. if not u:is_html_displayed() then
  330. local h = u:get_tld()
  331. if h then
  332. if util.is_utf_spoofed(h) then
  333. table.insert(bad_urls, string.format('%s', h))
  334. single_bad_omograps = single_bad_omograps + 1
  335. end
  336. end
  337. end
  338. end, urls)
  339. if bad_omographs > 0 then
  340. return true, 1.0, bad_urls
  341. elseif single_bad_omograps > 0 then
  342. return true, 0.5, bad_urls
  343. end
  344. end
  345. return false
  346. end,
  347. score = 5.0,
  348. group = 'url',
  349. description = 'Url contains both latin and non-latin characters'
  350. }
  351. rspamd_config.URL_IN_SUBJECT = {
  352. callback = function(task)
  353. local urls = task:get_urls()
  354. if urls then
  355. for _,u in ipairs(urls) do
  356. local flags = u:get_flags()
  357. if flags.subject then
  358. if flags.schemaless then
  359. return true,0.1,u:get_host()
  360. end
  361. local subject = task:get_subject()
  362. if subject then
  363. if tostring(u) == subject then
  364. return true,1.0,u:get_host()
  365. end
  366. end
  367. return true,0.25,u:get_host()
  368. end
  369. end
  370. end
  371. return false
  372. end,
  373. score = 4.0,
  374. group = 'subject',
  375. description = 'URL found in Subject'
  376. }
  377. local aliases_id = rspamd_config:register_symbol{
  378. type = 'prefilter',
  379. name = 'EMAIL_PLUS_ALIASES',
  380. callback = function(task)
  381. local function check_from(type)
  382. if task:has_from(type) then
  383. local addr = task:get_from(type)[1]
  384. local na,tags = rspamd_lua_utils.remove_email_aliases(addr)
  385. if na then
  386. task:set_from(type, addr)
  387. task:insert_result('TAGGED_FROM', 1.0, fun.totable(
  388. fun.filter(function(t) return t and #t > 0 end, tags)))
  389. end
  390. end
  391. end
  392. check_from('smtp')
  393. check_from('mime')
  394. local function check_rcpt(type)
  395. if task:has_recipients(type) then
  396. local modified = false
  397. local all_tags = {}
  398. local addrs = task:get_recipients(type)
  399. for _, addr in ipairs(addrs) do
  400. local na,tags = rspamd_lua_utils.remove_email_aliases(addr)
  401. if na then
  402. modified = true
  403. fun.each(function(t) table.insert(all_tags, t) end,
  404. fun.filter(function(t) return t and #t > 0 end, tags))
  405. end
  406. end
  407. if modified then
  408. task:set_recipients(type, addrs)
  409. task:insert_result('TAGGED_RCPT', 1.0, all_tags)
  410. end
  411. end
  412. end
  413. check_rcpt('smtp')
  414. check_rcpt('mime')
  415. end,
  416. priority = 150,
  417. description = 'Removes plus aliases from the email',
  418. group = 'headers',
  419. }
  420. rspamd_config:register_symbol{
  421. type = 'virtual',
  422. parent = aliases_id,
  423. name = 'TAGGED_RCPT',
  424. description = 'SMTP recipients have plus tags',
  425. group = 'headers',
  426. score = 0,
  427. }
  428. rspamd_config:register_symbol{
  429. type = 'virtual',
  430. parent = aliases_id,
  431. name = 'TAGGED_FROM',
  432. description = 'SMTP from has plus tags',
  433. group = 'headers',
  434. score = 0,
  435. }
  436. local check_from_display_name = rspamd_config:register_symbol{
  437. type = 'callback',
  438. name = 'FROM_DISPLAY_CALLBACK',
  439. callback = function (task)
  440. local from = task:get_from(2)
  441. if not (from and from[1] and from[1].name) then return false end
  442. -- See if we can parse an email address from the name
  443. local parsed = util.parse_mail_address(from[1].name, task:get_mempool())
  444. if not parsed then return false end
  445. if not (parsed[1] and parsed[1]['addr']) then return false end
  446. -- Make sure we did not mistake e.g. <something>@<name> for an email address
  447. if not parsed[1]['domain'] or not parsed[1]['domain']:find('%.') then return false end
  448. -- See if the parsed domains differ
  449. if not util.strequal_caseless(from[1]['domain'], parsed[1]['domain']) then
  450. -- See if the destination domain is the same as the spoof
  451. local mto = task:get_recipients(2)
  452. local sto = task:get_recipients(1)
  453. if mto then
  454. for _, to in ipairs(mto) do
  455. if to['domain'] ~= '' and util.strequal_caseless(to['domain'], parsed[1]['domain']) then
  456. task:insert_result('SPOOF_DISPLAY_NAME', 1.0, from[1]['domain'], parsed[1]['domain'])
  457. return false
  458. end
  459. end
  460. end
  461. if sto then
  462. for _, to in ipairs(sto) do
  463. if to['domain'] ~= '' and util.strequal_caseless(to['domain'], parsed[1]['domain']) then
  464. task:insert_result('SPOOF_DISPLAY_NAME', 1.0, from[1]['domain'], parsed[1]['domain'])
  465. return false
  466. end
  467. end
  468. end
  469. task:insert_result('FROM_NEQ_DISPLAY_NAME', 1.0, from[1]['domain'], parsed[1]['domain'])
  470. end
  471. return false
  472. end,
  473. group = 'headers',
  474. }
  475. rspamd_config:register_symbol{
  476. type = 'virtual',
  477. parent = check_from_display_name,
  478. name = 'SPOOF_DISPLAY_NAME',
  479. description = 'Display name is being used to spoof and trick the recipient',
  480. group = 'headers',
  481. score = 8,
  482. }
  483. rspamd_config:register_symbol{
  484. type = 'virtual',
  485. parent = check_from_display_name,
  486. name = 'FROM_NEQ_DISPLAY_NAME',
  487. group = 'headers',
  488. description = 'Display name contains an email address different to the From address',
  489. score = 4,
  490. }
  491. rspamd_config.SPOOF_REPLYTO = {
  492. callback = function (task)
  493. -- First check for a Reply-To header
  494. local rt = task:get_header_full('Reply-To')
  495. if not rt or not rt[1] then return false end
  496. -- Get From and To headers
  497. rt = rt[1]['value']
  498. local from = task:get_from(2)
  499. local to = task:get_recipients(2)
  500. if not (from and from[1] and from[1].addr) then return false end
  501. if (to and to[1] and to[1].addr) then
  502. -- Handle common case for Web Contact forms of From = To
  503. if util.strequal_caseless(from[1].addr, to[1].addr) then
  504. return false
  505. end
  506. end
  507. -- SMTP recipients must contain From domain
  508. to = task:get_recipients(1)
  509. if not to then return false end
  510. -- Try mitigate some possible FPs on mailing list posts
  511. if #to == 1 and util.strequal_caseless(to[1].addr, from[1].addr) then return false end
  512. local found_fromdom = false
  513. for _, t in ipairs(to) do
  514. if util.strequal_caseless(t.domain, from[1].domain) then
  515. found_fromdom = true
  516. break
  517. end
  518. end
  519. if not found_fromdom then return false end
  520. -- Parse Reply-To header
  521. local parsed = ((util.parse_mail_address(rt, task:get_mempool()) or E)[1] or E).domain
  522. if not parsed then return false end
  523. -- Reply-To domain must be different to From domain
  524. if not util.strequal_caseless(parsed, from[1].domain) then
  525. return true, from[1].domain, parsed
  526. end
  527. return false
  528. end,
  529. group = 'headers',
  530. description = 'Reply-To is being used to spoof and trick the recipient to send an off-domain reply',
  531. score = 6.0
  532. }
  533. rspamd_config.INFO_TO_INFO_LU = {
  534. callback = function(task)
  535. local lu = task:get_header('List-Unsubscribe')
  536. if not lu then return false end
  537. local from = task:get_from('mime')
  538. if not (from and from[1] and util.strequal_caseless(from[1].user, 'info')) then
  539. return false
  540. end
  541. local to = task:get_recipients('smtp')
  542. if not to then return false end
  543. local found = false
  544. for _,r in ipairs(to) do
  545. if util.strequal_caseless(r['user'], 'info') then
  546. found = true
  547. end
  548. end
  549. if found then return true end
  550. return false
  551. end,
  552. description = 'info@ From/To address with List-Unsubscribe headers',
  553. group = 'headers',
  554. score = 2.0
  555. }
  556. -- Detects bad content-transfer-encoding for text parts
  557. rspamd_config.R_BAD_CTE_7BIT = {
  558. callback = function(task)
  559. local tp = task:get_text_parts() or {}
  560. for _,p in ipairs(tp) do
  561. local cte = p:get_mimepart():get_cte() or ''
  562. if cte ~= '8bit' and p:has_8bit_raw() then
  563. local _,_,attrs = p:get_mimepart():get_type_full()
  564. local mul = 1.0
  565. local params = {cte}
  566. if attrs then
  567. if attrs.charset and attrs.charset:lower() == "utf-8" then
  568. -- Penalise rule as people don't know that utf8 is surprisingly
  569. -- eight bit encoding
  570. mul = 0.3
  571. table.insert(params, "utf8")
  572. end
  573. end
  574. return true,mul,params
  575. end
  576. end
  577. return false
  578. end,
  579. score = 3.5,
  580. description = 'Detects bad content-transfer-encoding for text parts',
  581. group = 'headers'
  582. }
  583. local check_encrypted_name = rspamd_config:register_symbol{
  584. name = 'BOGUS_ENCRYPTED_AND_TEXT',
  585. callback = function(task)
  586. local parts = task:get_parts() or {}
  587. local seen_encrypted, seen_text
  588. local opts = {}
  589. local function check_part(part)
  590. if part:is_multipart() then
  591. local children = part:get_children() or {}
  592. for _,cld in ipairs(children) do
  593. if cld:is_multipart() then
  594. check_part(cld)
  595. elseif cld:is_text() then
  596. seen_text = true
  597. else
  598. local type,subtype,_ = cld:get_type_full()
  599. if type:lower() == 'application' then
  600. if string.find(subtype:lower(), 'pkcs7%-mime') then
  601. -- S/MIME encrypted part
  602. seen_encrypted = true
  603. table.insert(opts, 'smime part')
  604. task:insert_result('ENCRYPTED_SMIME', 1.0)
  605. elseif string.find(subtype:lower(), 'pkcs7%-signature') then
  606. task:insert_result('SIGNED_SMIME', 1.0)
  607. elseif string.find(subtype:lower(), 'pgp%-encrypted') then
  608. -- PGP/GnuPG encrypted part
  609. seen_encrypted = true
  610. table.insert(opts, 'pgp part')
  611. task:insert_result('ENCRYPTED_PGP', 1.0)
  612. elseif string.find(subtype:lower(), 'pgp%-signature') then
  613. task:insert_result('SIGNED_PGP', 1.0)
  614. end
  615. end
  616. end
  617. end
  618. end
  619. end
  620. for _,part in ipairs(parts) do
  621. check_part(part)
  622. end
  623. if seen_text and seen_encrypted then
  624. return true, 1.0, opts
  625. end
  626. return false
  627. end,
  628. score = 10.0,
  629. description = 'Bogus mix of encrypted and text/html payloads',
  630. group = 'mime_types'
  631. }
  632. rspamd_config:register_symbol{
  633. type = 'virtual',
  634. parent = check_encrypted_name,
  635. name = 'ENCRYPTED_PGP',
  636. description = 'Message is encrypted with pgp',
  637. group = 'mime_types',
  638. score = -0.5,
  639. one_shot = true
  640. }
  641. rspamd_config:register_symbol{
  642. type = 'virtual',
  643. parent = check_encrypted_name,
  644. name = 'ENCRYPTED_SMIME',
  645. description = 'Message is encrypted with smime',
  646. group = 'mime_types',
  647. score = -0.5,
  648. one_shot = true
  649. }
  650. rspamd_config:register_symbol{
  651. type = 'virtual',
  652. parent = check_encrypted_name,
  653. name = 'SIGNED_PGP',
  654. description = 'Message is signed with pgp',
  655. group = 'mime_types',
  656. score = -2.0,
  657. one_shot = true
  658. }
  659. rspamd_config:register_symbol{
  660. type = 'virtual',
  661. parent = check_encrypted_name,
  662. name = 'SIGNED_SMIME',
  663. description = 'Message is signed with smime',
  664. group = 'mime_types',
  665. score = -2.0,
  666. one_shot = true
  667. }