aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/lua/spf.lua
blob: 26a47fdd8f6eb4cc8903ac1536e8717eabdf85fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
--[[
Copyright (c) 2019, Vsevolod Stakhov <vsevolod@highsecure.ru>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--

local N = "spf"
local lua_util = require "lua_util"
local rspamd_spf = require "rspamd_spf"
local bit = require "bit"

if confighelp then
  rspamd_config:add_example(nil, N,
      'Performs SPF checks',
      [[
spf {
  # Enable module
  enabled = true
  # Number of elements in the cache of parsed SPF records
  spf_cache_size = 2048;
  # Default max expire for an element in this cache
  spf_cache_expire = 1d;
  # Whitelist IPs from checks
  whitelist = "/path/to/some/file";
  # Maximum number of recursive DNS subrequests (e.g. includes chanin length)
  max_dns_nesting = 10;
  # Maximum count of DNS requests per record
  max_dns_requests = 30;
  # Minimum TTL enforced for all elements in SPF records
  min_cache_ttl = 5m;
  # Disable all IPv6 lookups
  disable_ipv6 = false;
}
  ]])
  return
end

local symbols = {
  fail = "R_SPF_FAIL",
  softfail = "R_SPF_SOFTFAIL",
  neutral = "R_SPF_NEUTRAL",
  allow = "R_SPF_ALLOW",
  dnsfail = "R_SPF_DNSFAIL",
  permfail = "R_SPF_PERMFAIL",
  na = "R_SPF_NA",
}

local default_config = {
  spf_cache_size = 2048,
  max_dns_nesting = 10,
  max_dns_requests = 30,
  whitelist = nil,
  min_cache_ttl = 60 * 5,
  disable_ipv6 = false,
  symbols = symbols
}

local local_config = rspamd_config:get_all_opt('spf')

if local_config then
  local_config = lua_util.override_defaults(default_config, local_config)
else
  local_config = default_config
end

local function spf_check_callback(task)
  local function flag_to_symbol(fl)
    if bit.band(fl, rspamd_spf.flags.temp_fail) ~= 0 then
      return local_config.symbols.dnsfail
    elseif bit.band(fl, rspamd_spf.flags.perm_fail) ~= 0 then
      return local_config.symbols.permfail
    elseif bit.band(fl, rspamd_spf.flags.na) ~= 0 then
      return local_config.symbols.na
    end

    return 'SPF_UNKNOWN'
  end

  local function policy_decode(res)
    if res == rspamd_spf.policy.fail then
      return local_config.symbols.fail,'-'
    elseif res == rspamd_spf.policy.pass then
      return local_config.symbols.allow,'+'
    elseif res == rspamd_spf.policy.soft_fail then
      return local_config.symbols.softfail,'~'
    elseif res == rspamd_spf.policy.neutral then
      return local_config.symbols.neutral,'?'
    end

    return 'SPF_UNKNOWN','?'
  end

  local function spf_resolved_cb(record, flags, err)
    if record then
      local result, flag_or_policy, error_or_addr = record:check_ip(task:get_from_ip())

      if result then
        local sym,code = policy_decode(flag_or_policy)
        local opt = string.format('%s%s', code, error_or_addr.str or '???')
        if bit.band(flags, rspamd_spf.flags.cached) ~= 0 then
          opt = opt .. ':c'
        end
        task:insert_result(sym, 1.0, opt)
      else
        local sym = flag_to_symbol(flag_or_policy)
        task:insert_result(sym, 1.0, error_or_addr)
      end
    else
      local sym = flag_to_symbol(flags)
      task:insert_result(sym, 1.0, err)
    end
  end

  rspamd_spf.resolve(task, spf_resolved_cb)
end

-- Register all symbols and init rspamd_spf library
rspamd_spf.config(local_config)
local sym_id = rspamd_config:register_symbol{
  name = 'SPF_CHECK',
  type = 'callback',
  flags = 'fine,empty',
  groups = {'policies','spf'},
  score = 0.0,
  callback = spf_check_callback
}

for _,sym in pairs(local_config.symbols) do
  rspamd_config:register_symbol{
    name = sym,
    type = 'virtual',
    parent = sym_id,
    groups = {'policies', 'spf'},
  }
end