diff options
Diffstat (limited to '')
-rw-r--r-- | lualib/lua_ffi/common.lua | 45 | ||||
-rw-r--r-- | lualib/lua_ffi/dkim.lua | 144 | ||||
-rw-r--r-- | lualib/lua_ffi/init.lua | 59 | ||||
-rw-r--r-- | lualib/lua_ffi/linalg.lua | 87 | ||||
-rw-r--r-- | lualib/lua_ffi/spf.lua | 143 |
5 files changed, 478 insertions, 0 deletions
diff --git a/lualib/lua_ffi/common.lua b/lualib/lua_ffi/common.lua new file mode 100644 index 0000000..4076cfa --- /dev/null +++ b/lualib/lua_ffi/common.lua @@ -0,0 +1,45 @@ +--[[ +Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com> + +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/common +-- Common ffi definitions +--]] + +local ffi = require 'ffi' + +ffi.cdef [[ +struct GString { + char *str; + size_t len; + size_t allocated_len; +}; +struct GArray { + char *data; + unsigned len; +}; +typedef void (*ref_dtor_cb_t)(void *data); +struct ref_entry_s { + unsigned int refcount; + ref_dtor_cb_t dtor; +}; + +void g_string_free (struct GString *st, int free_data); +void g_free (void *p); +long rspamd_snprintf (char *buf, long max, const char *fmt, ...); +]] + +return {}
\ No newline at end of file diff --git a/lualib/lua_ffi/dkim.lua b/lualib/lua_ffi/dkim.lua new file mode 100644 index 0000000..e4592c2 --- /dev/null +++ b/lualib/lua_ffi/dkim.lua @@ -0,0 +1,144 @@ +--[[ +Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com> + +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/dkim +-- This module contains ffi interfaces to DKIM +--]] + +local ffi = require 'ffi' + +ffi.cdef [[ +struct rspamd_dkim_sign_context_s; +struct rspamd_dkim_key_s; +struct rspamd_task; +enum rspamd_dkim_key_format { + RSPAMD_DKIM_KEY_FILE = 0, + RSPAMD_DKIM_KEY_PEM, + RSPAMD_DKIM_KEY_BASE64, + RSPAMD_DKIM_KEY_RAW, +}; +enum rspamd_dkim_type { + RSPAMD_DKIM_NORMAL, + RSPAMD_DKIM_ARC_SIG, + RSPAMD_DKIM_ARC_SEAL +}; +struct rspamd_dkim_sign_context_s* +rspamd_create_dkim_sign_context (struct rspamd_task *task, + struct rspamd_dkim_key_s *priv_key, + int headers_canon, + int body_canon, + const char *dkim_headers, + enum rspamd_dkim_type type, + void *unused); +struct rspamd_dkim_key_s* rspamd_dkim_sign_key_load (const char *what, size_t len, + enum rspamd_dkim_key_format, + void *err); +void rspamd_dkim_key_unref (struct rspamd_dkim_key_s *k); +struct GString *rspamd_dkim_sign (struct rspamd_task *task, + const char *selector, + const char *domain, + unsigned long expire, + size_t len, + unsigned int idx, + const char *arc_cv, + struct rspamd_dkim_sign_context_s *ctx); +]] + +local function load_sign_key(what, format) + if not format then + format = ffi.C.RSPAMD_DKIM_KEY_PEM + else + if format == 'file' then + format = ffi.C.RSPAMD_DKIM_KEY_FILE + elseif format == 'base64' then + format = ffi.C.RSPAMD_DKIM_KEY_BASE64 + elseif format == 'raw' then + format = ffi.C.RSPAMD_DKIM_KEY_RAW + else + return nil, 'unknown key format' + end + end + + return ffi.C.rspamd_dkim_sign_key_load(what, #what, format, nil) +end + +local default_dkim_headers = "(o)from:(o)sender:(o)reply-to:(o)subject:(o)date:(o)message-id:" .. + "(o)to:(o)cc:(o)mime-version:(o)content-type:(o)content-transfer-encoding:" .. + "resent-to:resent-cc:resent-from:resent-sender:resent-message-id:" .. + "(o)in-reply-to:(o)references:list-id:list-owner:list-unsubscribe:" .. + "list-subscribe:list-post:(o)openpgp:(o)autocrypt" + +local function create_sign_context(task, privkey, dkim_headers, sign_type) + if not task or not privkey then + return nil, 'invalid arguments' + end + + if not dkim_headers then + dkim_headers = default_dkim_headers + end + + if not sign_type then + sign_type = 'dkim' + end + + if sign_type == 'dkim' then + sign_type = ffi.C.RSPAMD_DKIM_NORMAL + elseif sign_type == 'arc-sig' then + sign_type = ffi.C.RSPAMD_DKIM_ARC_SIG + elseif sign_type == 'arc-seal' then + sign_type = ffi.C.RSPAMD_DKIM_ARC_SEAL + else + return nil, 'invalid sign type' + end + + return ffi.C.rspamd_create_dkim_sign_context(task:topointer(), privkey, + 1, 1, dkim_headers, sign_type, nil) +end + +local function do_sign(task, sign_context, selector, domain, + expire, len, arc_idx) + if not task or not sign_context or not selector or not domain then + return nil, 'invalid arguments' + end + + if not expire then + expire = 0 + end + if not len then + len = 0 + end + if not arc_idx then + arc_idx = 0 + end + + local gstring = ffi.C.rspamd_dkim_sign(task:topointer(), selector, domain, expire, len, arc_idx, nil, sign_context) + + if not gstring then + return nil, 'cannot sign' + end + + local ret = ffi.string(gstring.str, gstring.len) + ffi.C.g_string_free(gstring, true) + + return ret +end + +return { + load_sign_key = load_sign_key, + create_sign_context = create_sign_context, + do_sign = do_sign +} diff --git a/lualib/lua_ffi/init.lua b/lualib/lua_ffi/init.lua new file mode 100644 index 0000000..efbbc7a --- /dev/null +++ b/lualib/lua_ffi/init.lua @@ -0,0 +1,59 @@ +--[[ +Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com> + +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 +-- This module contains ffi interfaces (requires luajit or lua-ffi) +--]] + +local ffi + +local exports = {} + +if type(jit) == 'table' then + ffi = require "ffi" + local NULL = ffi.new 'void*' + + exports.is_null = function(o) + return o ~= NULL + end +else + local ret, result_or_err = pcall(require, 'ffi') + + if not ret then + return {} + end + + ffi = result_or_err + -- Lua ffi + local NULL = ffi.NULL or ffi.C.NULL + exports.is_null = function(o) + return o ~= NULL + end +end + +pcall(ffi.load, "rspamd-server", true) +exports.common = require "lua_ffi/common" +exports.dkim = require "lua_ffi/dkim" +exports.spf = require "lua_ffi/spf" +exports.linalg = require "lua_ffi/linalg" + +for k, v in pairs(ffi) do + -- Preserve all stuff to use lua_ffi as ffi itself + exports[k] = v +end + +return exports
\ No newline at end of file diff --git a/lualib/lua_ffi/linalg.lua b/lualib/lua_ffi/linalg.lua new file mode 100644 index 0000000..2df488a --- /dev/null +++ b/lualib/lua_ffi/linalg.lua @@ -0,0 +1,87 @@ +--[[ +Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com> + +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/linalg +-- This module contains ffi interfaces to linear algebra routines +--]] + +local ffi = require 'ffi' + +local exports = {} + +ffi.cdef [[ + void kad_sgemm_simple(int trans_A, int trans_B, int M, int N, int K, const float *A, const float *B, float *C); + bool kad_ssyev_simple (int N, float *A, float *output); +]] + +local function table_to_ffi(a, m, n) + local a_conv = ffi.new("float[?]", m * n) + for i = 1, m or #a do + for j = 1, n or #a[1] do + a_conv[(i - 1) * n + (j - 1)] = a[i][j] + end + end + return a_conv +end + +local function ffi_to_table(a, m, n) + local res = {} + + for i = 0, m - 1 do + res[i + 1] = {} + for j = 0, n - 1 do + res[i + 1][j + 1] = a[i * n + j] + end + end + + return res +end + +exports.sgemm = function(a, m, b, n, k, trans_a, trans_b) + if type(a) == 'table' then + -- Need to convert, slow! + a = table_to_ffi(a, m, k) + end + if type(b) == 'table' then + b = table_to_ffi(b, k, n) + end + local res = ffi.new("float[?]", m * n) + ffi.C.kad_sgemm_simple(trans_a or 0, trans_b or 0, m, n, k, ffi.cast('const float*', a), + ffi.cast('const float*', b), ffi.cast('float*', res)) + return res +end + +exports.eigen = function(a, n) + if type(a) == 'table' then + -- Need to convert, slow! + n = n or #a + a = table_to_ffi(a, n, n) + end + + local res = ffi.new("float[?]", n) + + if ffi.C.kad_ssyev_simple(n, ffi.cast('float*', a), res) then + return res, a + end + + return nil +end + +exports.ffi_to_table = ffi_to_table +exports.table_to_ffi = table_to_ffi + +return exports
\ No newline at end of file diff --git a/lualib/lua_ffi/spf.lua b/lualib/lua_ffi/spf.lua new file mode 100644 index 0000000..0f982f2 --- /dev/null +++ b/lualib/lua_ffi/spf.lua @@ -0,0 +1,143 @@ +--[[ +Copyright (c) 2022, Vsevolod Stakhov <vsevolod@rspamd.com> + +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 +}
\ No newline at end of file |