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 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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 logger = require "rspamd_logger"
  19. local function check_violation(N, task, domain)
  20. -- Check for DKIM_REJECT
  21. local sym_check = 'R_DKIM_REJECT'
  22. if N == 'arc' then sym_check = 'ARC_REJECT' end
  23. if task:has_symbol(sym_check) then
  24. local sym = task:get_symbol(sym_check)
  25. logger.infox(task, 'skip signing for %s: violation %s found: %s',
  26. domain, sym_check, sym.options)
  27. return false
  28. end
  29. return true
  30. end
  31. local function insert_or_update_prop(N, task, p, prop, origin, data)
  32. if #p == 0 then
  33. local k = {}
  34. k[prop] = data
  35. table.insert(p, k)
  36. lua_util.debugm(N, task, 'add %s "%s" using %s', prop, data, origin)
  37. else
  38. for _, k in ipairs(p) do
  39. if not k[prop] then
  40. k[prop] = data
  41. lua_util.debugm(N, task, 'set %s to "%s" using %s', prop, data, origin)
  42. end
  43. end
  44. end
  45. end
  46. local function get_mempool_selectors(N, task)
  47. local p = {}
  48. local key_var = "dkim_key"
  49. local selector_var = "dkim_selector"
  50. if N == "arc" then
  51. key_var = "arc_key"
  52. selector_var = "arc_selector"
  53. end
  54. p.key = task:get_mempool():get_variable(key_var)
  55. p.selector = task:get_mempool():get_variable(selector_var)
  56. if (not p.key or not p.selector) then
  57. return false, {}
  58. end
  59. lua_util.debugm(N, task, 'override selector and key to %s:%s', p.key, p.selector)
  60. return true, p
  61. end
  62. local function parse_dkim_http_headers(N, task, settings)
  63. -- Configure headers
  64. local headers = {
  65. sign_header = settings.http_sign_header or "PerformDkimSign",
  66. sign_on_reject_header = settings.http_sign_on_reject_header_header or 'SignOnAuthFailed',
  67. domain_header = settings.http_domain_header or 'DkimDomain',
  68. selector_header = settings.http_selector_header or 'DkimSelector',
  69. key_header = settings.http_key_header or 'DkimPrivateKey'
  70. }
  71. if task:get_request_header(headers.sign_header) then
  72. local domain = task:get_request_header(headers.domain_header)
  73. local selector = task:get_request_header(headers.selector_header)
  74. local key = task:get_request_header(headers.key_header)
  75. if not (domain and selector and key) then
  76. logger.errx(task, 'missing required headers to sign email')
  77. return false,{}
  78. end
  79. -- Now check if we need to check the existing auth
  80. local hdr = task:get_request_header(headers.sign_on_reject_header)
  81. if not hdr or tostring(hdr) == '0' or tostring(hdr) == 'false' then
  82. if not check_violation(N, task, domain, selector) then
  83. return false, {}
  84. end
  85. end
  86. local p = {}
  87. local k = {
  88. domain = tostring(domain),
  89. rawkey = tostring(key),
  90. selector = tostring(selector),
  91. }
  92. table.insert(p, k)
  93. return true, p
  94. end
  95. lua_util.debugm(N, task, 'no sign header %s', headers.sign_header)
  96. return false,{}
  97. end
  98. local function prepare_dkim_signing(N, task, settings)
  99. local is_local, is_sign_networks
  100. if settings.use_http_headers then
  101. return parse_dkim_http_headers(N, task, settings)
  102. end
  103. local auser = task:get_user()
  104. local ip = task:get_from_ip()
  105. if ip and ip:is_local() then
  106. is_local = true
  107. end
  108. if settings.auth_only and auser then
  109. lua_util.debugm(N, task, 'user is authenticated')
  110. elseif (settings.sign_networks and settings.sign_networks:get_key(ip)) then
  111. is_sign_networks = true
  112. lua_util.debugm(N, task, 'mail is from address in sign_networks')
  113. elseif settings.sign_local and is_local then
  114. lua_util.debugm(N, task, 'mail is from local address')
  115. elseif settings.sign_inbound and not is_local and not auser then
  116. lua_util.debugm(N, task, 'mail was sent to us')
  117. else
  118. lua_util.debugm(N, task, 'ignoring unauthenticated mail')
  119. return false,{}
  120. end
  121. local efrom = task:get_from('smtp')
  122. if not settings.allow_envfrom_empty and
  123. #(((efrom or E)[1] or E).addr or '') == 0 then
  124. lua_util.debugm(N, task, 'empty envelope from not allowed')
  125. return false,{}
  126. end
  127. local hfrom = task:get_from('mime')
  128. if not settings.allow_hdrfrom_multiple and (hfrom or E)[2] then
  129. lua_util.debugm(N, task, 'multiple header from not allowed')
  130. return false,{}
  131. end
  132. local eto = task:get_recipients(0)
  133. local dkim_domain
  134. local hdom = ((hfrom or E)[1] or E).domain
  135. local edom = ((efrom or E)[1] or E).domain
  136. local tdom = ((eto or E)[1] or E).domain
  137. local udom = string.match(auser or '', '.*@(.*)')
  138. local function get_dkim_domain(dtype)
  139. if settings[dtype] == 'header' then
  140. return hdom
  141. elseif settings[dtype] == 'envelope' then
  142. return edom
  143. elseif settings[dtype] == 'auth' then
  144. return udom
  145. elseif settings[dtype] == 'recipient' then
  146. return tdom
  147. else
  148. return settings[dtype]:lower()
  149. end
  150. end
  151. if hdom then
  152. hdom = hdom:lower()
  153. end
  154. if edom then
  155. edom = edom:lower()
  156. end
  157. if udom then
  158. udom = udom:lower()
  159. end
  160. if tdom then
  161. tdom = tdom:lower()
  162. end
  163. if settings.use_domain_sign_networks and is_sign_networks then
  164. dkim_domain = get_dkim_domain('use_domain_sign_networks')
  165. lua_util.debugm(N, task, 'sign_networks: use domain(%s) for signature: %s',
  166. settings.use_domain_sign_networks, dkim_domain)
  167. elseif settings.use_domain_sign_local and is_local then
  168. dkim_domain = get_dkim_domain('use_domain_sign_local')
  169. lua_util.debugm(N, task, 'local: use domain(%s) for signature: %s',
  170. settings.use_domain_sign_local, dkim_domain)
  171. elseif settings.use_domain_sign_inbound and not is_local and not auser then
  172. dkim_domain = get_dkim_domain('use_domain_sign_inbound')
  173. lua_util.debugm(N, task, 'inbound: use domain(%s) for signature: %s',
  174. settings.use_domain_sign_inbound, dkim_domain)
  175. elseif settings.use_domain_custom then
  176. if type(settings.use_domain_custom) == 'string' then
  177. -- Load custom function
  178. local loadstring = loadstring or load
  179. local ret, res_or_err = pcall(loadstring(settings.use_domain_custom))
  180. if ret then
  181. if type(res_or_err) == 'function' then
  182. settings.use_domain_custom = res_or_err
  183. dkim_domain = settings.use_domain_custom(task)
  184. lua_util.debugm(N, task, 'use custom domain for signing: %s',
  185. dkim_domain)
  186. else
  187. logger.errx(task, 'cannot load dkim domain custom script: invalid type: %s, expected function',
  188. type(res_or_err))
  189. settings.use_domain_custom = nil
  190. end
  191. else
  192. logger.errx(task, 'cannot load dkim domain custom script: %s', res_or_err)
  193. settings.use_domain_custom = nil
  194. end
  195. else
  196. dkim_domain = settings.use_domain_custom(task)
  197. lua_util.debugm(N, task, 'use custom domain for signing: %s',
  198. dkim_domain)
  199. end
  200. else
  201. dkim_domain = get_dkim_domain('use_domain')
  202. lua_util.debugm(N, task, 'use domain(%s) for signature: %s',
  203. settings.use_domain, dkim_domain)
  204. end
  205. if not dkim_domain then
  206. lua_util.debugm(N, task, 'could not extract dkim domain')
  207. return false,{}
  208. end
  209. if settings.use_esld then
  210. dkim_domain = rspamd_util.get_tld(dkim_domain)
  211. if hdom then
  212. hdom = rspamd_util.get_tld(hdom)
  213. end
  214. if edom then
  215. edom = rspamd_util.get_tld(edom)
  216. end
  217. end
  218. lua_util.debugm(N, task, 'final DKIM domain: %s', dkim_domain)
  219. if edom and hdom and not settings.allow_hdrfrom_mismatch and hdom ~= edom then
  220. if settings.allow_hdrfrom_mismatch_local and is_local then
  221. lua_util.debugm(N, task, 'domain mismatch allowed for local IP: %1 != %2', hdom, edom)
  222. elseif settings.allow_hdrfrom_mismatch_sign_networks and is_sign_networks then
  223. lua_util.debugm(N, task, 'domain mismatch allowed for sign_networks: %1 != %2', hdom, edom)
  224. else
  225. lua_util.debugm(N, task, 'domain mismatch not allowed: %1 != %2', hdom, edom)
  226. return false,{}
  227. end
  228. end
  229. if auser and not settings.allow_username_mismatch then
  230. if not udom then
  231. lua_util.debugm(N, task, 'couldnt find domain in username')
  232. return false,{}
  233. end
  234. if settings.use_esld then
  235. udom = rspamd_util.get_tld(udom)
  236. end
  237. if udom ~= dkim_domain then
  238. lua_util.debugm(N, task, 'user domain mismatch')
  239. return false,{}
  240. end
  241. end
  242. local p = {}
  243. if settings.domain[dkim_domain] then
  244. -- support old style selector/paths
  245. if settings.domain[dkim_domain].selector or
  246. settings.domain[dkim_domain].path then
  247. local k = {}
  248. k.selector = settings.domain[dkim_domain].selector
  249. k.key = settings.domain[dkim_domain].path
  250. table.insert(p, k)
  251. end
  252. for _, s in ipairs((settings.domain[dkim_domain].selectors or {})) do
  253. lua_util.debugm(N, task, 'adding selector: %1', s)
  254. local k = {}
  255. k.selector = s.selector
  256. k.key = s.path
  257. table.insert(p, k)
  258. end
  259. end
  260. if #p == 0 then
  261. local ret, k = get_mempool_selectors(N, task)
  262. if ret then
  263. table.insert(p, k)
  264. lua_util.debugm(N, task, 'using mempool selector %s with key %s',
  265. k.selector, k.key)
  266. end
  267. end
  268. if settings.selector_map then
  269. local data = settings.selector_map:get_key(dkim_domain)
  270. if data then
  271. insert_or_update_prop(N, task, p, 'selector', 'selector_map', data)
  272. else
  273. lua_util.debugm(N, task, 'no selector in map for %s', dkim_domain)
  274. end
  275. end
  276. if settings.path_map then
  277. local data = settings.path_map:get_key(dkim_domain)
  278. if data then
  279. insert_or_update_prop(N, task, p, 'key', 'path_map', data)
  280. else
  281. lua_util.debugm(N, task, 'no key in map for %s', dkim_domain)
  282. end
  283. end
  284. if #p == 0 and not settings.try_fallback then
  285. lua_util.debugm(N, task, 'dkim unconfigured and fallback disabled')
  286. return false,{}
  287. end
  288. if not settings.use_redis then
  289. insert_or_update_prop(N, task, p, 'key',
  290. 'default path', settings.path)
  291. end
  292. insert_or_update_prop(N, task, p, 'selector',
  293. 'default selector', settings.selector)
  294. if settings.check_violation then
  295. if not check_violation(N, task, p.domain) then
  296. return false,{}
  297. end
  298. end
  299. insert_or_update_prop(N, task, p, 'domain', 'dkim_domain',
  300. dkim_domain)
  301. return true,p
  302. end
  303. exports.prepare_dkim_signing = prepare_dkim_signing
  304. return exports