aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2019-12-02 13:11:55 +0000
committerVsevolod Stakhov <vsevolod@highsecure.ru>2019-12-02 13:11:55 +0000
commit2a0c89f2569b9a1507d757a0ed7a6a9dba03f65d (patch)
tree624e47fbe46be30a92004dcaee28df758581eb89
parent1e67120aa5d9371e9b87c43701e987ce49108f9b (diff)
downloadrspamd-2a0c89f2569b9a1507d757a0ed7a6a9dba03f65d.tar.gz
rspamd-2a0c89f2569b9a1507d757a0ed7a6a9dba03f65d.zip
[Project] Preliminary SPF plugin in Lua
-rw-r--r--src/plugins/lua/spf.lua147
1 files changed, 147 insertions, 0 deletions
diff --git a/src/plugins/lua/spf.lua b/src/plugins/lua/spf.lua
new file mode 100644
index 000000000..ce7296c68
--- /dev/null
+++ b/src/plugins/lua/spf.lua
@@ -0,0 +1,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.temp_fail
+ elseif bit.band(fl, rspamd_spf.flags.perm_fail) ~= 0 then
+ return local_config.symbols.perm_fail
+ 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
+
+