Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

lua_auth_results.lua 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. --[[
  2. Copyright (c) 2016, Andrew Lewis <nerf@judo.za.org>
  3. Copyright (c) 2017, 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. local rspamd_util = require "rspamd_util"
  15. local default_settings = {
  16. spf_symbols = {
  17. pass = 'R_SPF_ALLOW',
  18. fail = 'R_SPF_FAIL',
  19. softfail = 'R_SPF_SOFTFAIL',
  20. neutral = 'R_SPF_NEUTRAL',
  21. temperror = 'R_SPF_DNSFAIL',
  22. none = 'R_SPF_NA',
  23. permerror = 'R_SPF_PERMFAIL',
  24. },
  25. dmarc_symbols = {
  26. pass = 'DMARC_POLICY_ALLOW',
  27. permerror = 'DMARC_BAD_POLICY',
  28. temperror = 'DMARC_DNSFAIL',
  29. none = 'DMARC_NA',
  30. reject = 'DMARC_POLICY_REJECT',
  31. softfail = 'DMARC_POLICY_SOFTFAIL',
  32. quarantine = 'DMARC_POLICY_QUARANTINE',
  33. },
  34. arc_symbols = {
  35. pass = 'ARC_ALLOW',
  36. permerror = 'ARC_INVALID',
  37. temperror = 'ARC_DNSFAIL',
  38. none = 'ARC_NA',
  39. reject = 'ARC_REJECT',
  40. },
  41. dkim_symbols = {
  42. none = 'R_DKIM_NA',
  43. },
  44. add_smtp_user = true,
  45. }
  46. local exports = {
  47. default_settings = default_settings
  48. }
  49. local local_hostname = rspamd_util.get_hostname()
  50. local function gen_auth_results(task, settings)
  51. local auth_results, hdr_parts = {}, {}
  52. if not settings then
  53. settings = default_settings
  54. end
  55. local auth_types = {
  56. dkim = settings.dkim_symbols,
  57. dmarc = settings.dmarc_symbols,
  58. spf = settings.spf_symbols,
  59. arc = settings.arc_symbols,
  60. }
  61. local common = {
  62. symbols = {}
  63. }
  64. local mta_hostname = task:get_request_header('MTA-Name') or
  65. task:get_request_header('MTA-Tag')
  66. if mta_hostname then
  67. mta_hostname = tostring(mta_hostname)
  68. else
  69. mta_hostname = local_hostname
  70. end
  71. table.insert(hdr_parts, mta_hostname)
  72. for auth_type, symbols in pairs(auth_types) do
  73. for key, sym in pairs(symbols) do
  74. if not common.symbols.sym then
  75. local s = task:get_symbol(sym)
  76. if not s then
  77. common.symbols[sym] = false
  78. else
  79. common.symbols[sym] = s
  80. if not auth_results[auth_type] then
  81. auth_results[auth_type] = {key}
  82. else
  83. table.insert(auth_results[auth_type], key)
  84. end
  85. if auth_type ~= 'dkim' then
  86. break
  87. end
  88. end
  89. end
  90. end
  91. end
  92. local dkim_results = task:get_dkim_results()
  93. -- For each signature we set authentication results
  94. -- dkim=neutral (body hash did not verify) header.d=example.com header.s=sel header.b=fA8VVvJ8;
  95. -- dkim=neutral (body hash did not verify) header.d=example.com header.s=sel header.b=f8pM8o90;
  96. for _,dres in ipairs(dkim_results) do
  97. local ar_string = 'none'
  98. if dres.result == 'reject' then
  99. ar_string = 'fail' -- imply failure, not neutral
  100. elseif dres.result == 'allow' then
  101. ar_string = 'pass'
  102. elseif dres.result == 'bad record' or dres.result == 'permerror' then
  103. ar_string = 'permerror'
  104. elseif dres.result == 'tempfail' then
  105. ar_string = 'temperror'
  106. end
  107. local hdr = {}
  108. hdr[1] = string.format('dkim=%s', ar_string)
  109. if dres.fail_reason then
  110. hdr[#hdr + 1] = string.format('(%s)', dres.fail_reason)
  111. end
  112. if dres.domain then
  113. hdr[#hdr + 1] = string.format('header.d=%s', dres.domain)
  114. end
  115. if dres.selector then
  116. hdr[#hdr + 1] = string.format('header.s=%s', dres.selector)
  117. end
  118. if dres.bhash then
  119. hdr[#hdr + 1] = string.format('header.b=%s', dres.bhash)
  120. end
  121. table.insert(hdr_parts, table.concat(hdr, ' '))
  122. end
  123. if #dkim_results == 0 then
  124. -- We have no dkim results, so check for DKIM_NA symbol
  125. if common.symbols[settings.dkim_symbols.none] then
  126. table.insert(hdr_parts, 'dkim=none')
  127. end
  128. end
  129. for auth_type, keys in pairs(auth_results) do
  130. for _, key in ipairs(keys) do
  131. local hdr = ''
  132. if auth_type == 'dmarc' then
  133. local opts = common.symbols[auth_types['dmarc'][key]][1]['options'] or {}
  134. hdr = hdr .. 'dmarc='
  135. if key == 'reject' or key == 'quarantine' or key == 'softfail' then
  136. hdr = hdr .. 'fail'
  137. else
  138. hdr = hdr .. key
  139. end
  140. if key == 'pass' then
  141. hdr = hdr .. ' (policy=' .. opts[2] .. ')'
  142. hdr = hdr .. ' header.from=' .. opts[1]
  143. elseif key ~= 'none' then
  144. local t = {opts[1]:match('^([^%s]+) : (.*)$')}
  145. if #t > 0 then
  146. local dom = t[1]
  147. local rsn = t[2]
  148. if rsn then
  149. hdr = hdr .. ' reason="' .. rsn .. '"'
  150. end
  151. hdr = hdr .. ' header.from=' .. dom
  152. end
  153. if key == 'softfail' then
  154. hdr = hdr .. ' (policy=none)'
  155. else
  156. hdr = hdr .. ' (policy=' .. key .. ')'
  157. end
  158. end
  159. table.insert(hdr_parts, hdr)
  160. elseif auth_type == 'arc' then
  161. if common.symbols[auth_types['arc'][key]][1] then
  162. local opts = common.symbols[auth_types['arc'][key]][1]['options'] or {}
  163. for _, v in ipairs(opts) do
  164. hdr = hdr .. auth_type .. '=' .. key .. ' (' .. v .. ')'
  165. table.insert(hdr_parts, hdr)
  166. end
  167. end
  168. elseif auth_type == 'spf' then
  169. -- Main type
  170. local sender
  171. local sender_type
  172. local smtp_from = task:get_from('smtp')
  173. if smtp_from and
  174. smtp_from[1] and
  175. smtp_from[1]['addr'] ~= '' and
  176. smtp_from[1]['addr'] ~= nil then
  177. sender = smtp_from[1]['addr']
  178. sender_type = 'smtp.mailfrom'
  179. else
  180. local helo = task:get_helo()
  181. if helo then
  182. sender = helo
  183. sender_type = 'smtp.helo'
  184. end
  185. end
  186. if sender and sender_type then
  187. -- Comment line
  188. local comment = ''
  189. if key == 'pass' then
  190. comment = string.format('%s: domain of %s designates %s as permitted sender',
  191. mta_hostname, sender, tostring(task:get_from_ip() or 'unknown'))
  192. elseif key == 'fail' then
  193. comment = string.format('%s: domain of %s does not designate %s as permitted sender',
  194. mta_hostname, sender, tostring(task:get_from_ip() or 'unknown'))
  195. elseif key == 'neutral' or key == 'softfail' then
  196. comment = string.format('%s: %s is neither permitted nor denied by domain of %s',
  197. mta_hostname, tostring(task:get_from_ip() or 'unknown'), sender)
  198. elseif key == 'permerror' then
  199. comment = string.format('%s: domain of %s uses mechanism not recognized by this client',
  200. mta_hostname, sender)
  201. elseif key == 'temperror' then
  202. comment = string.format('%s: error in processing during lookup of %s: DNS error',
  203. mta_hostname, sender)
  204. elseif key == 'none' then
  205. comment = string.format('%s: domain of %s has no SPF policy when checking %s',
  206. mta_hostname, sender, tostring(task:get_from_ip() or 'unknown'))
  207. end
  208. hdr = string.format('%s=%s (%s) %s=%s', auth_type, key,
  209. comment, sender_type, sender)
  210. else
  211. hdr = string.format('%s=%s', auth_type, key)
  212. end
  213. table.insert(hdr_parts, hdr)
  214. end
  215. end
  216. end
  217. local u = task:get_user()
  218. local smtp_from = task:get_from('smtp')
  219. if u and smtp_from then
  220. local hdr = {[1] = 'auth=pass'}
  221. if settings['add_smtp_user'] then
  222. table.insert(hdr,'smtp.auth=' .. u)
  223. end
  224. if smtp_from[1]['addr'] then
  225. table.insert(hdr,'smtp.mailfrom=' .. smtp_from[1]['addr'])
  226. end
  227. table.insert(hdr_parts, table.concat(hdr,' '))
  228. end
  229. if #hdr_parts > 0 then
  230. if #hdr_parts == 1 then
  231. hdr_parts[2] = 'none'
  232. end
  233. return table.concat(hdr_parts, '; ')
  234. end
  235. return nil
  236. end
  237. exports.gen_auth_results = gen_auth_results
  238. return exports