123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- /*-
- * Copyright 2019 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.
- */
- /**
- * @file lua_spf.c
- * This module exports spf functions to Lua
- */
-
- #include "lua_common.h"
- #include "libserver/spf.h"
- #include "libutil/ref.h"
-
- #define SPF_RECORD_CLASS "rspamd{spf_record}"
-
- LUA_FUNCTION_DEF (spf, resolve);
- LUA_FUNCTION_DEF (spf, config);
-
- LUA_FUNCTION_DEF (spf_record, check_ip);
- LUA_FUNCTION_DEF (spf_record, dtor);
- LUA_FUNCTION_DEF (spf_record, get_domain);
- LUA_FUNCTION_DEF (spf_record, get_elts);
- LUA_FUNCTION_DEF (spf_record, get_ttl);
- LUA_FUNCTION_DEF (spf_record, get_timestamp);
- LUA_FUNCTION_DEF (spf_record, get_digest);
-
- static luaL_reg rspamd_spf_f[] = {
- LUA_INTERFACE_DEF (spf, resolve),
- LUA_INTERFACE_DEF (spf, config),
- {NULL, NULL},
- };
-
- static luaL_reg rspamd_spf_record_m[] = {
- LUA_INTERFACE_DEF (spf_record, check_ip),
- LUA_INTERFACE_DEF (spf_record, get_domain),
- LUA_INTERFACE_DEF (spf_record, get_ttl),
- LUA_INTERFACE_DEF (spf_record, get_digest),
- LUA_INTERFACE_DEF (spf_record, get_elts),
- LUA_INTERFACE_DEF (spf_record, get_timestamp),
- {"__gc", lua_spf_record_dtor},
- {NULL, NULL},
- };
-
- struct rspamd_lua_spf_cbdata {
- struct rspamd_task *task;
- lua_State *L;
- struct rspamd_symcache_item *item;
- gint cbref;
- ref_entry_t ref;
- };
-
- static gint
- lua_load_spf (lua_State * L)
- {
- lua_newtable (L);
-
- /* Create integer arguments to check SPF results */
- lua_newtable (L);
- lua_pushinteger (L, SPF_FAIL);
- lua_setfield (L, -2, "fail");
- lua_pushinteger (L, SPF_PASS);
- lua_setfield (L, -2, "pass");
- lua_pushinteger (L, SPF_NEUTRAL);
- lua_setfield (L, -2, "neutral");
- lua_pushinteger (L, SPF_SOFT_FAIL);
- lua_setfield (L, -2, "soft_fail");
-
- lua_setfield (L, -2, "policy");
-
- /* Flags stuff */
- lua_newtable (L);
-
- lua_pushinteger (L, RSPAMD_SPF_RESOLVED_TEMP_FAILED);
- lua_setfield (L, -2, "temp_fail");
- lua_pushinteger (L, RSPAMD_SPF_RESOLVED_NA);
- lua_setfield (L, -2, "na");
- lua_pushinteger (L, RSPAMD_SPF_RESOLVED_PERM_FAILED);
- lua_setfield (L, -2, "perm_fail");
- lua_pushinteger (L, RSPAMD_SPF_FLAG_CACHED);
- lua_setfield (L, -2, "cached");
-
- lua_setfield (L, -2, "flags");
-
- luaL_register (L, NULL, rspamd_spf_f);
-
- return 1;
- }
-
- void luaopen_spf (lua_State *L)
- {
- rspamd_lua_new_class (L, SPF_RECORD_CLASS, rspamd_spf_record_m);
- lua_pop (L, 1); /* No need in metatable... */
-
- rspamd_lua_add_preload (L, "rspamd_spf", lua_load_spf);
- lua_settop (L, 0);
- }
-
- static void
- lua_spf_push_result (struct rspamd_lua_spf_cbdata *cbd, gint code_flags,
- struct spf_resolved *resolved, const gchar *err)
- {
- g_assert (cbd != NULL);
- REF_RETAIN (cbd);
-
- lua_pushcfunction (cbd->L, &rspamd_lua_traceback);
- gint err_idx = lua_gettop (cbd->L);
-
- lua_rawgeti (cbd->L, LUA_REGISTRYINDEX, cbd->cbref);
-
- if (resolved) {
- struct spf_resolved **presolved;
-
- presolved = lua_newuserdata (cbd->L, sizeof (*presolved));
- rspamd_lua_setclass (cbd->L, SPF_RECORD_CLASS, -1);
- *presolved = spf_record_ref (resolved);
- }
- else {
- lua_pushnil (cbd->L);
- }
-
- lua_pushinteger (cbd->L, code_flags);
-
- if (err) {
- lua_pushstring (cbd->L, err);
- }
- else {
- lua_pushnil (cbd->L);
- }
-
- if (lua_pcall (cbd->L, 3, 0, err_idx) != 0) {
- struct rspamd_task *task = cbd->task;
-
- msg_err_task ("cannot call callback function for spf: %s",
- lua_tostring (cbd->L, -1));
- }
-
- lua_settop (cbd->L, err_idx - 1);
-
- REF_RELEASE (cbd);
- }
-
- static void
- lua_spf_dtor (struct rspamd_lua_spf_cbdata *cbd)
- {
- if (cbd) {
- luaL_unref (cbd->L, LUA_REGISTRYINDEX, cbd->cbref);
- if (cbd->item) {
- rspamd_symcache_item_async_dec_check (cbd->task, cbd->item,
- "lua_spf");
- }
- }
- }
-
- static void
- spf_lua_lib_callback (struct spf_resolved *record, struct rspamd_task *task,
- gpointer ud)
- {
- struct rspamd_lua_spf_cbdata *cbd = (struct rspamd_lua_spf_cbdata *)ud;
-
- if (record) {
- if ((record->flags & RSPAMD_SPF_RESOLVED_NA)) {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_NA, NULL,
- "no SPF record");
- }
- else if (record->elts->len == 0) {
- if (record->flags & RSPAMD_SPF_RESOLVED_PERM_FAILED) {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
- "bad SPF record");
- }
- else if ((record->flags & RSPAMD_SPF_RESOLVED_TEMP_FAILED)) {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_TEMP_FAILED, NULL,
- "temporary DNS error");
- }
- else {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
- "empty SPF record");
- }
- }
- else if (record->domain) {
- spf_record_ref (record);
- lua_spf_push_result (cbd, record->flags, record, NULL);
- spf_record_unref (record);
- }
- else {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
- "internal error: non empty record for no domain");
- }
- }
- else {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
- "internal error: no record");
- }
-
- REF_RELEASE (cbd);
- }
-
- /***
- * @function rspamd_spf.resolve(task, callback)
- * Resolves SPF credentials for a task
- * @param {rspamd_task} task task
- * @param {function} callback callback that is called on spf resolution
- */
- gint
- lua_spf_resolve (lua_State * L)
- {
- struct rspamd_task *task = lua_check_task (L, 1);
-
- if (task && lua_isfunction (L, 2)) {
- struct rspamd_lua_spf_cbdata *cbd = rspamd_mempool_alloc0 (task->task_pool,
- sizeof (*cbd));
- struct rspamd_spf_cred *spf_cred;
-
- cbd->task = task;
- cbd->L = L;
- lua_pushvalue (L, 2);
- cbd->cbref = luaL_ref (L, LUA_REGISTRYINDEX);
- /* TODO: make it as an optional parameter */
- spf_cred = rspamd_spf_get_cred (task);
- cbd->item = rspamd_symcache_get_cur_item (task);
-
- if (cbd->item) {
- rspamd_symcache_item_async_inc (task, cbd->item, "lua_spf");
- }
- REF_INIT_RETAIN (cbd, lua_spf_dtor);
-
- if (!rspamd_spf_resolve (task, spf_lua_lib_callback, cbd, spf_cred)) {
- msg_info_task ("cannot make spf request for %s",
- spf_cred ? spf_cred->domain : "empty domain");
- if (spf_cred) {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_TEMP_FAILED,
- NULL, "DNS failed");
- }
- else {
- lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_NA,
- NULL, "No domain");
- }
- REF_RELEASE (cbd);
- }
- }
- else {
- return luaL_error (L, "invalid arguments");
- }
-
- return 0;
- }
-
- static gint
- lua_spf_record_dtor (lua_State *L)
- {
- struct spf_resolved *record;
-
- RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
- struct spf_resolved,
- record);
-
- if (record) {
- spf_record_unref (record);
- }
-
- return 0;
- }
-
- static void
- lua_spf_push_spf_addr (lua_State *L, struct spf_addr *addr)
- {
- gchar *addr_mask;
-
- lua_createtable (L, 0, 4);
-
- lua_pushinteger (L, addr->mech);
- lua_setfield (L, -2, "result");
- lua_pushinteger (L, addr->flags);
- lua_setfield (L, -2, "flags");
-
- if (addr->spf_string) {
- lua_pushstring (L, addr->spf_string);
- lua_setfield (L, -2, "str");
- }
-
- addr_mask = spf_addr_mask_to_string (addr);
-
- if (addr_mask) {
- lua_pushstring (L, addr_mask);
- lua_setfield (L, -2, "addr");
- g_free (addr_mask);
- }
- }
-
- static gint
- spf_check_element (lua_State *L, struct spf_resolved *rec, struct spf_addr *addr,
- struct rspamd_lua_ip *ip)
- {
- gboolean res = FALSE;
- const guint8 *s, *d;
- guint af, mask, bmask, addrlen;
-
-
- if (addr->flags & RSPAMD_SPF_FLAG_TEMPFAIL) {
- /* Ignore failed addresses */
-
- return -1;
- }
-
- af = rspamd_inet_address_get_af (ip->addr);
- /* Basic comparing algorithm */
- if (((addr->flags & RSPAMD_SPF_FLAG_IPV6) && af == AF_INET6) ||
- ((addr->flags & RSPAMD_SPF_FLAG_IPV4) && af == AF_INET)) {
- d = rspamd_inet_address_get_hash_key (ip->addr, &addrlen);
-
- if (af == AF_INET6) {
- s = (const guint8 *)addr->addr6;
- mask = addr->m.dual.mask_v6;
- }
- else {
- s = (const guint8 *)addr->addr4;
- mask = addr->m.dual.mask_v4;
- }
-
- /* Compare the first bytes */
- bmask = mask / CHAR_BIT;
- if (mask > addrlen * CHAR_BIT) {
- /* XXX: add logging */
- }
- else if (memcmp (s, d, bmask) == 0) {
- if (bmask * CHAR_BIT < mask) {
- /* Compare the remaining bits */
- s += bmask;
- d += bmask;
- mask = (0xff << (CHAR_BIT - (mask - bmask * 8))) & 0xff;
-
- if ((*s & mask) == (*d & mask)) {
- res = TRUE;
- }
- }
- else {
- res = TRUE;
- }
- }
- }
- else {
- if (addr->flags & RSPAMD_SPF_FLAG_ANY) {
- res = TRUE;
- }
- else {
- res = FALSE;
- }
- }
-
- if (res) {
- if (addr->flags & RSPAMD_SPF_FLAG_ANY) {
- if (rec->flags & RSPAMD_SPF_RESOLVED_PERM_FAILED) {
- lua_pushboolean (L, false);
- lua_pushinteger (L, RSPAMD_SPF_RESOLVED_PERM_FAILED);
- lua_pushfstring (L, "%cany", spf_mech_char (addr->mech));
- }
- else if (rec->flags & RSPAMD_SPF_RESOLVED_TEMP_FAILED) {
- lua_pushboolean (L, false);
- lua_pushinteger (L, RSPAMD_SPF_RESOLVED_TEMP_FAILED);
- lua_pushfstring (L, "%cany", spf_mech_char (addr->mech));
- }
- else {
- lua_pushboolean (L, true);
- lua_pushinteger (L, addr->mech);
- lua_spf_push_spf_addr (L, addr);
- }
- }
- else {
- lua_pushboolean (L, true);
- lua_pushinteger (L, addr->mech);
- lua_spf_push_spf_addr (L, addr);
- }
-
- return 3;
- }
-
- return -1;
- }
-
- /***
- * @method rspamd_spf_record:check_ip(ip)
- * Checks the processed record versus a specific IP address. This function
- * returns 3 values normally:
- * 1. Boolean check result
- * 2. If result is `false` then the second value is the error flag (e.g. rspamd_spf.flags.temp_fail), otherwise it will be an SPF method
- * 3. If result is `false` then this will be an error string, otherwise - an SPF string (e.g. `mx` or `ip4:x.y.z.1`)
- * @param {rspamd_ip|string} ip address
- * @return {result,flag_or_policy,error_or_addr} - triplet
- */
- static gint
- lua_spf_record_check_ip (lua_State *L)
- {
- struct spf_resolved *record;
- RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
- struct spf_resolved,
- record);
- struct rspamd_lua_ip *ip = NULL;
- gint nres = 0;
- gboolean need_free_ip = FALSE;
-
- if (lua_type (L, 2) == LUA_TUSERDATA) {
- ip = lua_check_ip (L, 2);
- }
- else if (lua_type (L, 2) == LUA_TSTRING) {
- const gchar *ip_str;
- gsize iplen;
-
- ip = g_malloc0 (sizeof (struct rspamd_lua_ip));
- ip_str = lua_tolstring (L, 2, &iplen);
-
- if (!rspamd_parse_inet_address (&ip->addr,
- ip_str, iplen, RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
- g_free (ip);
- ip = NULL;
- }
- else {
- need_free_ip = TRUE;
- }
- }
-
- if (record && ip && ip->addr) {
- for (guint i = 0; i < record->elts->len; i ++) {
- struct spf_addr *addr = &g_array_index (record->elts, struct spf_addr, i);
- if ((nres = spf_check_element (L, record, addr, ip)) > 0) {
- if (need_free_ip) {
- g_free (ip);
- }
-
- return nres;
- }
- }
- }
- else {
- if (need_free_ip) {
- g_free (ip);
- }
-
- return luaL_error (L, "invalid arguments");
- }
-
- if (need_free_ip) {
- g_free (ip);
- }
-
- /* If we are here it means that there is no ALL record */
- /*
- * According to https://tools.ietf.org/html/rfc7208#section-4.7 it means
- * SPF neutral
- */
- struct spf_addr fake_all;
-
- fake_all.mech = SPF_NEUTRAL;
- fake_all.flags = RSPAMD_SPF_FLAG_ANY;
- fake_all.spf_string = "all";
-
- lua_pushboolean (L, true);
- lua_pushinteger (L, SPF_NEUTRAL);
- lua_spf_push_spf_addr (L, &fake_all);
-
- return 3;
- }
-
- /***
- * @method rspamd_spf_record:get_domain()
- * Returns domain for the specific spf record
- */
- static gint
- lua_spf_record_get_domain (lua_State *L)
- {
- struct spf_resolved *record;
- RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
- struct spf_resolved,
- record);
-
- if (record) {
- lua_pushstring (L, record->domain);
- }
- else {
- return luaL_error (L, "invalid arguments");
- }
-
- return 1;
- }
-
- /***
- * @method rspamd_spf_record:get_ttl()
- * Returns ttl for the specific spf record
- */
- static gint
- lua_spf_record_get_ttl (lua_State *L)
- {
- struct spf_resolved *record;
- RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
- struct spf_resolved,
- record);
-
- if (record) {
- lua_pushinteger (L, record->ttl);
- }
- else {
- return luaL_error (L, "invalid arguments");
- }
-
- return 1;
- }
-
- /***
- * @method rspamd_spf_record:get_timestamp()
- * Returns ttl for the specific spf record
- */
- static gint
- lua_spf_record_get_timestamp (lua_State *L)
- {
- struct spf_resolved *record;
- RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
- struct spf_resolved,
- record);
-
- if (record) {
- lua_pushnumber (L, record->timestamp);
- }
- else {
- return luaL_error (L, "invalid arguments");
- }
-
- return 1;
- }
-
- /***
- * @method rspamd_spf_record:get_digest()
- * Returns string hex representation of the record digest (fast hash function)
- */
- static gint
- lua_spf_record_get_digest (lua_State *L)
- {
- struct spf_resolved *record;
- RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
- struct spf_resolved,
- record);
-
- if (record) {
- gchar hexbuf[64];
-
- rspamd_snprintf (hexbuf, sizeof (hexbuf), "%xuL", record->digest);
- lua_pushstring (L, hexbuf);
- }
- else {
- return luaL_error (L, "invalid arguments");
- }
-
- return 1;
- }
-
- /***
- * @method rspamd_spf_record:get_elts()
- * Returns a list of all elements in an SPF record. Each element is a table with the
- * following fields:
- *
- * - result - mech flag from rspamd_spf.results
- * - flags - all flags
- * - addr - address and mask as a string
- * - str - string representation (if available)
- */
- static gint
- lua_spf_record_get_elts (lua_State *L)
- {
- struct spf_resolved *record;
- RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
- struct spf_resolved,
- record);
-
- if (record) {
- guint i;
- struct spf_addr *addr;
-
- lua_createtable (L, record->elts->len, 0);
-
- for (i = 0; i < record->elts->len; i ++) {
- addr = (struct spf_addr *)&g_array_index (record->elts,
- struct spf_addr, i);
- lua_spf_push_spf_addr (L, addr);
-
- lua_rawseti (L, -2, i + 1);
- }
- }
- else {
- return luaL_error (L, "invalid arguments");
- }
-
- return 1;
- }
-
- /***
- * @function rspamd_spf.config(object)
- * Configures SPF library according to the UCL config
- * @param {table} object configuration object
- */
- gint
- lua_spf_config (lua_State * L)
- {
- ucl_object_t *config_obj = ucl_object_lua_import (L, 1);
-
- if (config_obj) {
- spf_library_config (config_obj);
- ucl_object_unref (config_obj); /* As we copy data all the time */
- }
- else {
- return luaL_error (L, "invalid arguments");
- }
-
- return 0;
- }
|