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