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.

lua_dkim_tools.lua 8.5KB

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 exports = {}
  15. local E = {}
  16. local lua_util = require "lua_util"
  17. local rspamd_util = require "rspamd_util"
  18. local function parse_dkim_http_headers(N, task, settings)
  19. local logger = require "rspamd_logger"
  20. -- Configure headers
  21. local headers = {
  22. sign_header = settings.http_sign_header or "PerformDkimSign",
  23. sign_on_reject_header = settings.http_sign_on_reject_header_header or 'SignOnAuthFailed',
  24. domain_header = settings.http_domain_header or 'DkimDomain',
  25. selector_header = settings.http_selector_header or 'DkimSelector',
  26. key_header = settings.http_key_header or 'DkimPrivateKey'
  27. }
  28. if task:get_request_header(headers.sign_header) then
  29. local domain = task:get_request_header(headers.domain_header)
  30. local selector = task:get_request_header(headers.selector_header)
  31. local key = task:get_request_header(headers.key_header)
  32. if not (domain and selector and key) then
  33. logger.errx(task, 'missing required headers to sign email')
  34. return false,{}
  35. end
  36. -- Now check if we need to check the existing auth
  37. local hdr = task:get_request_header(headers.sign_on_reject_header)
  38. if not hdr then
  39. -- Check for DKIM_REJECT
  40. if task:has_symbol('R_DKIM_REJECT') then
  41. local sym = task:get_symbol('R_DKIM_REJECT')
  42. logger.infox(task, 'skip signing for %s:%s: R_DKIM_REJECT found: %s',
  43. domain, selector, sym.options)
  44. return false,{}
  45. end
  46. end
  47. return true,{
  48. rawkey = key,
  49. domain = domain,
  50. selector = selector
  51. }
  52. end
  53. lua_util.debugm(N, task, 'no sign header %s', headers.sign_header)
  54. return false,{}
  55. end
  56. local function prepare_dkim_signing(N, task, settings)
  57. local is_local, is_sign_networks
  58. if settings.use_http_headers then
  59. return parse_dkim_http_headers(N, task, settings)
  60. end
  61. local auser = task:get_user()
  62. local ip = task:get_from_ip()
  63. if ip and ip:is_local() then
  64. is_local = true
  65. end
  66. if settings.auth_only and auser then
  67. lua_util.debugm(N, task, 'user is authenticated')
  68. elseif (settings.sign_networks and settings.sign_networks:get_key(ip)) then
  69. is_sign_networks = true
  70. lua_util.debugm(N, task, 'mail is from address in sign_networks')
  71. elseif settings.sign_local and is_local then
  72. lua_util.debugm(N, task, 'mail is from local address')
  73. elseif settings.sign_inbound and not is_local and not auser then
  74. lua_util.debugm(N, task, 'mail was sent to us')
  75. else
  76. lua_util.debugm(N, task, 'ignoring unauthenticated mail')
  77. return false,{}
  78. end
  79. local efrom = task:get_from('smtp')
  80. if not settings.allow_envfrom_empty and
  81. #(((efrom or E)[1] or E).addr or '') == 0 then
  82. lua_util.debugm(N, task, 'empty envelope from not allowed')
  83. return false,{}
  84. end
  85. local hfrom = task:get_from('mime')
  86. if not settings.allow_hdrfrom_multiple and (hfrom or E)[2] then
  87. lua_util.debugm(N, task, 'multiple header from not allowed')
  88. return false,{}
  89. end
  90. local eto = task:get_recipients(0)
  91. local dkim_domain
  92. local hdom = ((hfrom or E)[1] or E).domain
  93. local edom = ((efrom or E)[1] or E).domain
  94. local tdom = ((eto or E)[1] or E).domain
  95. local udom = string.match(auser or '', '.*@(.*)')
  96. local function get_dkim_domain(dtype)
  97. if settings[dtype] == 'header' then
  98. return hdom
  99. elseif settings[dtype] == 'envelope' then
  100. return edom
  101. elseif settings[dtype] == 'auth' then
  102. return udom
  103. elseif settings[dtype] == 'recipient' then
  104. return tdom
  105. end
  106. end
  107. if hdom then
  108. hdom = hdom:lower()
  109. end
  110. if edom then
  111. edom = edom:lower()
  112. end
  113. if udom then
  114. udom = udom:lower()
  115. end
  116. if tdom then
  117. tdom = tdom:lower()
  118. end
  119. if settings.use_domain_sign_networks and is_sign_networks then
  120. dkim_domain = get_dkim_domain('use_domain_sign_networks')
  121. lua_util.debugm(N, task, 'sign_networks: use domain(%s) for signature: %s',
  122. settings.use_domain_sign_networks, dkim_domain)
  123. elseif settings.use_domain_sign_local and is_local then
  124. dkim_domain = get_dkim_domain('use_domain_sign_local')
  125. lua_util.debugm(N, task, 'local: use domain(%s) for signature: %s',
  126. settings.use_domain_sign_local, dkim_domain)
  127. elseif settings.use_domain_sign_inbound and not is_local and not auser then
  128. dkim_domain = get_dkim_domain('use_domain_sign_inbound')
  129. lua_util.debugm(N, task, 'inbound: use domain(%s) for signature: %s',
  130. settings.use_domain_sign_inbound, dkim_domain)
  131. else
  132. dkim_domain = get_dkim_domain('use_domain')
  133. lua_util.debugm(N, task, 'use domain(%s) for signature: %s',
  134. settings.use_domain, dkim_domain)
  135. end
  136. if not dkim_domain then
  137. lua_util.debugm(N, task, 'could not extract dkim domain')
  138. return false,{}
  139. end
  140. if settings.use_esld then
  141. dkim_domain = rspamd_util.get_tld(dkim_domain)
  142. if hdom then
  143. hdom = rspamd_util.get_tld(hdom)
  144. end
  145. if edom then
  146. edom = rspamd_util.get_tld(edom)
  147. end
  148. end
  149. lua_util.debugm(N, task, 'final DKIM domain: %s', dkim_domain)
  150. if edom and hdom and not settings.allow_hdrfrom_mismatch and hdom ~= edom then
  151. if settings.allow_hdrfrom_mismatch_local and is_local then
  152. lua_util.debugm(N, task, 'domain mismatch allowed for local IP: %1 != %2', hdom, edom)
  153. elseif settings.allow_hdrfrom_mismatch_sign_networks and is_sign_networks then
  154. lua_util.debugm(N, task, 'domain mismatch allowed for sign_networks: %1 != %2', hdom, edom)
  155. else
  156. lua_util.debugm(N, task, 'domain mismatch not allowed: %1 != %2', hdom, edom)
  157. return false,{}
  158. end
  159. end
  160. if auser and not settings.allow_username_mismatch then
  161. if not udom then
  162. lua_util.debugm(N, task, 'couldnt find domain in username')
  163. return false,{}
  164. end
  165. if settings.use_esld then
  166. udom = rspamd_util.get_tld(udom)
  167. end
  168. if udom ~= dkim_domain then
  169. lua_util.debugm(N, task, 'user domain mismatch')
  170. return false,{}
  171. end
  172. end
  173. local p = {}
  174. if settings.domain[dkim_domain] then
  175. p.selector = settings.domain[dkim_domain].selector
  176. p.key = settings.domain[dkim_domain].path
  177. end
  178. if not p.key and p.selector then
  179. local key_var = "dkim_key"
  180. local selector_var = "dkim_selector"
  181. if N == "arc" then
  182. key_var = "arc_key"
  183. selector_var = "arc_selector"
  184. end
  185. p.key = task:get_mempool():get_variable(key_var)
  186. local selector_override = task:get_mempool():get_variable(selector_var)
  187. if selector_override then
  188. p.selector = selector_override
  189. end
  190. if (not p.key or not p.selector) and (not (settings.try_fallback or
  191. settings.use_redis or settings.selector_map
  192. or settings.path_map)) then
  193. lua_util.debugm(N, task, 'dkim unconfigured and fallback disabled')
  194. return false,{}
  195. end
  196. lua_util.debugm(N, task, 'override selector and key to %s:%s', p.key, p.selector)
  197. end
  198. if not p.selector and settings.selector_map then
  199. local data = settings.selector_map:get_key(dkim_domain)
  200. if data then
  201. p.selector = data
  202. lua_util.debugm(N, task, 'override selector to "%s" using selector_map', p.selector)
  203. elseif not settings.try_fallback then
  204. lua_util.debugm(N, task, 'no selector for %s', dkim_domain)
  205. return false,{}
  206. end
  207. end
  208. if not p.key and settings.path_map then
  209. local data = settings.path_map:get_key(dkim_domain)
  210. if data then
  211. p.key = data
  212. lua_util.debugm(N, task, 'override key to "%s" using path_map', p.key)
  213. elseif not settings.try_fallback then
  214. lua_util.debugm(N, task, 'no key for %s', dkim_domain)
  215. return false,{}
  216. end
  217. end
  218. if not p.key then
  219. if not settings.use_redis then
  220. p.key = settings.path
  221. lua_util.debugm(N, task, 'use default key "%s" from path', p.key)
  222. end
  223. end
  224. if not p.selector then
  225. p.selector = settings.selector
  226. lua_util.debugm(N, task, 'use default selector "%s"', p.selector)
  227. end
  228. p.domain = dkim_domain
  229. return true,p
  230. end
  231. exports.prepare_dkim_signing = prepare_dkim_signing
  232. return exports