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.

mime_types.lua 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. --[[
  2. Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com>
  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. if confighelp then
  14. return
  15. end
  16. -- This plugin implements mime types checks for mail messages
  17. local logger = require "rspamd_logger"
  18. local lua_util = require "lua_util"
  19. local rspamd_util = require "rspamd_util"
  20. local lua_maps = require "lua_maps"
  21. local lua_mime_types = require "lua_mime_types"
  22. local lua_magic_types = require "lua_magic/types"
  23. local fun = require "fun"
  24. local N = "mime_types"
  25. local settings = {
  26. file = '',
  27. symbol_unknown = 'MIME_UNKNOWN',
  28. symbol_bad = 'MIME_BAD',
  29. symbol_good = 'MIME_GOOD',
  30. symbol_attachment = 'MIME_BAD_ATTACHMENT',
  31. symbol_encrypted_archive = 'MIME_ENCRYPTED_ARCHIVE',
  32. symbol_obfuscated_archive = 'MIME_OBFUSCATED_ARCHIVE',
  33. symbol_exe_in_gen_split_rar = 'MIME_EXE_IN_GEN_SPLIT_RAR',
  34. symbol_archive_in_archive = 'MIME_ARCHIVE_IN_ARCHIVE',
  35. symbol_double_extension = 'MIME_DOUBLE_BAD_EXTENSION',
  36. symbol_bad_extension = 'MIME_BAD_EXTENSION',
  37. symbol_bad_unicode = 'MIME_BAD_UNICODE',
  38. regexp = false,
  39. extension_map = { -- extension -> mime_type
  40. html = 'text/html',
  41. htm = 'text/html',
  42. pdf = 'application/pdf',
  43. shtm = 'text/html',
  44. shtml = 'text/html',
  45. txt = 'text/plain'
  46. },
  47. bad_extensions = {
  48. cue = 2,
  49. exe = 1,
  50. iso = 4,
  51. jar = 2,
  52. zpaq = 2,
  53. -- In contrast to HTML MIME parts, dedicated HTML attachments are considered harmful
  54. htm = 1,
  55. html = 1,
  56. shtm = 1,
  57. shtml = 1,
  58. -- Have you ever seen that in legit email?
  59. ace = 4,
  60. arj = 2,
  61. aspx = 1,
  62. asx = 2,
  63. cab = 3,
  64. dll = 4,
  65. dqy = 2,
  66. iqy = 2,
  67. mht = 2,
  68. mhtml = 2,
  69. oqy = 2,
  70. rqy = 2,
  71. sfx = 2,
  72. slk = 2,
  73. vst = 2,
  74. vss = 2,
  75. wim = 2,
  76. -- Additional bad extensions from Gmail
  77. ade = 4,
  78. adp = 4,
  79. cmd = 4,
  80. cpl = 4,
  81. ins = 4,
  82. isp = 4,
  83. js = 4,
  84. jse = 4,
  85. lib = 4,
  86. mde = 4,
  87. msc = 4,
  88. msi = 4,
  89. msp = 4,
  90. mst = 4,
  91. nsh = 4,
  92. pif = 4,
  93. sct = 4,
  94. shb = 4,
  95. sys = 4,
  96. vb = 4,
  97. vbe = 4,
  98. vbs = 4,
  99. vxd = 4,
  100. wsc = 4,
  101. wsh = 4,
  102. -- Additional bad extensions from Outlook
  103. app = 4,
  104. asp = 4,
  105. bas = 4,
  106. bat = 4,
  107. chm = 4,
  108. cnt = 4,
  109. com = 4,
  110. csh = 4,
  111. diagcab = 4,
  112. fxp = 4,
  113. gadget = 4,
  114. grp = 4,
  115. hlp = 4,
  116. hpj = 4,
  117. hta = 4,
  118. htc = 4,
  119. inf = 4,
  120. its = 4,
  121. jnlp = 4,
  122. lnk = 4,
  123. ksh = 4,
  124. mad = 4,
  125. maf = 4,
  126. mag = 4,
  127. mam = 4,
  128. maq = 4,
  129. mar = 4,
  130. mas = 4,
  131. mat = 4,
  132. mau = 4,
  133. mav = 4,
  134. maw = 4,
  135. mcf = 4,
  136. mda = 4,
  137. mdb = 4,
  138. mdt = 4,
  139. mdw = 4,
  140. mdz = 4,
  141. msh = 4,
  142. msh1 = 4,
  143. msh2 = 4,
  144. mshxml = 4,
  145. msh1xml = 4,
  146. msh2xml = 4,
  147. msu = 4,
  148. ops = 4,
  149. osd = 4,
  150. pcd = 4,
  151. pl = 4,
  152. plg = 4,
  153. prf = 4,
  154. prg = 4,
  155. printerexport = 4,
  156. ps1 = 4,
  157. ps1xml = 4,
  158. ps2 = 4,
  159. ps2xml = 4,
  160. psc1 = 4,
  161. psc2 = 4,
  162. psd1 = 4,
  163. psdm1 = 4,
  164. pst = 4,
  165. pyc = 4,
  166. pyo = 4,
  167. pyw = 4,
  168. pyz = 4,
  169. pyzw = 4,
  170. reg = 4,
  171. scf = 4,
  172. scr = 4,
  173. shs = 4,
  174. theme = 4,
  175. url = 4,
  176. vbp = 4,
  177. vhd = 4,
  178. vhdx = 4,
  179. vsmacros = 4,
  180. vsw = 4,
  181. webpnp = 4,
  182. website = 4,
  183. ws = 4,
  184. wsf = 4,
  185. xbap = 4,
  186. xll = 4,
  187. xnk = 4,
  188. },
  189. -- Something that should not be in archive
  190. bad_archive_extensions = {
  191. docx = 0.1,
  192. hta = 4,
  193. jar = 3,
  194. js = 0.5,
  195. pdf = 0.1,
  196. pptx = 0.1,
  197. vbs = 4,
  198. wsf = 4,
  199. xlsx = 0.1,
  200. },
  201. archive_extensions = {
  202. ['7z'] = 1,
  203. ace = 1,
  204. alz = 1,
  205. arj = 1,
  206. bz2 = 1,
  207. cab = 1,
  208. egg = 1,
  209. lz = 1,
  210. rar = 1,
  211. xz = 1,
  212. zip = 1,
  213. zpaq = 1,
  214. },
  215. -- Not really archives
  216. archive_exceptions = {
  217. docx = true,
  218. odp = true,
  219. ods = true,
  220. odt = true,
  221. pptx = true,
  222. vsdx = true,
  223. xlsx = true,
  224. -- jar = true,
  225. },
  226. -- Multiplier for full extension_map mismatch
  227. other_extensions_mult = 0.4,
  228. }
  229. local map = nil
  230. local function check_mime_type(task)
  231. local function gen_extension(fname)
  232. local parts = lua_util.str_split(fname or '', '.')
  233. local ext = {}
  234. for n = 1, 2 do
  235. ext[n] = #parts > n and string.lower(parts[#parts + 1 - n]) or nil
  236. end
  237. return ext[1], ext[2], parts
  238. end
  239. local function check_filename(fname, ct, is_archive, part, detected_ext, nfiles)
  240. lua_util.debugm(N, task, "check filename: %s, ct=%s, is_archive=%s, detected_ext=%s, nfiles=%s",
  241. fname, ct, is_archive, detected_ext, nfiles)
  242. local has_bad_unicode, char, ch_pos = rspamd_util.has_obscured_unicode(fname)
  243. if has_bad_unicode then
  244. task:insert_result(settings.symbol_bad_unicode, 1.0,
  245. string.format("0x%xd after %s", char,
  246. fname:sub(1, ch_pos)))
  247. end
  248. -- Decode hex encoded characters
  249. fname = string.gsub(fname, '%%(%x%x)',
  250. function(hex)
  251. return string.char(tonumber(hex, 16))
  252. end)
  253. -- Replace potentially bad characters with '?'
  254. fname = fname:gsub('[^%s%g]', '?')
  255. -- Check file is in filename whitelist
  256. if settings.filename_whitelist and
  257. settings.filename_whitelist:get_key(fname) then
  258. logger.debugm("mime_types", task, "skip checking of %s - file is in filename whitelist",
  259. fname)
  260. return
  261. end
  262. local ext, ext2, parts = gen_extension(fname)
  263. -- ext is the last extension, LOWERCASED
  264. -- ext2 is the one before last extension LOWERCASED
  265. local detected
  266. if not is_archive and detected_ext then
  267. detected = lua_magic_types[detected_ext]
  268. end
  269. if detected_ext and ((not ext) or ext ~= detected_ext) then
  270. -- Try to find extension by real content type
  271. check_filename('detected.' .. detected_ext, detected.ct,
  272. false, part, nil, 1)
  273. end
  274. if not ext then
  275. return
  276. end
  277. local function check_extension(badness_mult, badness_mult2)
  278. if not badness_mult and not badness_mult2 then
  279. return
  280. end
  281. if #parts > 2 then
  282. -- We need to ensure that next-to-last extension is an extension,
  283. -- so we check for its length and if it is not a number or date
  284. if #ext2 > 0 and #ext2 <= 4 and not string.match(ext2, '^%d+[%]%)]?$') then
  285. -- Use the greatest badness multiplier
  286. if not badness_mult or
  287. (badness_mult2 and badness_mult < badness_mult2) then
  288. badness_mult = badness_mult2
  289. end
  290. -- Double extension + bad extension == VERY bad
  291. task:insert_result(settings['symbol_double_extension'], badness_mult,
  292. string.format(".%s.%s", ext2, ext))
  293. task:insert_result('MIME_TRACE', 0.0,
  294. string.format("%s:%s", part:get_id(), '-'))
  295. return
  296. end
  297. end
  298. if badness_mult then
  299. -- Just bad extension
  300. task:insert_result(settings['symbol_bad_extension'], badness_mult, ext)
  301. task:insert_result('MIME_TRACE', 0.0,
  302. string.format("%s:%s", part:get_id(), '-'))
  303. end
  304. end
  305. -- Process settings
  306. local extra_table = {}
  307. local extra_archive_table = {}
  308. local user_settings = task:cache_get('settings')
  309. if user_settings and user_settings.plugins then
  310. user_settings = user_settings.plugins.mime_types
  311. end
  312. if user_settings then
  313. logger.infox(task, 'using special tables from user settings')
  314. if user_settings.bad_extensions then
  315. if user_settings.bad_extensions[1] then
  316. -- Convert to a key-value map
  317. extra_table = fun.tomap(
  318. fun.map(function(e)
  319. return e, 1.0
  320. end,
  321. user_settings.bad_extensions))
  322. else
  323. extra_table = user_settings.bad_extensions
  324. end
  325. end
  326. if user_settings.bad_archive_extensions then
  327. if user_settings.bad_archive_extensions[1] then
  328. -- Convert to a key-value map
  329. extra_archive_table = fun.tomap(fun.map(
  330. function(e)
  331. return e, 1.0
  332. end,
  333. user_settings.bad_archive_extensions))
  334. else
  335. extra_archive_table = user_settings.bad_archive_extensions
  336. end
  337. end
  338. end
  339. local function check_tables(e)
  340. if is_archive then
  341. return extra_archive_table[e] or (nfiles < 2 and settings.bad_archive_extensions[e]) or
  342. extra_table[e] or settings.bad_extensions[e]
  343. end
  344. return extra_table[e] or settings.bad_extensions[e]
  345. end
  346. -- Also check for archive bad extension
  347. if is_archive then
  348. if ext2 then
  349. local score1 = check_tables(ext)
  350. local score2 = check_tables(ext2)
  351. check_extension(score1, score2)
  352. else
  353. local score1 = check_tables(ext)
  354. check_extension(score1, nil)
  355. end
  356. if settings['archive_extensions'][ext] then
  357. -- Archive in archive
  358. task:insert_result(settings['symbol_archive_in_archive'], 1.0, ext)
  359. task:insert_result('MIME_TRACE', 0.0,
  360. string.format("%s:%s", part:get_id(), '-'))
  361. end
  362. else
  363. if ext2 then
  364. local score1 = check_tables(ext)
  365. local score2 = check_tables(ext2)
  366. -- Check if detected extension match real extension
  367. if detected_ext and detected_ext == ext then
  368. check_extension(score1, nil)
  369. else
  370. check_extension(score1, score2)
  371. end
  372. -- Check for archive cloaking like .zip.gz
  373. if settings['archive_extensions'][ext2]
  374. -- Exclude multipart archive extensions, e.g. .zip.001
  375. and not string.match(ext, '^%d+$')
  376. then
  377. task:insert_result(settings['symbol_archive_in_archive'],
  378. 1.0, string.format(".%s.%s", ext2, ext))
  379. task:insert_result('MIME_TRACE', 0.0,
  380. string.format("%s:%s", part:get_id(), '-'))
  381. end
  382. else
  383. local score1 = check_tables(ext)
  384. check_extension(score1, nil)
  385. end
  386. end
  387. local mt = settings['extension_map'][ext]
  388. if mt and ct and ct ~= 'application/octet-stream' then
  389. local found
  390. local mult
  391. for _, v in ipairs(mt) do
  392. mult = v.mult
  393. if ct == v.ct then
  394. found = true
  395. break
  396. end
  397. end
  398. if not found then
  399. task:insert_result(settings['symbol_attachment'], mult, string.format('%s:%s',
  400. ext, ct))
  401. end
  402. end
  403. end
  404. local parts = task:get_parts()
  405. if parts then
  406. for _, p in ipairs(parts) do
  407. local mtype, subtype = p:get_type()
  408. if not mtype then
  409. lua_util.debugm(N, task, "no content type for part: %s", p:get_id())
  410. task:insert_result(settings['symbol_unknown'], 1.0, 'missing content type')
  411. task:insert_result('MIME_TRACE', 0.0,
  412. string.format("%s:%s", p:get_id(), '~'))
  413. else
  414. -- Check for attachment
  415. local filename = p:get_filename()
  416. local ct = string.format('%s/%s', mtype, subtype):lower()
  417. local detected_ext = p:get_detected_ext()
  418. if filename then
  419. check_filename(filename, ct, false, p, detected_ext, 1)
  420. end
  421. if p:is_archive() then
  422. local check = true
  423. if detected_ext then
  424. local detected_type = lua_magic_types[detected_ext]
  425. if detected_type.type ~= 'archive' then
  426. logger.debugm("mime_types", task, "skip checking of %s as archive, %s is not archive but %s",
  427. filename, detected_type.type)
  428. check = false
  429. end
  430. end
  431. if check and filename then
  432. local ext = gen_extension(filename)
  433. if ext and settings.archive_exceptions[ext] then
  434. check = false
  435. logger.debugm("mime_types", task, "skip checking of %s as archive, %s is whitelisted",
  436. filename, ext)
  437. end
  438. end
  439. local arch = p:get_archive()
  440. -- TODO: migrate to flags once C part is ready
  441. if arch:is_encrypted() then
  442. task:insert_result(settings.symbol_encrypted_archive, 1.0, filename)
  443. task:insert_result('MIME_TRACE', 0.0,
  444. string.format("%s:%s", p:get_id(), '-'))
  445. elseif arch:is_unreadable() then
  446. task:insert_result(settings.symbol_encrypted_archive, 0.5, {
  447. 'compressed header',
  448. filename,
  449. })
  450. task:insert_result('MIME_TRACE', 0.0,
  451. string.format("%s:%s", p:get_id(), '-'))
  452. elseif arch:is_obfuscated() then
  453. task:insert_result(settings.symbol_obfuscated_archive, 1.0, {
  454. 'obfuscated archive',
  455. filename,
  456. })
  457. task:insert_result('MIME_TRACE', 0.0,
  458. string.format("%s:%s", p:get_id(), '-'))
  459. end
  460. if check then
  461. local is_gen_split_rar = false
  462. if filename then
  463. local ext = gen_extension(filename)
  464. is_gen_split_rar = ext and (string.match(ext, '^%d%d%d$')) and (arch:get_type() == 'rar')
  465. end
  466. local fl = arch:get_files_full(1000)
  467. local nfiles = #fl
  468. for _, f in ipairs(fl) do
  469. if f['encrypted'] then
  470. task:insert_result(settings['symbol_encrypted_archive'],
  471. 1.0, f['name'])
  472. task:insert_result('MIME_TRACE', 0.0,
  473. string.format("%s:%s", p:get_id(), '-'))
  474. end
  475. if f['name'] then
  476. if is_gen_split_rar and (gen_extension(f['name']) or '') == 'exe' then
  477. task:insert_result(settings['symbol_exe_in_gen_split_rar'], 1.0, f['name'])
  478. else
  479. check_filename(f['name'], nil,
  480. true, p, nil, nfiles)
  481. end
  482. end
  483. end
  484. if nfiles == 1 and fl[1].name then
  485. -- We check that extension of the file inside archive is
  486. -- the same as double extension of the file
  487. local _, ext2 = gen_extension(filename)
  488. if ext2 and #ext2 > 0 then
  489. local enc_ext = gen_extension(fl[1].name)
  490. if enc_ext
  491. and settings['bad_extensions'][enc_ext]
  492. and not tonumber(ext2)
  493. and enc_ext ~= ext2 then
  494. task:insert_result(settings['symbol_double_extension'], 2.0,
  495. string.format("%s!=%s", ext2, enc_ext))
  496. end
  497. end
  498. end
  499. end
  500. end
  501. if map then
  502. local v = map:get_key(ct)
  503. local detected_different = false
  504. local detected_type
  505. if detected_ext then
  506. detected_type = lua_magic_types[detected_ext]
  507. end
  508. if detected_type and detected_type.ct ~= ct then
  509. local v_detected = map:get_key(detected_type.ct)
  510. if not v or v_detected and v_detected > v then
  511. v = v_detected
  512. end
  513. detected_different = true
  514. end
  515. if v then
  516. local n = tonumber(v)
  517. if n then
  518. if n > 0 then
  519. if detected_different then
  520. -- Penalize case
  521. n = n * 1.5
  522. task:insert_result(settings['symbol_bad'], n,
  523. string.format('%s:%s', ct, detected_type.ct))
  524. else
  525. task:insert_result(settings['symbol_bad'], n, ct)
  526. end
  527. task:insert_result('MIME_TRACE', 0.0,
  528. string.format("%s:%s", p:get_id(), '-'))
  529. elseif n < 0 then
  530. task:insert_result(settings['symbol_good'], -n, ct)
  531. task:insert_result('MIME_TRACE', 0.0,
  532. string.format("%s:%s", p:get_id(), '+'))
  533. else
  534. -- Neutral content type
  535. task:insert_result('MIME_TRACE', 0.0,
  536. string.format("%s:%s", p:get_id(), '~'))
  537. end
  538. else
  539. logger.warnx(task, 'unknown value: "%s" for content type %s in the map',
  540. v, ct)
  541. end
  542. else
  543. task:insert_result(settings['symbol_unknown'], 1.0, ct)
  544. task:insert_result('MIME_TRACE', 0.0,
  545. string.format("%s:%s", p:get_id(), '~'))
  546. end
  547. end
  548. end
  549. end
  550. end
  551. end
  552. local opts = rspamd_config:get_all_opt('mime_types')
  553. if opts then
  554. for k, v in pairs(opts) do
  555. settings[k] = v
  556. end
  557. settings.filename_whitelist = lua_maps.rspamd_map_add('mime_types', 'filename_whitelist', 'regexp',
  558. 'filename whitelist')
  559. local function change_extension_map_entry(ext, ct, mult)
  560. if type(ct) == 'table' then
  561. local tbl = {}
  562. for _, elt in ipairs(ct) do
  563. table.insert(tbl, {
  564. ct = elt,
  565. mult = mult,
  566. })
  567. end
  568. settings.extension_map[ext] = tbl
  569. else
  570. settings.extension_map[ext] = { [1] = {
  571. ct = ct,
  572. mult = mult
  573. } }
  574. end
  575. end
  576. -- Transform extension_map
  577. for ext, ct in pairs(settings.extension_map) do
  578. change_extension_map_entry(ext, ct, 1.0)
  579. end
  580. -- Add all extensions
  581. for _, pair in ipairs(lua_mime_types.full_extensions_map) do
  582. local ext, ct = pair[1], pair[2]
  583. if not settings.extension_map[ext] then
  584. change_extension_map_entry(ext, ct, settings.other_extensions_mult)
  585. end
  586. end
  587. local map_type = 'map'
  588. if settings['regexp'] then
  589. map_type = 'regexp'
  590. end
  591. map = lua_maps.rspamd_map_add('mime_types', 'file', map_type,
  592. 'mime types map')
  593. if map then
  594. local id = rspamd_config:register_symbol({
  595. name = 'MIME_TYPES_CALLBACK',
  596. callback = check_mime_type,
  597. type = 'callback',
  598. flags = 'nostat',
  599. group = 'mime_types',
  600. })
  601. rspamd_config:register_symbol({
  602. type = 'virtual',
  603. name = settings['symbol_unknown'],
  604. parent = id,
  605. group = 'mime_types',
  606. })
  607. rspamd_config:register_symbol({
  608. type = 'virtual',
  609. name = settings['symbol_bad'],
  610. parent = id,
  611. group = 'mime_types',
  612. })
  613. rspamd_config:register_symbol({
  614. type = 'virtual',
  615. name = settings['symbol_good'],
  616. flags = 'nice',
  617. parent = id,
  618. group = 'mime_types',
  619. })
  620. rspamd_config:register_symbol({
  621. type = 'virtual',
  622. name = settings['symbol_attachment'],
  623. parent = id,
  624. group = 'mime_types',
  625. })
  626. rspamd_config:register_symbol({
  627. type = 'virtual',
  628. name = settings['symbol_encrypted_archive'],
  629. parent = id,
  630. group = 'mime_types',
  631. })
  632. rspamd_config:register_symbol({
  633. type = 'virtual',
  634. name = settings['symbol_obfuscated_archive'],
  635. parent = id,
  636. group = 'mime_types',
  637. })
  638. rspamd_config:register_symbol({
  639. type = 'virtual',
  640. name = settings['symbol_exe_in_gen_split_rar'],
  641. parent = id,
  642. group = 'mime_types',
  643. })
  644. rspamd_config:register_symbol({
  645. type = 'virtual',
  646. name = settings['symbol_archive_in_archive'],
  647. parent = id,
  648. group = 'mime_types',
  649. })
  650. rspamd_config:register_symbol({
  651. type = 'virtual',
  652. name = settings['symbol_double_extension'],
  653. parent = id,
  654. group = 'mime_types',
  655. })
  656. rspamd_config:register_symbol({
  657. type = 'virtual',
  658. name = settings['symbol_bad_extension'],
  659. parent = id,
  660. group = 'mime_types',
  661. })
  662. rspamd_config:register_symbol({
  663. type = 'virtual',
  664. name = settings['symbol_bad_unicode'],
  665. parent = id,
  666. group = 'mime_types',
  667. })
  668. rspamd_config:register_symbol({
  669. type = 'virtual',
  670. name = 'MIME_TRACE',
  671. parent = id,
  672. group = 'mime_types',
  673. flags = 'nostat',
  674. score = 0,
  675. })
  676. else
  677. lua_util.disable_module(N, "config")
  678. end
  679. end