--[[ Copyright (c) 2022, Vsevolod Stakhov 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. ]]-- --[[[ -- @module lua_ffi/spf -- This module contains ffi interfaces to SPF --]] local ffi = require 'ffi' ffi.cdef [[ enum spf_mech_e { SPF_FAIL, SPF_SOFT_FAIL, SPF_PASS, SPF_NEUTRAL }; static const unsigned RSPAMD_SPF_FLAG_IPV6 = (1 << 0); static const unsigned RSPAMD_SPF_FLAG_IPV4 = (1 << 1); static const unsigned RSPAMD_SPF_FLAG_ANY = (1 << 3); struct spf_addr { unsigned char addr6[16]; unsigned char addr4[4]; union { struct { uint16_t mask_v4; uint16_t mask_v6; } dual; uint32_t idx; } m; unsigned flags; enum spf_mech_e mech; char *spf_string; struct spf_addr *prev, *next; }; struct spf_resolved { char *domain; unsigned ttl; int temp_failed; int na; int perm_failed; uint64_t digest; struct GArray *elts; struct ref_entry_s ref; }; typedef void (*spf_cb_t)(struct spf_resolved *record, struct rspamd_task *task, void *data); struct rspamd_task; int rspamd_spf_resolve(struct rspamd_task *task, spf_cb_t callback, void *cbdata); const char * rspamd_spf_get_domain (struct rspamd_task *task); struct spf_resolved * spf_record_ref (struct spf_resolved *rec); void spf_record_unref (struct spf_resolved *rec); char * spf_addr_mask_to_string (struct spf_addr *addr); struct spf_addr * spf_addr_match_task (struct rspamd_task *task, struct spf_resolved *rec); ]] local function convert_mech(mech) if mech == ffi.C.SPF_FAIL then return 'fail' elseif mech == ffi.C.SPF_SOFT_FAIL then return 'softfail' elseif mech == ffi.C.SPF_PASS then return 'pass' elseif mech == ffi.C.SPF_NEUTRAL then return 'neutral' end end local NULL = ffi.new 'void*' local function spf_addr_tolua(ffi_spf_addr) local ipstr = ffi.C.spf_addr_mask_to_string(ffi_spf_addr) local ret = { res = convert_mech(ffi_spf_addr.mech), ipnet = ffi.string(ipstr), } if ffi_spf_addr.spf_string ~= NULL then ret.spf_str = ffi.string(ffi_spf_addr.spf_string) end ffi.C.g_free(ipstr) return ret end local function spf_resolve(task, cb) local function spf_cb(rec, _, _) if not rec then cb(false, 'record is empty') else local nelts = rec.elts.len local elts = ffi.cast("struct spf_addr *", rec.elts.data) local res = { addrs = {} } local digstr = ffi.new("char[64]") ffi.C.rspamd_snprintf(digstr, 64, "0x%xuL", rec.digest) res.digest = ffi.string(digstr) for i = 1, nelts do res.addrs[i] = spf_addr_tolua(elts[i - 1]) end local matched = ffi.C.spf_addr_match_task(task:topointer(), rec) if matched ~= NULL then cb(true, res, spf_addr_tolua(matched)) else cb(true, res, nil) end end end local ret = ffi.C.rspamd_spf_resolve(task:topointer(), spf_cb, nil) if not ret then cb(false, 'cannot perform resolving') end end local function spf_unref(rec) ffi.C.spf_record_unref(rec) end return { spf_resolve = spf_resolve, spf_unref = spf_unref }