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.

http_headers.lua 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. --[[
  2. Copyright (c) 2015, 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. local logger = require "rspamd_logger"
  14. local ucl = require "ucl"
  15. local spf_symbols = {
  16. symbol_allow = 'R_SPF_ALLOW',
  17. symbol_deny = 'R_SPF_FAIL',
  18. symbol_softfail = 'R_SPF_SOFTFAIL',
  19. symbol_neutral = 'R_SPF_NEUTRAL',
  20. symbol_tempfail = 'R_SPF_DNSFAIL',
  21. symbol_na = 'R_SPF_NA',
  22. symbol_permfail = 'R_SPF_PERMFAIL',
  23. }
  24. local dkim_symbols = {
  25. symbol_allow = 'R_DKIM_ALLOW',
  26. symbol_deny = 'R_DKIM_REJECT',
  27. symbol_tempfail = 'R_DKIM_TEMPFAIL',
  28. symbol_na = 'R_DKIM_NA',
  29. symbol_permfail = 'R_DKIM_PERMFAIL',
  30. symbol_trace = 'DKIM_TRACE',
  31. }
  32. local dkim_trace = {
  33. pass = '+',
  34. fail = '-',
  35. temperror = '?',
  36. permerror = '~',
  37. }
  38. local dmarc_symbols = {
  39. allow = 'DMARC_POLICY_ALLOW',
  40. badpolicy = 'DMARC_BAD_POLICY',
  41. dnsfail = 'DMARC_DNSFAIL',
  42. na = 'DMARC_NA',
  43. reject = 'DMARC_POLICY_REJECT',
  44. softfail = 'DMARC_POLICY_SOFTFAIL',
  45. quarantine = 'DMARC_POLICY_QUARANTINE',
  46. }
  47. local opts = rspamd_config:get_all_opt('dmarc')
  48. if opts and opts['symbols'] then
  49. for k,_ in pairs(dmarc_symbols) do
  50. if opts['symbols'][k] then
  51. dmarc_symbols[k] = opts['symbols'][k]
  52. end
  53. end
  54. end
  55. opts = rspamd_config:get_all_opt('dkim')
  56. if opts then
  57. for k,_ in pairs(dkim_symbols) do
  58. if opts[k] then
  59. dkim_symbols[k] = opts[k]
  60. end
  61. end
  62. end
  63. opts = rspamd_config:get_all_opt('spf')
  64. if opts then
  65. for k,_ in pairs(spf_symbols) do
  66. if opts[k] then
  67. spf_symbols[k] = opts[k]
  68. end
  69. end
  70. end
  71. -- Disable DKIM checks if passed via HTTP headers
  72. rspamd_config:add_condition("DKIM_CHECK", function(task)
  73. local hdr = task:get_request_header('DKIM')
  74. if hdr then
  75. local parser = ucl.parser()
  76. local res, err = parser:parse_string(tostring(hdr))
  77. if not res then
  78. logger.infox(task, "cannot parse DKIM header: %1", err)
  79. return true
  80. end
  81. local p_obj = parser:get_object()
  82. local results = p_obj['results']
  83. if not results and p_obj['result'] then
  84. results = {{result = p_obj['result'], domain = 'unknown'}}
  85. end
  86. if results then
  87. for _, obj in ipairs(results) do
  88. local dkim_domain = obj['domain'] or 'unknown'
  89. if obj['result'] == 'pass' or obj['result'] == 'allow' then
  90. task:insert_result(dkim_symbols['symbol_allow'], 1.0, 'http header')
  91. task:insert_result(dkim_symbols['symbol_trace'], 1.0,
  92. string.format('%s:%s', dkim_domain, dkim_trace.pass))
  93. elseif obj['result'] == 'fail' or obj['result'] == 'reject' then
  94. task:insert_result(dkim_symbols['symbol_deny'], 1.0, 'http header')
  95. task:insert_result(dkim_symbols['symbol_trace'], 1.0,
  96. string.format('%s:%s', dkim_domain, dkim_trace.fail))
  97. elseif obj['result'] == 'tempfail' or obj['result'] == 'softfail' then
  98. task:insert_result(dkim_symbols['symbol_tempfail'], 1.0, 'http header')
  99. task:insert_result(dkim_symbols['symbol_trace'], 1.0,
  100. string.format('%s:%s', dkim_domain, dkim_trace.temperror))
  101. elseif obj['result'] == 'permfail' then
  102. task:insert_result(dkim_symbols['symbol_permfail'], 1.0, 'http header')
  103. task:insert_result(dkim_symbols['symbol_trace'], 1.0,
  104. string.format('%s:%s', dkim_domain, dkim_trace.permerror))
  105. elseif obj['result'] == 'na' then
  106. task:insert_result(dkim_symbols['symbol_na'], 1.0, 'http header')
  107. end
  108. end
  109. end
  110. end
  111. return false
  112. end)
  113. -- Disable SPF checks if passed via HTTP headers
  114. rspamd_config:add_condition("SPF_CHECK", function(task)
  115. local hdr = task:get_request_header('SPF')
  116. if hdr then
  117. local parser = ucl.parser()
  118. local res, err = parser:parse_string(tostring(hdr))
  119. if not res then
  120. logger.infox(task, "cannot parse SPF header: %1", err)
  121. return true
  122. end
  123. local obj = parser:get_object()
  124. if obj['result'] then
  125. if obj['result'] == 'pass' or obj['result'] == 'allow' then
  126. task:insert_result(spf_symbols['symbol_allow'], 1.0, 'http header')
  127. elseif obj['result'] == 'fail' or obj['result'] == 'reject' then
  128. task:insert_result(spf_symbols['symbol_deny'], 1.0, 'http header')
  129. elseif obj['result'] == 'neutral' then
  130. task:insert_result(spf_symbols['symbol_neutral'], 1.0, 'http header')
  131. elseif obj['result'] == 'softfail' then
  132. task:insert_result(spf_symbols['symbol_softfail'], 1.0, 'http header')
  133. elseif obj['result'] == 'permfail' then
  134. task:insert_result(spf_symbols['symbol_permfail'], 1.0, 'http header')
  135. elseif obj['result'] == 'na' then
  136. task:insert_result(spf_symbols['symbol_na'], 1.0, 'http header')
  137. end
  138. end
  139. end
  140. return false
  141. end)
  142. rspamd_config:add_condition("DMARC_CALLBACK", function(task)
  143. local hdr = task:get_request_header('DMARC')
  144. if hdr then
  145. local parser = ucl.parser()
  146. local res, err = parser:parse_string(tostring(hdr))
  147. if not res then
  148. logger.infox(task, "cannot parse DMARC header: %1", err)
  149. return true
  150. end
  151. local obj = parser:get_object()
  152. if obj['result'] then
  153. if obj['result'] == 'pass' or obj['result'] == 'allow' then
  154. task:insert_result(dmarc_symbols['allow'], 1.0, 'http header')
  155. elseif obj['result'] == 'fail' or obj['result'] == 'reject' then
  156. task:insert_result(dmarc_symbols['reject'], 1.0, 'http header')
  157. elseif obj['result'] == 'quarantine' then
  158. task:insert_result(dmarc_symbols['quarantine'], 1.0, 'http header')
  159. elseif obj['result'] == 'tempfail' then
  160. task:insert_result(dmarc_symbols['dnsfail'], 1.0, 'http header')
  161. elseif obj['result'] == 'softfail' or obj['result'] == 'none' then
  162. task:insert_result(dmarc_symbols['softfail'], 1.0, 'http header')
  163. elseif obj['result'] == 'permfail' or obj['result'] == 'badpolicy' then
  164. task:insert_result(dmarc_symbols['badpolicy'], 1.0, 'http header')
  165. elseif obj['result'] == 'na' then
  166. task:insert_result(dmarc_symbols['na'], 1.0, 'http header')
  167. end
  168. end
  169. end
  170. return false
  171. end)