/* * Copyright 2024 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. */ #include "lua_common.h" #include "libmime/message.h" #include "libutil/expression.h" #include "src/libserver/composites/composites.h" #include "libserver/cfg_file_private.h" #include "libmime/lang_detection.h" #include "lua/lua_map.h" #include "lua/lua_thread_pool.h" #include "utlist.h" #include /*** * This module is used to configure rspamd and is normally available as global * variable named `rspamd_config`. Unlike other modules, it is not necessary to * require it before usage. * @module rspamd_config * @example -- Register some callback symbol local function foo(task) -- do something end rspamd_config:register_symbol('SYMBOL', 1.0, foo) -- Get configuration local tab = rspamd_config:get_all_opt('module') -- get table for module's options local opts = rspamd_config:get_key('options') -- get content of the specified key in rspamd configuration */ /* Config file methods */ /*** * @method rspamd_config:get_module_opt(mname, optname) * Returns value of specified option `optname` for a module `mname`, * @param {string} mname name of module * @param {string} optname option to get * @return {string or table} value of the option or `nil` if option is not found */ LUA_FUNCTION_DEF(config, get_module_opt); /*** * @method rspamd_config:get_all_opt(mname) * Returns value of all options for a module `mname`, flattening values into a single table consisting * of all sections with such a name. * @param {string} mname name of module * @return {table} table of all options for `mname` or `nil` if a module's configuration is not found */ LUA_FUNCTION_DEF(config, get_all_opt); /*** * @method rspamd_config:get_ucl() * Returns full configuration as a native Lua object (ucl to lua conversion). * This method uses caching if possible. * @return {table} table of all options in the configuration */ LUA_FUNCTION_DEF(config, get_ucl); /*** * @method rspamd_config:get_mempool() * Returns static configuration memory pool. * @return {mempool} [memory pool](mempool.md) object */ LUA_FUNCTION_DEF(config, get_mempool); /*** * @method rspamd_config:get_resolver() * Returns DNS resolver. * @return {dns_resolver} opaque DNS resolver pointer if any */ LUA_FUNCTION_DEF(config, get_resolver); /*** * @method rspamd_config:add_radix_map(mapline[, description]) * Creates new dynamic map of IP/mask addresses. * @param {string} mapline URL for a map * @param {string} description optional map description * @return {map} radix tree object * @example local ip_map = rspamd_config:add_radix_map ('file:///path/to/file', 'my radix map') ... local function foo(task) local ip = task:get_from_ip() if ip_map:get_key(ip) then return true end return false end */ /*** * @method rspamd_config:radix_from_config(mname, optname) * Creates new embedded map of IP/mask addresses from config. * @param {string} mname name of module * @param {string} optname option to get * @return {map} radix tree object * @example local ip_map = rspamd_config:radix_from_config ('mymodule', 'ips') ... local function foo(task) local ip = task:get_from_ip() if ip_map:get_key(ip) then return true end return false end */ /*** * @method rspamd_config:radix_from_ucl(obj) * Creates new embedded map of IP/mask addresses from object. * @param {ucl} obj object * @return {map} radix tree object */ /*** * @method rspamd_config:add_hash_map(mapline[, description]) * Creates new dynamic map string objects. * @param {string} mapline URL for a map * @param {string} description optional map description * @return {map} hash set object * @example local hash_map = rspamd_config:add_hash_map ('file:///path/to/file', 'my hash map') ... local function foo(task) local from = task:get_from() if hash_map:get_key(from['user']) then return true end return false end */ /*** * @method rspamd_config:add_kv_map(mapline[, description]) * Creates new dynamic map of key/values associations. * @param {string} mapline URL for a map * @param {string} description optional map description * @return {map} hash table object * @example local kv_map = rspamd_config:add_kv_map ('file:///path/to/file', 'my kv map') ... local function foo(task) local from = task:get_from() if from then local value = kv_map:get_key(from['user']) if value then return true,value end end return false end */ /*** * @method rspamd_config:add_map({args}) * Creates new dynamic map according to the attributes passed. * * - `type`: type of map to be created, can be one of the following set: * + `set`: set of strings * + `radix`: map of IP addresses to strings * + `map`: map of strings to strings * + `regexp`: map of regexps to strings * + `callback`: map processed by lua callback * - `url`: url to load map from * - `description`: map's description * - `callback`: lua callback for the map * * @return {map} `true` if map has been added * @example local str = '' local function process_map(in) str = in end rspamd_config:add_map('http://example.com/map', "settings map", process_map) */ /*** * @method rspamd_config:get_maps() * Get all maps defined as an array of rspamd{map} objects * * @return {table|rspamd{map}} */ /*** * @method rspamd_config:get_classifier(name) * Returns classifier config. * @param {string} name name of classifier (e.g. `bayes`) * @return {classifier} classifier object or `nil` */ LUA_FUNCTION_DEF(config, get_classifier); /*** * @method rspamd_config:register_symbol(table) * Register symbol of a specified type in rspamd. This function accepts table of arguments: * * - `name`: name of symbol (can be missing for callback symbols) * - `callback`: function to be called for symbol's check (can be absent for virtual symbols) * - `weight`: weight of symbol (should normally be 1 or missing) * - `priority`: priority of symbol (normally 0 or missing) * - `type`: type of symbol: * + `normal`: executed after prefilters, according to dependency graph or in undefined order * + `callback`: a check that merely inserts virtual symbols * + `connfilter`: executed early; before message body is available * + `idempotent`: cannot change result in any way; executed last * + `postfilter`: executed after most other checks * + `prefilter`: executed before most other checks * + `virtual`: a symbol inserted by its parent check * - `flags`: various flags split by commas or spaces: * + `nice` if symbol can produce negative score; * + `empty` if symbol can be called for empty messages * + `skip` if symbol should be skipped now * + `nostat` if symbol should be excluded from stat tokens * + `trivial` symbol is trivial (e.g. no network requests) * + `explicit_disable` requires explicit disabling (e.g. via settings) * + `ignore_passthrough` executed even if passthrough result has been set * - `parent`: id of parent symbol (useful for virtual symbols) * * @return {number} id of symbol registered */ LUA_FUNCTION_DEF(config, register_symbol); /*** * @method rspamd_config:register_symbols(callback, [weight], callback_name, [, symbol, ...]) * Register callback function to be called for a set of symbols with initial weight. * @param {function} callback callback function to be called for a specified symbol * @param {number} weight initial weight of symbol (can be less than zero to specify non-spam symbols) * @param {string} callback_name symbolic name of callback * @param {list of strings} symbol list of symbols registered by this function */ LUA_FUNCTION_DEF(config, register_symbols); /*** * @method rspamd_config:register_virtual_symbol(name, weight,) * Register virtual symbol that is not associated with any callback. * * **This method is deprecated and should not be used in newly written code ** * @param {string} virtual name symbol's name * @param {number} weight initial weight of symbol (can be less than zero to specify non-spam symbols) */ LUA_FUNCTION_DEF(config, register_virtual_symbol); /*** * @method rspamd_config:register_callback_symbol(name, weight, callback) * Register callback function to be called for a specified symbol with initial weight. Symbol itself is * not registered in the metric and is not intended to be visible by a user. * * **This method is deprecated and should not be used in newly written code ** * @param {string} name symbol's name (just for unique id purposes) * @param {number} weight initial weight of symbol (can be less than zero to specify non-spam symbols) * @param {function} callback callback function to be called for a specified symbol */ LUA_FUNCTION_DEF(config, register_callback_symbol); LUA_FUNCTION_DEF(config, register_callback_symbol_priority); /*** * @method rspamd_config:register_dependency(id|name, depname) * Create a dependency on symbol identified by name for symbol identified by ID or name. * This affects order of checks only (a symbol is still checked if its dependencies are disabled). * @param {number|string} id id or name of source (numeric id is returned by all register_*_symbol) * @param {string} depname dependency name * @example local function cb(task) ... end local id = rspamd_config:register_symbol('SYM', 1.0, cb) rspamd_config:register_dependency(id, 'OTHER_SYM') -- Alternative form -- Symbol MY_RULE needs result from SPF_CHECK rspamd_config:register_dependency('MY_RULE', 'SPF_CHECK') */ LUA_FUNCTION_DEF(config, register_dependency); /*** * @method rspamd_config:get_symbol_flags(name) * Returns symbol flags * @param {string} name symbols's name * @return {table|string} list of flags for symbol or nil */ LUA_FUNCTION_DEF(config, get_symbol_flags); /*** * @method rspamd_config:add_symbol_flags(name, flags) * Adds flags to a symbol * @param {string} name symbols's name * @param {table|string} flags flags to add * @return {table|string} new set of flags */ LUA_FUNCTION_DEF(config, add_symbol_flags); /** * @method rspamd_config:register_re_selector(name, selector_str, [delimiter, [flatten]]) * Registers selector with the specific name to use in regular expressions in form * name=/re/$ or name=/re/{selector} * @param {string} name name of the selector * @param {string} selector_str selector definition * @param {string} delimiter delimiter to use when joining strings if flatten is false * @param {bool} flatten if true then selector will return a table of captures instead of a single string * @return true if selector has been registered */ LUA_FUNCTION_DEF(config, register_re_selector); /** * @method rspamd_config:set_symbol({table}) * Sets the value of a specified symbol in a metric. This function accepts table with the following elements: * * - `name`: name of symbol (string) * - `score`: score for symbol (number) * - `metric`: name of metric (string, optional) * - `description`: description of symbol (string, optional) * - `group`: name of group for symbol (string, optional) * - `one_shot`: turn off multiple hits for a symbol (boolean, optional) * - `one_param`: turn off multiple options for a symbol (boolean, optional) * - `flags`: comma separated string of flags: * + `ignore`: do not strictly check validity of symbol and corresponding rule * + `one_shot`: turn off multiple hits for a symbol * + `one_param`: allow only one parameter for a symbol * - `priority`: priority of symbol's definition */ LUA_FUNCTION_DEF(config, set_metric_symbol); /** * @method rspamd_config:set_action({table}) * Sets the score of a specified action in a metric. This function accepts table with the following elements: * * - `action`: name of action (string) * - `score`: score for action (number) * - `metric`: name of metric (string, optional) * - `priority`: priority of action's definition */ LUA_FUNCTION_DEF(config, set_metric_action); /** * @method rspamd_config:get_action(name) * Gets data for a specific action in config. This function returns number representing action's score * * @param {string} name name of action * @return {number} action's score or nil in case of undefined score or action */ LUA_FUNCTION_DEF(config, get_metric_action); /** * @method rspamd_config:get_all_actions() * Gets data for all action in config * @return {table|str->num} action's score or nil in case of undefined score or action */ LUA_FUNCTION_DEF(config, get_all_actions); /** * @method rspamd_config:add_composite(name, expression) * @param {string} name name of composite symbol * @param {string} expression symbolic expression of the composite rule * @return {bool} true if a composite has been added successfully */ LUA_FUNCTION_DEF(config, add_composite); /*** * @method rspamd_config:register_pre_filter(callback[, order]) * Register function to be called prior to symbols processing. * @param {function} callback callback function * @param {number} order filters are called from lower orders to higher orders, order is equal to 0 by default * @example local function check_function(task) -- It is possible to manipulate the task object here: set settings, set pre-action and so on ... end rspamd_config:register_pre_filter(check_function) */ LUA_FUNCTION_DEF(config, register_pre_filter); /*** * @method rspamd_config:register_post_filter(callback[, order]) * Register function to be called after symbols are processed. * * @param {function} callback callback function * @param {number} order filters are called from lower orders to higher orders, order is equal to 0 by default */ LUA_FUNCTION_DEF(config, register_post_filter); /* XXX: obsoleted */ LUA_FUNCTION_DEF(config, register_module_option); /* XXX: not needed now */ LUA_FUNCTION_DEF(config, get_api_version); /*** * @method rspamd_config:get_key(name) * Returns configuration section with the specified `name`. * @param {string} name name of config section * @return {variant} specific value of section * @example local set_section = rspamd_config:get_key("settings") if type(set_section) == "string" then -- Just a map of ucl if rspamd_config:add_map(set_section, "settings map", process_settings_map) then rspamd_config:register_pre_filter(check_settings) end elseif type(set_section) == "table" then if process_settings_table(set_section) then rspamd_config:register_pre_filter(check_settings) end end */ LUA_FUNCTION_DEF(config, get_key); /*** * @method rspamd_config:add_condition(symbol, condition) * Adds condition callback for specified symbol * @param {string} symbol symbol's name * @param {function} condition condition callback * @return {boolean} true if condition has been added * @example local condition_map = rspamd_config:add_map{ type = "hash", urls = ['file:///path/to/file'], description = 'SMTP from map that allows FUZZY_DENIED skip for the listed addresses' } rspamd_config:add_condition('FUZZY_DENIED', function(task) local E = {} -- Check for the smtp from address adding fail safe checks if condition_map:find_key(((task:get_from('smtp') or E)[1] or E).addr) then return false end -- Allow execution otherwise return true end) */ LUA_FUNCTION_DEF(config, add_condition); /*** * @method rspamd_config:enable_symbol(symbol) * Enables execution for the specified symbol * @param {string} symbol symbol's name */ LUA_FUNCTION_DEF(config, enable_symbol); /*** * @method rspamd_config:disable_symbol(symbol, [disable_parent=true]) * Disables execution for the specified symbol * @param {string} symbol symbol's name * @param {boolean} disable_parent if true then disable parent execution in case of a virtual symbol */ LUA_FUNCTION_DEF(config, disable_symbol); /*** * @method rspamd_config:get_symbol_parent(symbol) * Returns a parent symbol for specific symbol (or symbol itself if top level) * @param {string} symbol symbol's name */ LUA_FUNCTION_DEF(config, get_symbol_parent); /*** * @method rspamd_config:get_group_symbols(group) * Returns list of symbols for a specific group * @param {string} group group's name * @available 2.0+ * @return {list|string} list of all symbols in a specific group */ LUA_FUNCTION_DEF(config, get_group_symbols); /*** * @method rspamd_config:get_groups([need_private]) * Returns list of all groups defined * @param {boolean} need_private optional flag to include private groups * @available 2.3+ * @return {list|table} list of all groups */ LUA_FUNCTION_DEF(config, get_groups); /*** * @method rspamd_config:register_settings_id(name, symbols_enabled, symbols_disabled) * Register new static settings id in config * @param {string} name id name (not numeric!) * @param {map|string->string} symbols_enabled map from symbol's name to boolean (currently) * @param {map|string->string} symbols_disabled map from symbol's name to boolean (currently) * @available 2.0+ */ LUA_FUNCTION_DEF(config, register_settings_id); /*** * @method rspamd_config:__newindex(name, callback) * This metamethod is called if new indices are added to the `rspamd_config` object. * Technically, it is the equivalent of @see rspamd_config:register_symbol where `weight` is 1.0. * There is also table form invocation that allows to control more things: * * - `callback`: has the same meaning and acts as function of task * - `score`: default score for a symbol * - `group`: default group for a symbol * - `description`: default symbol's description * - `priority`: additional priority value * - `one_shot`: default value for one shot attribute * - `condition`: function of task that can enable or disable this specific rule's execution * @param {string} name index name * @param {function/table} callback callback to be called * @return {number} id of the new symbol added * @example rspamd_config.R_EMPTY_IMAGE = function (task) parts = task:get_text_parts() if parts then for _,part in ipairs(parts) do if part:is_empty() then images = task:get_images() if images then -- Symbol `R_EMPTY_IMAGE` is inserted return true end return false end end end return false end rspamd_config.SYMBOL = { callback = function(task) ... end, score = 5.1, description = 'sample symbol', group = 'sample symbols', condition = function(task) if task:get_from()[1]['addr'] == 'user@example.com' then return false end return true end } */ LUA_FUNCTION_DEF(config, newindex); /*** * @method rspamd_config:register_regexp(params) * Registers new re for further cached usage * Params is the table with the following fields (mandatory fields are marked with `*`): * - `re`* : regular expression object * - `type`*: type of regular expression: * + `mime`: mime regexp * + `rawmime`: raw mime regexp * + `header`: header regexp * + `rawheader`: raw header expression * + `body`: raw body regexp * + `url`: url regexp * - `header`: for header and rawheader regexp means the name of header * - `pcre_only`: flag regexp as pcre only regexp */ LUA_FUNCTION_DEF(config, register_regexp); /*** * @method rspamd_config:replace_regexp(params) * Replaces regexp with a new one * Params is the table with the following fields (mandatory fields are marked with `*`): * - `old_re`* : old regular expression object (must be in the cache) * - `new_re`* : old regular expression object (must not be in the cache) */ LUA_FUNCTION_DEF(config, replace_regexp); /*** * @method rspamd_config:register_worker_script(worker_type, script) * Registers the following script for workers of a specified type. The exact type * of script function depends on worker type * @param {string} worker_type worker type (e.g. "normal") * @param {function} script script for a worker * @return {boolean} `true` if a script has been registered */ LUA_FUNCTION_DEF(config, register_worker_script); /*** * @method rspamd_config:add_on_load(function(cfg, ev_base, worker) ... end) * Registers the following script to be executed when configuration is completely loaded * and the worker is already started (forked) * @param {function} script function to be executed * @example rspamd_config:add_on_load(function(cfg, ev_base, worker) rspamd_config:add_periodic(ev_base, 1.0, function(cfg, ev_base) local logger = require "rspamd_logger" logger.infox(cfg, "periodic function in worker %s", worker:get_name()) return true end) end) */ LUA_FUNCTION_DEF(config, add_on_load); /*** * @method rspamd_config:add_periodic(event_base, timeout, function(cfg, ev_base) ... end, [jitter = false]) * Registers function to be periodically executed by Rspamd * @param {ev_base} event_base event base that is needed for async events * @param {number} timeout time in seconds (could be fractional) * @param {function} script function to be executed * @param {boolean} jitter `true` if timeout jittering is needed * @example rspamd_config:add_on_load(function(cfg, ev_base) rspamd_config:add_periodic(ev_base, 1.0, function(cfg, ev_base) local logger = require "rspamd_logger" logger.infox(cfg, "periodic function") return true -- if return numeric, a new interval is set. if return false, then the periodic event is removed end) end) */ LUA_FUNCTION_DEF(config, add_periodic); /*** * @method rspamd_config:add_post_init(function(cfg) ... end) * Registers the following script to be executed when configuration is completely loaded * @available 2.0+ * @param {function} script function to be executed */ LUA_FUNCTION_DEF(config, add_post_init); /*** * @method rspamd_config:add_config_unload(function(cfg) ... end) * Registers the following script to be executed when configuration is unloaded * @available 2.0+ * @param {function} script function to be executed */ LUA_FUNCTION_DEF(config, add_config_unload); /*** * @method rspamd_config:get_symbols_count() * Returns number of symbols registered in rspamd configuration * @return {number} number of symbols registered in the configuration */ LUA_FUNCTION_DEF(config, get_symbols_count); /*** * @method rspamd_config:get_symbols_cksum() * Returns checksum for all symbols in the cache * @return {int64} boxed value of the 64 bit checksum */ LUA_FUNCTION_DEF(config, get_symbols_cksum); /*** * @method rspamd_config:get_symbols_counters() * Returns table of all counters in the cache (weights, frequencies etc) * @return {table|tables} all symbols indexed by name */ LUA_FUNCTION_DEF(config, get_symbols_counters); /*** * @method rspamd_config:get_symbols() * Returns table of all scores defined in config. From version 2.0 returns table: * - name * - score * - flags (e.g. `ignore` or `oneparam`) * - nshots (== maxhits) * - group - main group * - groups - array of all groups * @available 2.0+ * @return {table|tables} all symbols indexed by name */ LUA_FUNCTION_DEF(config, get_symbols); /*** * @method rspamd_config:get_symbol(sym_name) * Returns table for a specific symbol getting data from the static config: * - name * - score * - flags (e.g. `ignore` or `oneparam`) * - nshots (== maxhits) * - group - main group * - groups - array of all groups * @available 3.3+ * @return {table} symbol data (or nil) */ LUA_FUNCTION_DEF(config, get_symbol); /*** * @method rspamd_config:get_symbol_callback(name) * Returns callback function for the specified symbol if it is a lua registered callback * @return {function} callback function or nil */ LUA_FUNCTION_DEF(config, get_symbol_callback); /*** * @method rspamd_config:get_symbol_stat(name) * Returns table with statistics for a specific symbol: * - `frequency`: frequency for symbol's hits * - `stddev`: standard deviation of `frequency` * - `time`: average time in seconds (floating point) * - `count`: total number of hits * @return {table} symbol stats */ LUA_FUNCTION_DEF(config, get_symbol_stat); /*** * @method rspamd_config:set_symbol_callback(name, callback) * Sets callback for the specified symbol * @return {boolean} true if function has been replaced */ LUA_FUNCTION_DEF(config, set_symbol_callback); /*** * @method rspamd_config:register_finish_script(callback) * Adds new callback that is called on worker process termination when all * tasks pending are processed * * @param callback {function} a function with one argument (rspamd_task) */ LUA_FUNCTION_DEF(config, register_finish_script); /*** * @method rspamd_config:register_monitored(url, type, [{params}]) * Registers monitored resource to watch its availability. Supported types: * * - `dns`: DNS monitored object * * Params are optional table specific for each type. For DNS it supports the * following options: * * - `prefix`: prefix to add before making request * - `type`: type of request (e.g. 'a' or 'txt') * - `ipnet`: array of ip/networks to expect on reply * - `rcode`: expected return code (e.g. `nxdomain`) * * Returned object has the following methods: * * - `alive`: returns `true` if monitored resource is alive * - `offline`: returns number of seconds of the current offline period (or 0 if alive) * - `total_offline`: returns number of seconds of the overall offline * - `latency`: returns the current average latency in seconds (or 0 if offline) * * @param {string} url resource to monitor * @param {string} type type of monitoring * @param {table} opts optional parameters * @return {rspamd_monitored} rspamd monitored object */ LUA_FUNCTION_DEF(config, register_monitored); /*** * @method rspamd_config:add_doc(path, option, doc_string, [{params}]) * Adds new documentation string for an option `option` at path `path` * Options defines optional params, such as: * * - `default`: default option value * - `type`: type of an option (`string`, `number`, `object`, `array` etc) * - `required`: if an option is required * * @param {string} path documentation path (e.g. module name) * @param {string} option name of the option * @param {string} doc_string documentation string * @param {table} params optional parameters */ LUA_FUNCTION_DEF(config, add_doc); /*** * @method rspamd_config:add_example(path, option, doc_string, example) * Adds new documentation * * @param {string} path documentation path (e.g. module name or nil for top) * @param {string} option name of the option * @param {string} doc_string documentation string * @param {string} example example in ucl format, comments are also parsed */ LUA_FUNCTION_DEF(config, add_example); /*** * @method rspamd_config:set_peak_cb(function) * Sets a function that will be called when frequency of some symbol goes out of * stddev * 2 over the last period of refreshment. * * @example rspamd_config:set_peak_cb(function(ev_base, sym, mean, stddev, value, error) -- ev_base: event base for async events (e.g. redis) -- sym: symbol's name -- mean: mean frequency value -- stddev: standard deviation of frequency -- value: current frequency value -- error: squared error local logger = require "rspamd_logger" logger.infox(rspamd_config, "symbol %s has changed frequency significantly: %s(%s) over %s(%s)", sym, value, error, mean, stddev) end) */ LUA_FUNCTION_DEF(config, set_peak_cb); /*** * @method rspamd_config:get_cpu_flags() * Returns architecture dependent flags supported by the CPU * Currently, only x86 flags are supported: * - 'ssse3' * - 'sse42' * - 'avx' * - 'avx2' * @return {table} flag -> true table */ LUA_FUNCTION_DEF(config, get_cpu_flags); /*** * @method rspamd_config:has_torch() * Returns true if Rspamd is compiled with torch support and the runtime CPU * supports sse4.2 required for torch. * @return {boolean} true if torch is compiled and supported */ LUA_FUNCTION_DEF(config, has_torch); /*** * @method rspamd_config:experimental_enabled() * Returns true if experimental plugins are enabled * @return {boolean} true if experimental plugins are enabled */ LUA_FUNCTION_DEF(config, experimental_enabled); /*** * @method rspamd_config:load_ucl(filename[, include_trace]) * Loads config from the UCL file (but does not perform parsing using rcl) * @param {string} filename file to load * @return true or false + error message */ LUA_FUNCTION_DEF(config, load_ucl); /*** * @method rspamd_config:parse_rcl([skip_sections]) * Parses RCL using loaded ucl file * @param {table|string} sections to skip * @return true or false + error message */ LUA_FUNCTION_DEF(config, parse_rcl); /*** * @method rspamd_config:init_modules() * Initialize lua and internal modules * @return true or false */ LUA_FUNCTION_DEF(config, init_modules); /*** * @method rspamd_config:init_subsystem(str) * Initialize config subsystem from a comma separated list: * - `modules` - init modules * - `langdet` - language detector * - `dns` - DNS resolver * - TODO: add more */ LUA_FUNCTION_DEF(config, init_subsystem); /*** * @method rspamd_config:get_tld_path() * Returns path to TLD file * @return {string} path to tld file */ LUA_FUNCTION_DEF(config, get_tld_path); /*** * @method rspamd_config:get_dns_max_requests() * Returns limit of DNS requests per task * @return {number} number of dns requests allowed */ LUA_FUNCTION_DEF(config, get_dns_max_requests); /*** * @method rspamd_config:get_dns_timeout() * Returns timeout for a DNS request * @return {number} DNS timeout in second or 0 if not defined */ LUA_FUNCTION_DEF(config, get_dns_timeout); static const struct luaL_reg configlib_m[] = { LUA_INTERFACE_DEF(config, get_module_opt), LUA_INTERFACE_DEF(config, get_mempool), LUA_INTERFACE_DEF(config, get_resolver), LUA_INTERFACE_DEF(config, get_all_opt), LUA_INTERFACE_DEF(config, get_ucl), LUA_INTERFACE_DEF(config, add_radix_map), LUA_INTERFACE_DEF(config, radix_from_config), LUA_INTERFACE_DEF(config, radix_from_ucl), LUA_INTERFACE_DEF(config, add_hash_map), LUA_INTERFACE_DEF(config, add_kv_map), LUA_INTERFACE_DEF(config, add_map), LUA_INTERFACE_DEF(config, get_maps), LUA_INTERFACE_DEF(config, get_classifier), LUA_INTERFACE_DEF(config, register_symbol), LUA_INTERFACE_DEF(config, register_symbols), LUA_INTERFACE_DEF(config, register_virtual_symbol), LUA_INTERFACE_DEF(config, register_callback_symbol), LUA_INTERFACE_DEF(config, register_callback_symbol_priority), LUA_INTERFACE_DEF(config, register_dependency), LUA_INTERFACE_DEF(config, register_settings_id), LUA_INTERFACE_DEF(config, get_symbol_flags), LUA_INTERFACE_DEF(config, set_metric_symbol), {"set_symbol", lua_config_set_metric_symbol}, LUA_INTERFACE_DEF(config, set_metric_action), {"set_action", lua_config_set_metric_action}, {"get_metric_symbol", lua_config_get_symbol}, LUA_INTERFACE_DEF(config, get_metric_action), {"get_action", lua_config_get_metric_action}, LUA_INTERFACE_DEF(config, get_all_actions), LUA_INTERFACE_DEF(config, add_composite), LUA_INTERFACE_DEF(config, register_module_option), LUA_INTERFACE_DEF(config, register_pre_filter), LUA_INTERFACE_DEF(config, register_post_filter), LUA_INTERFACE_DEF(config, get_api_version), LUA_INTERFACE_DEF(config, get_key), LUA_INTERFACE_DEF(config, add_condition), LUA_INTERFACE_DEF(config, enable_symbol), LUA_INTERFACE_DEF(config, disable_symbol), LUA_INTERFACE_DEF(config, register_regexp), LUA_INTERFACE_DEF(config, replace_regexp), LUA_INTERFACE_DEF(config, register_worker_script), LUA_INTERFACE_DEF(config, register_re_selector), LUA_INTERFACE_DEF(config, add_on_load), LUA_INTERFACE_DEF(config, add_periodic), LUA_INTERFACE_DEF(config, add_post_init), LUA_INTERFACE_DEF(config, add_config_unload), LUA_INTERFACE_DEF(config, get_symbols_count), LUA_INTERFACE_DEF(config, get_symbols_cksum), LUA_INTERFACE_DEF(config, get_symbols_counters), {"get_symbols_scores", lua_config_get_symbols}, LUA_INTERFACE_DEF(config, get_symbols), LUA_INTERFACE_DEF(config, get_symbol), LUA_INTERFACE_DEF(config, get_groups), LUA_INTERFACE_DEF(config, get_symbol_callback), LUA_INTERFACE_DEF(config, set_symbol_callback), LUA_INTERFACE_DEF(config, get_symbol_stat), LUA_INTERFACE_DEF(config, get_symbol_parent), LUA_INTERFACE_DEF(config, get_group_symbols), LUA_INTERFACE_DEF(config, register_finish_script), LUA_INTERFACE_DEF(config, register_monitored), LUA_INTERFACE_DEF(config, add_doc), LUA_INTERFACE_DEF(config, add_example), LUA_INTERFACE_DEF(config, set_peak_cb), LUA_INTERFACE_DEF(config, get_cpu_flags), LUA_INTERFACE_DEF(config, has_torch), LUA_INTERFACE_DEF(config, experimental_enabled), LUA_INTERFACE_DEF(config, load_ucl), LUA_INTERFACE_DEF(config, parse_rcl), LUA_INTERFACE_DEF(config, init_modules), LUA_INTERFACE_DEF(config, init_subsystem), LUA_INTERFACE_DEF(config, get_tld_path), LUA_INTERFACE_DEF(config, get_dns_max_requests), LUA_INTERFACE_DEF(config, get_dns_timeout), {"__tostring", rspamd_lua_class_tostring}, {"__newindex", lua_config_newindex}, {NULL, NULL}}; LUA_FUNCTION_DEF(monitored, alive); LUA_FUNCTION_DEF(monitored, latency); LUA_FUNCTION_DEF(monitored, offline); LUA_FUNCTION_DEF(monitored, total_offline); static const struct luaL_reg monitoredlib_m[] = { LUA_INTERFACE_DEF(monitored, alive), LUA_INTERFACE_DEF(monitored, latency), LUA_INTERFACE_DEF(monitored, offline), LUA_INTERFACE_DEF(monitored, total_offline), {"__tostring", rspamd_lua_class_tostring}, {NULL, NULL}}; static const uint64_t rspamd_lua_callback_magic = 0x32c118af1e3263c7ULL; struct rspamd_config * lua_check_config(lua_State *L, int pos) { void *ud = rspamd_lua_check_udata(L, pos, rspamd_config_classname); luaL_argcheck(L, ud != NULL, pos, "'config' expected"); return ud ? *((struct rspamd_config **) ud) : NULL; } static struct rspamd_monitored * lua_check_monitored(lua_State *L, int pos) { void *ud = rspamd_lua_check_udata(L, pos, rspamd_monitored_classname); luaL_argcheck(L, ud != NULL, pos, "'monitored' expected"); return ud ? *((struct rspamd_monitored **) ud) : NULL; } /*** Config functions ***/ static int lua_config_get_api_version(lua_State *L) { msg_warn("get_api_version is deprecated, do not use it"); lua_pushnumber(L, 100); return 1; } static int lua_config_get_module_opt(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *mname, *optname; const ucl_object_t *obj; if (cfg) { mname = luaL_checkstring(L, 2); optname = luaL_checkstring(L, 3); if (mname && optname) { obj = rspamd_config_get_module_opt(cfg, mname, optname); if (obj) { return ucl_object_push_lua(L, obj, TRUE); } } } lua_pushnil(L); return 1; } static int lua_config_get_mempool(lua_State *L) { LUA_TRACE_POINT; rspamd_mempool_t **ppool; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL) { ppool = lua_newuserdata(L, sizeof(rspamd_mempool_t *)); rspamd_lua_setclass(L, rspamd_mempool_classname, -1); *ppool = cfg->cfg_pool; } else { lua_pushnil(L); } return 1; } static int lua_config_get_resolver(lua_State *L) { LUA_TRACE_POINT; struct rspamd_dns_resolver **pres; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL && cfg->dns_resolver) { pres = lua_newuserdata(L, sizeof(*pres)); rspamd_lua_setclass(L, rspamd_resolver_classname, -1); *pres = cfg->dns_resolver; } else { lua_pushnil(L); } return 1; } static int lua_config_get_all_opt(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *mname; const ucl_object_t *obj, *cur, *cur_elt; ucl_object_iter_t it = NULL; int i; if (cfg) { mname = luaL_checkstring(L, 2); if (mname) { obj = ucl_obj_get_key(cfg->cfg_ucl_obj, mname); /* Flatten object */ if (obj != NULL && (ucl_object_type(obj) == UCL_OBJECT || ucl_object_type(obj) == UCL_ARRAY)) { lua_newtable(L); it = ucl_object_iterate_new(obj); LL_FOREACH(obj, cur) { it = ucl_object_iterate_reset(it, cur); while ((cur_elt = ucl_object_iterate_safe(it, true))) { lua_pushstring(L, ucl_object_key(cur_elt)); ucl_object_push_lua(L, cur_elt, true); lua_settable(L, -3); } } ucl_object_iterate_free(it); return 1; } else if (obj != NULL) { lua_newtable(L); i = 1; LL_FOREACH(obj, cur) { lua_pushinteger(L, i++); ucl_object_push_lua(L, cur, true); lua_settable(L, -3); } return 1; } } } lua_pushnil(L); return 1; } struct rspamd_lua_cached_config { lua_State *L; int ref; }; static void lua_config_ucl_dtor(gpointer p) { struct rspamd_lua_cached_config *cached = p; luaL_unref(cached->L, LUA_REGISTRYINDEX, cached->ref); } static int lua_config_get_ucl(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_lua_cached_config *cached; if (cfg) { cached = rspamd_mempool_get_variable(cfg->cfg_pool, "ucl_cached"); if (cached) { lua_rawgeti(L, LUA_REGISTRYINDEX, cached->ref); } else { if (cfg->cfg_ucl_obj) { ucl_object_push_lua(L, cfg->cfg_ucl_obj, true); lua_pushvalue(L, -1); cached = rspamd_mempool_alloc(cfg->cfg_pool, sizeof(*cached)); cached->L = L; cached->ref = luaL_ref(L, LUA_REGISTRYINDEX); rspamd_mempool_set_variable(cfg->cfg_pool, "ucl_cached", cached, lua_config_ucl_dtor); } else { lua_pushnil(L); } } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_classifier(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_classifier_config *clc = NULL, **pclc = NULL; const char *name; GList *cur; if (cfg) { name = luaL_checkstring(L, 2); cur = g_list_first(cfg->classifiers); while (cur) { clc = cur->data; if (g_ascii_strcasecmp(clc->name, name) == 0) { pclc = &clc; break; } cur = g_list_next(cur); } if (pclc) { pclc = lua_newuserdata(L, sizeof(struct rspamd_classifier_config *)); rspamd_lua_setclass(L, rspamd_classifier_classname, -1); *pclc = clc; return 1; } } lua_pushnil(L); return 1; } struct lua_callback_data { uint64_t magic; lua_State *L; char *symbol; union { char *name; int ref; } callback; gboolean cb_is_ref; /* Dynamic data */ int stack_level; int order; struct rspamd_symcache_dynamic_item *item; }; /* * Unref symbol if it is local reference */ static void lua_destroy_cfg_symbol(gpointer ud) { struct lua_callback_data *cd = ud; /* Unref callback */ if (cd->cb_is_ref) { luaL_unref(cd->L, LUA_REGISTRYINDEX, cd->callback.ref); } } static int lua_config_register_module_option(lua_State *L) { return 0; } static int rspamd_compare_order_func(gconstpointer a, gconstpointer b) { const struct lua_callback_data *cb1 = a, *cb2 = b; /* order of call goes from lower to higher */ return cb2->order - cb1->order; } static void lua_metric_symbol_callback(struct rspamd_task *task, struct rspamd_symcache_dynamic_item *item, gpointer ud) { struct lua_callback_data *cd = ud; struct rspamd_task **ptask; int level = lua_gettop(cd->L), nresults, err_idx, ret; lua_State *L = cd->L; struct rspamd_symbol_result *s; cd->item = item; rspamd_symcache_item_async_inc(task, item, "lua symbol"); lua_pushcfunction(L, &rspamd_lua_traceback); err_idx = lua_gettop(L); level++; if (cd->cb_is_ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, cd->callback.ref); } else { lua_getglobal(L, cd->callback.name); } ptask = lua_newuserdata(L, sizeof(struct rspamd_task *)); rspamd_lua_setclass(L, rspamd_task_classname, -1); *ptask = task; if ((ret = lua_pcall(L, 1, LUA_MULTRET, err_idx)) != 0) { msg_err_task("call to (%s) failed (%d): %s", cd->symbol, ret, lua_tostring(L, -1)); lua_settop(L, err_idx); /* Not -1 here, as err_func is popped below */ } else { nresults = lua_gettop(L) - level; if (nresults >= 1) { /* Function returned boolean, so maybe we need to insert result? */ int res = 0; int i; double flag = 1.0; int type; type = lua_type(cd->L, level + 1); if (type == LUA_TBOOLEAN) { res = lua_toboolean(L, level + 1); } else if (type == LUA_TNUMBER) { res = lua_tonumber(L, level + 1); } else if (type == LUA_TNIL) { /* Can happen sometimes... */ res = FALSE; } else { /* Something bogus has been returned, so we should log it */ msg_err_task("invalid return value for %s: %s", cd->symbol, lua_typename(L, type)); res = FALSE; } if (res) { int first_opt = 2; if (lua_type(L, level + 2) == LUA_TNUMBER) { flag = lua_tonumber(L, level + 2); /* Shift opt index */ first_opt = 3; } else { flag = res; } s = rspamd_task_insert_result(task, cd->symbol, flag, NULL); if (s) { unsigned int last_pos = lua_gettop(L); for (i = level + first_opt; i <= last_pos; i++) { if (lua_type(L, i) == LUA_TSTRING) { gsize optlen; const char *opt = lua_tolstring(L, i, &optlen); rspamd_task_add_result_option(task, s, opt, optlen); } else if (lua_type(L, i) == LUA_TUSERDATA) { struct rspamd_lua_text *t = lua_check_text(L, i); if (t) { rspamd_task_add_result_option(task, s, t->start, t->len); } } else if (lua_type(L, i) == LUA_TTABLE) { gsize objlen = rspamd_lua_table_size(L, i); for (unsigned int j = 1; j <= objlen; j++) { lua_rawgeti(L, i, j); if (lua_type(L, -1) == LUA_TSTRING) { gsize optlen; const char *opt = lua_tolstring(L, -1, &optlen); rspamd_task_add_result_option(task, s, opt, optlen); } else if (lua_type(L, -1) == LUA_TUSERDATA) { struct rspamd_lua_text *t = lua_check_text(L, -1); if (t) { rspamd_task_add_result_option(task, s, t->start, t->len); } } lua_pop(L, 1); } } } } } lua_pop(L, nresults); } } lua_pop(L, 1); /* Error function */ rspamd_symcache_item_async_dec_check(task, cd->item, "lua symbol"); g_assert(lua_gettop(L) == level - 1); } static void lua_metric_symbol_callback_return(struct thread_entry *thread_entry, int ret); static void lua_metric_symbol_callback_error(struct thread_entry *thread_entry, int ret, const char *msg); static void lua_metric_symbol_callback_coro(struct rspamd_task *task, struct rspamd_symcache_dynamic_item *item, gpointer ud) { struct lua_callback_data *cd = ud; struct rspamd_task **ptask; struct thread_entry *thread_entry; cd->item = item; rspamd_symcache_item_async_inc(task, item, "lua coro symbol"); thread_entry = lua_thread_pool_get_for_task(task); g_assert(thread_entry->cd == NULL); thread_entry->cd = cd; lua_State *thread = thread_entry->lua_state; cd->stack_level = lua_gettop(thread); if (cd->cb_is_ref) { lua_rawgeti(thread, LUA_REGISTRYINDEX, cd->callback.ref); } else { lua_getglobal(thread, cd->callback.name); } ptask = lua_newuserdata(thread, sizeof(struct rspamd_task *)); rspamd_lua_setclass(thread, rspamd_task_classname, -1); *ptask = task; thread_entry->finish_callback = lua_metric_symbol_callback_return; thread_entry->error_callback = lua_metric_symbol_callback_error; lua_thread_call(thread_entry, 1); } static void lua_metric_symbol_callback_error(struct thread_entry *thread_entry, int ret, const char *msg) { struct lua_callback_data *cd = thread_entry->cd; struct rspamd_task *task = thread_entry->task; msg_err_task("call to coroutine (%s) failed (%d): %s", cd->symbol, ret, msg); rspamd_symcache_item_async_dec_check(task, cd->item, "lua coro symbol"); } static void lua_metric_symbol_callback_return(struct thread_entry *thread_entry, int ret) { struct lua_callback_data *cd = thread_entry->cd; struct rspamd_task *task = thread_entry->task; int nresults; struct rspamd_symbol_result *s; (void) ret; lua_State *L = thread_entry->lua_state; nresults = lua_gettop(L) - cd->stack_level; if (nresults >= 1) { /* Function returned boolean, so maybe we need to insert result? */ int res = 0; int i; double flag = 1.0; int type; type = lua_type(L, cd->stack_level + 1); if (type == LUA_TBOOLEAN) { res = lua_toboolean(L, cd->stack_level + 1); } else if (type == LUA_TFUNCTION) { g_assert_not_reached(); } else { res = lua_tonumber(L, cd->stack_level + 1); } if (res) { int first_opt = 2; if (lua_type(L, cd->stack_level + 2) == LUA_TNUMBER) { flag = lua_tonumber(L, cd->stack_level + 2); /* Shift opt index */ first_opt = 3; } else { flag = res; } s = rspamd_task_insert_result(task, cd->symbol, flag, NULL); if (s) { unsigned int last_pos = lua_gettop(L); for (i = cd->stack_level + first_opt; i <= last_pos; i++) { if (lua_type(L, i) == LUA_TSTRING) { gsize optlen; const char *opt = lua_tolstring(L, i, &optlen); rspamd_task_add_result_option(task, s, opt, optlen); } else if (lua_type(L, i) == LUA_TUSERDATA) { struct rspamd_lua_text *t = lua_check_text(L, i); if (t) { rspamd_task_add_result_option(task, s, t->start, t->len); } } else if (lua_type(L, i) == LUA_TTABLE) { gsize objlen = rspamd_lua_table_size(L, i); for (unsigned int j = 1; j <= objlen; j++) { lua_rawgeti(L, i, j); if (lua_type(L, -1) == LUA_TSTRING) { gsize optlen; const char *opt = lua_tolstring(L, -1, &optlen); rspamd_task_add_result_option(task, s, opt, optlen); } else if (lua_type(L, -1) == LUA_TUSERDATA) { struct rspamd_lua_text *t = lua_check_text(L, -1); if (t) { rspamd_task_add_result_option(task, s, t->start, t->len); } } lua_pop(L, 1); } } } } } lua_pop(L, nresults); } g_assert(lua_gettop(L) == cd->stack_level); /* we properly cleaned up the stack */ cd->stack_level = 0; rspamd_symcache_item_async_dec_check(task, cd->item, "lua coro symbol"); } static GArray * rspamd_process_id_list(const char *entries) { char **sym_elts; GArray *ret; sym_elts = g_strsplit_set(entries, ",;", -1); unsigned int nids = g_strv_length(sym_elts); ret = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), nids); for (unsigned int i = 0; i < nids; i++) { uint32_t v = rspamd_config_name_to_id(sym_elts[i], strlen(sym_elts[i])); g_array_append_val(ret, v); } g_strfreev(sym_elts); return ret; } static int rspamd_register_symbol_fromlua(lua_State *L, struct rspamd_config *cfg, const char *name, int ref, double weight, int priority, enum rspamd_symbol_type type, int parent, GArray *allowed_ids, GArray *forbidden_ids, gboolean optional) { struct lua_callback_data *cd; int ret = -1; if (priority == 0 && weight < 0) { priority = 1; } if ((ret = rspamd_symcache_find_symbol(cfg->cache, name)) != -1) { if (optional) { msg_debug_config("duplicate symbol: %s, skip registering", name); return ret; } else { msg_err_config("duplicate symbol: %s, skip registering", name); return -1; } } if (allowed_ids && !(type & SYMBOL_TYPE_EXPLICIT_DISABLE)) { /* Mark symbol as explicit allow */ msg_info_config("mark symbol %s as explicit enable as its execution is" "allowed merely on specific settings ids", name); type |= SYMBOL_TYPE_EXPLICIT_ENABLE; } if (ref != -1) { cd = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(struct lua_callback_data)); cd->magic = rspamd_lua_callback_magic; cd->cb_is_ref = TRUE; cd->callback.ref = ref; cd->L = L; if (name) { cd->symbol = rspamd_mempool_strdup(cfg->cfg_pool, name); } if (type & SYMBOL_TYPE_USE_CORO) { ret = rspamd_symcache_add_symbol(cfg->cache, name, priority, lua_metric_symbol_callback_coro, cd, type, parent); } else { ret = rspamd_symcache_add_symbol(cfg->cache, name, priority, lua_metric_symbol_callback, cd, type, parent); } rspamd_mempool_add_destructor(cfg->cfg_pool, (rspamd_mempool_destruct_t) lua_destroy_cfg_symbol, cd); } else { /* No callback */ ret = rspamd_symcache_add_symbol(cfg->cache, name, priority, NULL, NULL, type, parent); } if (allowed_ids) { rspamd_symcache_set_allowed_settings_ids(cfg->cache, name, &g_array_index(allowed_ids, uint32_t, 0), allowed_ids->len); } if (forbidden_ids) { rspamd_symcache_set_forbidden_settings_ids(cfg->cache, name, &g_array_index(forbidden_ids, uint32_t, 0), forbidden_ids->len); } return ret; } static int lua_config_register_post_filter(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); int order = 0, cbref, ret; if (cfg) { if (lua_type(L, 3) == LUA_TNUMBER) { order = lua_tonumber(L, 3); } if (lua_type(L, 2) == LUA_TFUNCTION) { lua_pushvalue(L, 2); /* Get a reference */ cbref = luaL_ref(L, LUA_REGISTRYINDEX); } else { return luaL_error(L, "invalid type for callback: %s", lua_typename(L, lua_type(L, 2))); } msg_warn_config("register_post_filter function is deprecated, " "use register_symbol instead"); ret = rspamd_register_symbol_fromlua(L, cfg, NULL, cbref, 1.0, order, SYMBOL_TYPE_POSTFILTER | SYMBOL_TYPE_CALLBACK, -1, NULL, NULL, FALSE); lua_pushboolean(L, ret); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_register_pre_filter(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); int order = 0, cbref, ret; if (cfg) { if (lua_type(L, 3) == LUA_TNUMBER) { order = lua_tonumber(L, 3); } if (lua_type(L, 2) == LUA_TFUNCTION) { lua_pushvalue(L, 2); /* Get a reference */ cbref = luaL_ref(L, LUA_REGISTRYINDEX); } else { return luaL_error(L, "invalid type for callback: %s", lua_typename(L, lua_type(L, 2))); } msg_warn_config("register_pre_filter function is deprecated, " "use register_symbol instead"); ret = rspamd_register_symbol_fromlua(L, cfg, NULL, cbref, 1.0, order, SYMBOL_TYPE_PREFILTER | SYMBOL_TYPE_CALLBACK, -1, NULL, NULL, FALSE); lua_pushboolean(L, ret); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_key(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name; size_t namelen; const ucl_object_t *val; name = luaL_checklstring(L, 2, &namelen); if (name && cfg) { val = ucl_object_lookup_len(cfg->cfg_ucl_obj, name, namelen); if (val != NULL) { ucl_object_push_lua(L, val, val->type != UCL_ARRAY); } else { lua_pushnil(L); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static unsigned int lua_parse_symbol_flags(const char *str) { unsigned int ret = 0; if (str) { if (strstr(str, "fine") != NULL) { ret |= SYMBOL_TYPE_FINE; } if (strstr(str, "nice") != NULL) { ret |= SYMBOL_TYPE_FINE; } if (strstr(str, "empty") != NULL) { ret |= SYMBOL_TYPE_EMPTY; } if (strstr(str, "skip") != NULL) { ret |= SYMBOL_TYPE_SKIPPED; } if (strstr(str, "nostat") != NULL) { ret |= SYMBOL_TYPE_NOSTAT; } if (strstr(str, "idempotent") != NULL) { ret |= SYMBOL_TYPE_IDEMPOTENT; } if (strstr(str, "trivial") != NULL) { ret |= SYMBOL_TYPE_TRIVIAL; } if (strstr(str, "ghost") != NULL) { ret |= SYMBOL_TYPE_GHOST; } if (strstr(str, "mime") != NULL) { ret |= SYMBOL_TYPE_MIME_ONLY; } if (strstr(str, "ignore_passthrough") != NULL) { ret |= SYMBOL_TYPE_IGNORE_PASSTHROUGH; } if (strstr(str, "explicit_disable") != NULL) { ret |= SYMBOL_TYPE_EXPLICIT_DISABLE; } if (strstr(str, "explicit_enable") != NULL) { ret |= SYMBOL_TYPE_EXPLICIT_ENABLE; } if (strstr(str, "coro") != NULL) { ret |= SYMBOL_TYPE_USE_CORO; } } return ret; } static unsigned int lua_parse_symbol_type(const char *str) { unsigned int ret = SYMBOL_TYPE_NORMAL; char **vec; unsigned int i, l; if (str) { vec = g_strsplit_set(str, ",;", -1); if (vec) { l = g_strv_length(vec); for (i = 0; i < l; i++) { str = vec[i]; /* TODO: total shit, rework some day */ if (g_ascii_strcasecmp(str, "virtual") == 0) { ret |= SYMBOL_TYPE_VIRTUAL; ret &= ~SYMBOL_TYPE_NORMAL; ret &= ~SYMBOL_TYPE_CALLBACK; } else if (g_ascii_strcasecmp(str, "callback") == 0) { ret |= SYMBOL_TYPE_CALLBACK; ret &= ~SYMBOL_TYPE_NORMAL; ret &= ~SYMBOL_TYPE_VIRTUAL; } else if (g_ascii_strcasecmp(str, "normal") == 0) { ret |= SYMBOL_TYPE_NORMAL; ret &= ~SYMBOL_TYPE_CALLBACK; ret &= ~SYMBOL_TYPE_VIRTUAL; } else if (g_ascii_strcasecmp(str, "prefilter") == 0) { ret |= SYMBOL_TYPE_PREFILTER | SYMBOL_TYPE_GHOST; } else if (g_ascii_strcasecmp(str, "postfilter") == 0) { ret |= SYMBOL_TYPE_POSTFILTER | SYMBOL_TYPE_GHOST; } else if (g_ascii_strcasecmp(str, "connfilter") == 0 || g_ascii_strcasecmp(str, "conn_filter") == 0) { ret |= SYMBOL_TYPE_CONNFILTER | SYMBOL_TYPE_GHOST; } else if (g_ascii_strcasecmp(str, "idempotent") == 0) { ret |= SYMBOL_TYPE_GHOST | SYMBOL_TYPE_IDEMPOTENT | SYMBOL_TYPE_CALLBACK; } else { int fl = 0; fl = lua_parse_symbol_flags(str); if (fl == 0) { msg_warn("bad type: %s", str); } else { ret |= fl; } } } g_strfreev(vec); } } return ret; } enum lua_push_symbol_flags_opts { LUA_SYMOPT_FLAG_CREATE_ARRAY = 1u << 0u, LUA_SYMOPT_FLAG_CREATE_MAP = 1u << 1u, LUA_SYMOPT_FLAG_USE_MAP = 1u << 2u, LUA_SYMOPT_FLAG_USE_ARRAY = 1u << 3u, }; #define LUA_SYMOPT_IS_ARRAY(f) ((f) & (LUA_SYMOPT_FLAG_CREATE_ARRAY | LUA_SYMOPT_FLAG_USE_ARRAY)) #define LUA_SYMOPT_IS_CREATE(f) ((f) & (LUA_SYMOPT_FLAG_CREATE_ARRAY | LUA_SYMOPT_FLAG_CREATE_MAP)) #define LUA_OPTION_PUSH(nm) \ do { \ if (LUA_SYMOPT_IS_ARRAY(fl)) { \ lua_pushstring(L, #nm); \ lua_rawseti(L, -2, i++); \ } \ else { \ lua_pushboolean(L, true); \ lua_setfield(L, -2, #nm); \ } \ } while (0) static void lua_push_symbol_flags(lua_State *L, unsigned int flags, enum lua_push_symbol_flags_opts fl) { unsigned int i = 1; if (LUA_SYMOPT_IS_CREATE(fl)) { lua_newtable(L); } if (flags & SYMBOL_TYPE_FINE) { LUA_OPTION_PUSH(fine); } if (flags & SYMBOL_TYPE_EMPTY) { LUA_OPTION_PUSH(empty); } if (flags & SYMBOL_TYPE_EXPLICIT_DISABLE) { LUA_OPTION_PUSH(explicit_disable); } if (flags & SYMBOL_TYPE_EXPLICIT_ENABLE) { LUA_OPTION_PUSH(explicit_enable); } if (flags & SYMBOL_TYPE_IGNORE_PASSTHROUGH) { LUA_OPTION_PUSH(ignore_passthrough); } if (flags & SYMBOL_TYPE_NOSTAT) { LUA_OPTION_PUSH(nostat); } if (flags & SYMBOL_TYPE_IDEMPOTENT) { LUA_OPTION_PUSH(idempotent); } if (flags & SYMBOL_TYPE_MIME_ONLY) { LUA_OPTION_PUSH(mime); } if (flags & SYMBOL_TYPE_TRIVIAL) { LUA_OPTION_PUSH(trivial); } if (flags & SYMBOL_TYPE_SKIPPED) { LUA_OPTION_PUSH(skip); } if (flags & SYMBOL_TYPE_COMPOSITE) { LUA_OPTION_PUSH(composite); } } static int lua_config_get_symbol_flags(lua_State *L) { struct rspamd_config *cfg = lua_check_config(L, 1); const char *name = luaL_checkstring(L, 2); unsigned int flags; if (cfg && name) { flags = rspamd_symcache_get_symbol_flags(cfg->cache, name); if (flags != 0) { lua_push_symbol_flags(L, flags, LUA_SYMOPT_FLAG_CREATE_ARRAY); } else { lua_pushnil(L); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_register_symbol(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name = NULL, *type_str = NULL, *description = NULL, *group = NULL; double weight = 0, score = NAN, parent_float = NAN; gboolean one_shot = FALSE; int ret = -1, cbref = -1; unsigned int type = 0, flags = 0; int64_t parent = 0, priority = 0, nshots = 0; GArray *allowed_ids = NULL, *forbidden_ids = NULL; GError *err = NULL; int prev_top = lua_gettop(L); if (cfg) { if (!rspamd_lua_parse_table_arguments(L, 2, &err, RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, "name=S;weight=N;callback=F;type=S;priority=I;parent=D;" "score=D;description=S;group=S;one_shot=B;nshots=I", &name, &weight, &cbref, &type_str, &priority, &parent_float, &score, &description, &group, &one_shot, &nshots)) { msg_err_config("bad arguments: %e", err); g_error_free(err); lua_settop(L, prev_top); return luaL_error(L, "invalid arguments"); } /* Deal with flags and ids */ lua_pushstring(L, "flags"); lua_gettable(L, 2); if (lua_type(L, -1) == LUA_TSTRING) { flags = lua_parse_symbol_flags(lua_tostring(L, -1)); } else if (lua_type(L, -1) == LUA_TTABLE) { for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { flags |= lua_parse_symbol_flags(lua_tostring(L, -1)); } } lua_pop(L, 1); /* Clean flags */ lua_pushstring(L, "allowed_ids"); lua_gettable(L, 2); if (lua_type(L, -1) == LUA_TSTRING) { allowed_ids = rspamd_process_id_list(lua_tostring(L, -1)); } else if (lua_type(L, -1) == LUA_TTABLE) { allowed_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), rspamd_lua_table_size(L, -1)); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { uint32_t v = lua_tointeger(L, -1); g_array_append_val(allowed_ids, v); } } lua_pop(L, 1); lua_pushstring(L, "forbidden_ids"); lua_gettable(L, 2); if (lua_type(L, -1) == LUA_TSTRING) { forbidden_ids = rspamd_process_id_list(lua_tostring(L, -1)); } else if (lua_type(L, -1) == LUA_TTABLE) { forbidden_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), rspamd_lua_table_size(L, -1)); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { uint32_t v = lua_tointeger(L, -1); g_array_append_val(forbidden_ids, v); } } lua_pop(L, 1); if (nshots == 0) { nshots = cfg->default_max_shots; } type = lua_parse_symbol_type(type_str); if (!name && !(type & SYMBOL_TYPE_CALLBACK)) { lua_settop(L, prev_top); return luaL_error(L, "no symbol name but type is not callback"); } else if (!(type & SYMBOL_TYPE_VIRTUAL) && cbref == -1) { lua_settop(L, prev_top); return luaL_error(L, "no callback for symbol %s", name); } if (isnan(parent_float)) { parent = -1; } else { parent = parent_float; } ret = rspamd_register_symbol_fromlua(L, cfg, name, cbref, weight == 0 ? 1.0 : weight, priority, type | flags, parent, allowed_ids, forbidden_ids, FALSE); if (allowed_ids) { g_array_free(allowed_ids, TRUE); } if (forbidden_ids) { g_array_free(forbidden_ids, TRUE); } if (ret != -1) { if (!isnan(score) || group) { if (one_shot) { nshots = 1; } rspamd_config_add_symbol(cfg, name, score, description, group, flags, 0, nshots); lua_pushstring(L, "groups"); lua_gettable(L, 2); if (lua_istable(L, -1)) { for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { if (lua_isstring(L, -1)) { rspamd_config_add_symbol_group(cfg, name, lua_tostring(L, -1)); } else { lua_settop(L, prev_top); return luaL_error(L, "invalid groups element"); } } } lua_pop(L, 1); } lua_pushstring(L, "augmentations"); lua_gettable(L, 2); if (lua_type(L, -1) == LUA_TTABLE) { int tbl_idx = lua_gettop(L); for (lua_pushnil(L); lua_next(L, tbl_idx); lua_pop(L, 1)) { size_t len; const char *augmentation = lua_tolstring(L, -1, &len), *eqsign_pos; /* Find `=` symbol and use it as a separator */ eqsign_pos = memchr(augmentation, '=', len); if (eqsign_pos != NULL && eqsign_pos + 1 < augmentation + len) { rspamd_ftok_t tok; tok.begin = augmentation; tok.len = eqsign_pos - augmentation; char *augentation_name = rspamd_ftokdup(&tok); tok.begin = eqsign_pos + 1; tok.len = (augmentation + len) - tok.begin; char *augmentation_value = rspamd_ftokdup(&tok); if (!rspamd_symcache_add_symbol_augmentation(cfg->cache, ret, augentation_name, augmentation_value)) { lua_settop(L, prev_top); g_free(augmentation_value); g_free(augentation_name); return luaL_error(L, "unknown or invalid augmentation %s in symbol %s", augmentation, name); } g_free(augmentation_value); g_free(augentation_name); } else { /* Just a value */ if (!rspamd_symcache_add_symbol_augmentation(cfg->cache, ret, augmentation, NULL)) { lua_settop(L, prev_top); return luaL_error(L, "unknown augmentation %s in symbol %s", augmentation, name); } } } } } } else { lua_settop(L, prev_top); return luaL_error(L, "invalid arguments"); } lua_settop(L, prev_top); lua_pushinteger(L, ret); return 1; } static int lua_config_register_symbols(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); int i, top, idx, ret = -1; const char *sym; double weight = 1.0; if (lua_gettop(L) < 3) { if (cfg) { msg_err_config("not enough arguments to register a function"); } lua_error(L); return 0; } if (cfg) { if (lua_type(L, 2) == LUA_TSTRING) { lua_getglobal(L, luaL_checkstring(L, 2)); } else { lua_pushvalue(L, 2); } idx = luaL_ref(L, LUA_REGISTRYINDEX); if (lua_type(L, 3) == LUA_TNUMBER) { weight = lua_tonumber(L, 3); top = 4; } else { top = 3; } sym = luaL_checkstring(L, top++); ret = rspamd_register_symbol_fromlua(L, cfg, sym, idx, weight, 0, SYMBOL_TYPE_CALLBACK, -1, NULL, NULL, FALSE); for (i = top; i <= lua_gettop(L); i++) { if (lua_type(L, i) == LUA_TTABLE) { lua_pushvalue(L, i); lua_pushnil(L); while (lua_next(L, -2)) { lua_pushvalue(L, -2); sym = luaL_checkstring(L, -2); rspamd_symcache_add_symbol(cfg->cache, sym, 0, NULL, NULL, SYMBOL_TYPE_VIRTUAL, ret); lua_pop(L, 2); } lua_pop(L, 1); } else if (lua_type(L, i) == LUA_TSTRING) { sym = luaL_checkstring(L, i); rspamd_symcache_add_symbol(cfg->cache, sym, 0, NULL, NULL, SYMBOL_TYPE_VIRTUAL, ret); } } } lua_pushinteger(L, ret); return 1; } static int lua_config_register_virtual_symbol(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name; double weight; int ret = -1, parent = -1; if (cfg) { name = luaL_checkstring(L, 2); weight = luaL_checknumber(L, 3); if (lua_gettop(L) > 3) { parent = lua_tonumber(L, 4); } if (name) { ret = rspamd_symcache_add_symbol(cfg->cache, name, weight > 0 ? 0 : -1, NULL, NULL, SYMBOL_TYPE_VIRTUAL, parent); } } lua_pushinteger(L, ret); return 1; } static int lua_config_register_callback_symbol(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name = NULL; double weight; int ret = -1, top = 2; if (cfg) { if (lua_type(L, 2) == LUA_TSTRING) { /* Legacy syntax */ name = luaL_checkstring(L, 2); top++; } weight = luaL_checknumber(L, top); if (lua_type(L, top + 1) == LUA_TSTRING) { lua_getglobal(L, luaL_checkstring(L, top + 1)); } else { lua_pushvalue(L, top + 1); } ret = rspamd_register_symbol_fromlua(L, cfg, name, luaL_ref(L, LUA_REGISTRYINDEX), weight, 0, SYMBOL_TYPE_CALLBACK, -1, NULL, NULL, FALSE); } lua_pushinteger(L, ret); return 1; } static int lua_config_register_callback_symbol_priority(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name = NULL; double weight; int priority, ret = -1, top = 2; if (cfg) { if (lua_type(L, 2) == LUA_TSTRING) { /* Legacy syntax */ name = luaL_checkstring(L, 2); top++; } weight = luaL_checknumber(L, top); priority = luaL_checknumber(L, top + 1); if (lua_type(L, top + 2) == LUA_TSTRING) { lua_getglobal(L, luaL_checkstring(L, top + 2)); } else { lua_pushvalue(L, top + 2); } ret = rspamd_register_symbol_fromlua(L, cfg, name, luaL_ref(L, LUA_REGISTRYINDEX), weight, priority, SYMBOL_TYPE_CALLBACK, -1, NULL, NULL, FALSE); } lua_pushinteger(L, ret); return 1; } static int lua_config_register_dependency(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *parent = NULL, *child = NULL; int child_id; if (cfg == NULL) { lua_error(L); return 0; } if (lua_type(L, 2) == LUA_TNUMBER) { child_id = luaL_checknumber(L, 2); parent = luaL_checkstring(L, 3); return luaL_error(L, "calling for obsolete method to register deps for symbol %d->%s", child_id, parent); } else { child = luaL_checkstring(L, 2); parent = luaL_checkstring(L, 3); if (child != NULL && parent != NULL) { rspamd_symcache_add_delayed_dependency(cfg->cache, child, parent); } } return 0; } static int lua_config_set_metric_symbol(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *description = NULL, *group = NULL, *name = NULL, *flags_str = NULL; double score; gboolean one_shot = FALSE, one_param = FALSE; GError *err = NULL; double priority = 0.0; unsigned int flags = 0; int64_t nshots = 0; if (cfg) { if (lua_type(L, 2) == LUA_TTABLE) { if (!rspamd_lua_parse_table_arguments(L, 2, &err, RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, "*name=S;score=N;description=S;" "group=S;one_shot=B;one_param=B;priority=N;flags=S;" "nshots=I", &name, &score, &description, &group, &one_shot, &one_param, &priority, &flags_str, &nshots)) { msg_err_config("bad arguments: %e", err); g_error_free(err); return 0; } } else { name = luaL_checkstring(L, 2); score = luaL_checknumber(L, 3); if (lua_gettop(L) > 3 && lua_type(L, 4) == LUA_TSTRING) { description = luaL_checkstring(L, 4); } if (lua_gettop(L) > 4 && lua_type(L, 5) == LUA_TSTRING) { /* XXX: metrics */ } if (lua_gettop(L) > 5 && lua_type(L, 6) == LUA_TSTRING) { group = luaL_checkstring(L, 6); } if (lua_gettop(L) > 6 && lua_type(L, 7) == LUA_TBOOLEAN) { one_shot = lua_toboolean(L, 7); } } if (nshots == 0) { nshots = cfg->default_max_shots; } if (one_shot) { nshots = 1; } if (one_param) { flags |= RSPAMD_SYMBOL_FLAG_ONEPARAM; } if (flags_str) { if (strstr(flags_str, "one_shot") != NULL) { nshots = 1; } if (strstr(flags_str, "ignore") != NULL) { flags |= RSPAMD_SYMBOL_FLAG_IGNORE_METRIC; } if (strstr(flags_str, "one_param") != NULL) { flags |= RSPAMD_SYMBOL_FLAG_ONEPARAM; } } rspamd_config_add_symbol(cfg, name, score, description, group, flags, (unsigned int) priority, nshots); if (lua_type(L, 2) == LUA_TTABLE) { lua_pushstring(L, "groups"); lua_gettable(L, 2); if (lua_istable(L, -1)) { for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { if (lua_isstring(L, -1)) { rspamd_config_add_symbol_group(cfg, name, lua_tostring(L, -1)); } else { return luaL_error(L, "invalid groups element"); } } } lua_pop(L, 1); } } else { return luaL_error(L, "invalid arguments, rspamd_config expected"); } return 0; } static int lua_config_set_metric_action(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name = NULL; double threshold = NAN; GError *err = NULL; double priority = 0.0; ucl_object_t *obj_tbl = NULL; if (cfg) { if (lua_type(L, 2) == LUA_TTABLE) { if (!rspamd_lua_parse_table_arguments(L, 2, &err, RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, "*action=S;score=N;" "priority=N", &name, &threshold, &priority)) { msg_err_config("bad arguments: %e", err); g_error_free(err); return 0; } } else if (lua_type(L, 2) == LUA_TSTRING && lua_type(L, 3) == LUA_TTABLE) { name = lua_tostring(L, 2); obj_tbl = ucl_object_lua_import(L, 3); if (obj_tbl) { if (name) { rspamd_config_set_action_score(cfg, name, obj_tbl); ucl_object_unref(obj_tbl); } else { ucl_object_unref(obj_tbl); return luaL_error(L, "invalid first argument, action name expected"); } } else { return luaL_error(L, "invalid second argument, table expected"); } } else { return luaL_error(L, "invalid arguments, table expected"); } if (name != NULL && !isnan(threshold) && threshold != 0) { obj_tbl = ucl_object_typed_new(UCL_OBJECT); ucl_object_insert_key(obj_tbl, ucl_object_fromdouble(threshold), "score", 0, false); ucl_object_insert_key(obj_tbl, ucl_object_fromdouble(priority), "priority", 0, false); rspamd_config_set_action_score(cfg, name, obj_tbl); ucl_object_unref(obj_tbl); } } else { return luaL_error(L, "invalid arguments, rspamd_config expected"); } return 0; } static int lua_config_get_metric_action(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *act_name = luaL_checkstring(L, 2); struct rspamd_action *act; if (cfg && act_name) { act = rspamd_config_get_action(cfg, act_name); if (act) { if (!isnan(act->threshold)) { lua_pushnumber(L, act->threshold); } else { lua_pushnil(L); } } else { lua_pushnil(L); } } else { return luaL_error(L, "invalid arguments, rspamd_config expected"); } return 1; } static void lua_config_actions_cb(struct rspamd_action *act, void *cbd) { lua_State *L = (lua_State *) cbd; if (!isnan(act->threshold)) { lua_pushstring(L, act->name); lua_pushnumber(L, act->threshold); lua_settable(L, -3); } } static int lua_config_get_all_actions(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg) { lua_createtable(L, 0, rspamd_config_actions_size(cfg)); rspamd_config_actions_foreach(cfg, lua_config_actions_cb, L); } else { return luaL_error(L, "invalid arguments, rspamd_config expected"); } return 1; } static int lua_config_add_composite(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); char *name; const char *expr_str; struct rspamd_composite *composite; gboolean ret = FALSE; if (cfg) { name = rspamd_mempool_strdup(cfg->cfg_pool, luaL_checkstring(L, 2)); expr_str = luaL_checkstring(L, 3); if (name && expr_str) { composite = rspamd_composites_manager_add_from_string(cfg->composites_manager, name, expr_str); if (composite) { rspamd_symcache_add_symbol(cfg->cache, name, 0, NULL, composite, SYMBOL_TYPE_COMPOSITE, -1); ret = TRUE; } } } lua_pushboolean(L, ret); return 1; } static int lua_config_newindex(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name; GArray *allowed_ids = NULL, *forbidden_ids = NULL; int id, nshots; unsigned int flags = 0; gboolean optional = FALSE; name = luaL_checkstring(L, 2); if (cfg != NULL && name != NULL && lua_gettop(L) == 3) { if (lua_type(L, 3) == LUA_TFUNCTION) { /* Normal symbol from just a function */ lua_pushvalue(L, 3); rspamd_register_symbol_fromlua(L, cfg, name, luaL_ref(L, LUA_REGISTRYINDEX), 1.0, 0, SYMBOL_TYPE_NORMAL, -1, NULL, NULL, FALSE); } else if (lua_type(L, 3) == LUA_TTABLE) { unsigned int type = SYMBOL_TYPE_NORMAL, priority = 0; int idx; double weight = 1.0, score = NAN; const char *type_str, *group = NULL, *description = NULL; /* * Table can have the following attributes: * "callback" - should be a callback function * "weight" - optional weight * "priority" - optional priority * "type" - optional type (normal, virtual, callback) * "flags" - optional flags * -- Metric options * "score" - optional default score (overridden by metric) * "group" - optional default group * "one_shot" - optional one shot mode * "description" - optional description */ lua_pushvalue(L, 3); lua_pushstring(L, "callback"); lua_gettable(L, -2); if (lua_type(L, -1) != LUA_TFUNCTION) { lua_pop(L, 2); msg_info_config("cannot find callback definition for %s", name); return 0; } idx = luaL_ref(L, LUA_REGISTRYINDEX); /* Optional fields */ lua_pushstring(L, "weight"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TNUMBER) { weight = lua_tonumber(L, -1); } lua_pop(L, 1); lua_pushstring(L, "priority"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TNUMBER) { priority = lua_tointeger(L, -1); } lua_pop(L, 1); lua_pushstring(L, "optional"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TBOOLEAN) { optional = lua_toboolean(L, -1); } lua_pop(L, 1); lua_pushstring(L, "type"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { type_str = lua_tostring(L, -1); type = lua_parse_symbol_type(type_str); } lua_pop(L, 1); /* Deal with flags and ids */ lua_pushstring(L, "flags"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { flags = lua_parse_symbol_flags(lua_tostring(L, -1)); } else if (lua_type(L, -1) == LUA_TTABLE) { for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { flags |= lua_parse_symbol_flags(lua_tostring(L, -1)); } } lua_pop(L, 1); /* Clean flags */ lua_pushstring(L, "allowed_ids"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { allowed_ids = rspamd_process_id_list(lua_tostring(L, -1)); } else if (lua_type(L, -1) == LUA_TTABLE) { allowed_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), rspamd_lua_table_size(L, -1)); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { uint32_t v = lua_tointeger(L, -1); g_array_append_val(allowed_ids, v); } } lua_pop(L, 1); lua_pushstring(L, "forbidden_ids"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { forbidden_ids = rspamd_process_id_list(lua_tostring(L, -1)); } else if (lua_type(L, -1) == LUA_TTABLE) { forbidden_ids = g_array_sized_new(FALSE, FALSE, sizeof(uint32_t), rspamd_lua_table_size(L, -1)); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { uint32_t v = lua_tointeger(L, -1); g_array_append_val(forbidden_ids, v); } } lua_pop(L, 1); id = rspamd_register_symbol_fromlua(L, cfg, name, idx, weight, priority, type | flags, -1, allowed_ids, forbidden_ids, optional); if (allowed_ids) { g_array_free(allowed_ids, TRUE); } if (forbidden_ids) { g_array_free(forbidden_ids, TRUE); } if (id != -1) { /* Check for condition */ lua_pushstring(L, "condition"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TFUNCTION) { int condref; /* Here we pop function from the stack, so no lua_pop is required */ condref = luaL_ref(L, LUA_REGISTRYINDEX); g_assert(name != NULL); rspamd_symcache_add_condition_delayed(cfg->cache, name, L, condref); } else { lua_pop(L, 1); } /* Check for augmentations */ lua_pushstring(L, "augmentations"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TTABLE) { int tbl_idx = lua_gettop(L); for (lua_pushnil(L); lua_next(L, tbl_idx); lua_pop(L, 1)) { rspamd_symcache_add_symbol_augmentation(cfg->cache, id, lua_tostring(L, -1), NULL); } } lua_pop(L, 1); } /* * Now check if a symbol has not been registered in any metric and * insert default value if applicable */ struct rspamd_symbol *sym = g_hash_table_lookup(cfg->symbols, name); if (sym == NULL || (sym->flags & RSPAMD_SYMBOL_FLAG_UNSCORED)) { nshots = cfg->default_max_shots; lua_pushstring(L, "score"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TNUMBER) { score = lua_tonumber(L, -1); if (sym) { /* Reset unscored flag */ sym->flags &= ~RSPAMD_SYMBOL_FLAG_UNSCORED; } } lua_pop(L, 1); lua_pushstring(L, "group"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { group = lua_tostring(L, -1); } lua_pop(L, 1); if (!isnan(score) || group != NULL) { lua_pushstring(L, "description"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { description = lua_tostring(L, -1); } lua_pop(L, 1); lua_pushstring(L, "one_shot"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TBOOLEAN) { if (lua_toboolean(L, -1)) { nshots = 1; } } lua_pop(L, 1); lua_pushstring(L, "one_param"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TBOOLEAN) { if (lua_toboolean(L, -1)) { flags |= RSPAMD_SYMBOL_FLAG_ONEPARAM; } } lua_pop(L, 1); /* * Do not override the existing symbols (using zero priority), * since we are defining default values here */ if (!isnan(score)) { rspamd_config_add_symbol(cfg, name, score, description, group, flags, 0, nshots); } else if (group) { /* Add with zero score */ rspamd_config_add_symbol(cfg, name, NAN, description, group, flags, 0, nshots); } lua_pushstring(L, "groups"); lua_gettable(L, -2); if (lua_istable(L, -1)) { for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { if (lua_isstring(L, -1)) { rspamd_config_add_symbol_group(cfg, name, lua_tostring(L, -1)); } else { return luaL_error(L, "invalid groups element"); } } } lua_pop(L, 1); } } else { /* Fill in missing fields from lua definition if they are not set */ if (sym->description == NULL) { lua_pushstring(L, "description"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { description = lua_tostring(L, -1); } lua_pop(L, 1); if (description) { sym->description = rspamd_mempool_strdup(cfg->cfg_pool, description); } } /* If ungrouped and there is a group defined in lua, change the primary group * Otherwise, add to the list of groups for this symbol. */ lua_pushstring(L, "group"); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TSTRING) { group = lua_tostring(L, -1); } lua_pop(L, 1); if (group) { if (sym->flags & RSPAMD_SYMBOL_FLAG_UNGROUPED) { /* Unset the "ungrouped" group */ sym->gr = NULL; } /* Add the group. If the symbol was ungrouped, this will * clear RSPAMD_SYMBOL_FLAG_UNGROUPED from the flags. */ rspamd_config_add_symbol_group(cfg, name, group); } } /* Remove table from stack */ lua_pop(L, 1); } } else { return luaL_error(L, "invalid arguments"); } return 0; } static int lua_config_add_condition(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym = luaL_checkstring(L, 2); gboolean ret = FALSE; int condref; if (cfg && sym && lua_type(L, 3) == LUA_TFUNCTION) { lua_pushvalue(L, 3); condref = luaL_ref(L, LUA_REGISTRYINDEX); ret = rspamd_symcache_add_condition_delayed(cfg->cache, sym, L, condref); if (!ret) { luaL_unref(L, LUA_REGISTRYINDEX, condref); } } lua_pushboolean(L, ret); return 1; } static int lua_config_set_peak_cb(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); int condref; if (cfg && lua_type(L, 2) == LUA_TFUNCTION) { lua_pushvalue(L, 2); condref = luaL_ref(L, LUA_REGISTRYINDEX); rspamd_symcache_set_peak_callback(cfg->cache, condref); } return 0; } static int lua_config_enable_symbol(lua_State *L) { struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym = luaL_checkstring(L, 2); if (!sym || !cfg) { return luaL_error(L, "invalid arguments"); } rspamd_symcache_enable_symbol_static(cfg->cache, sym); return 0; } static int lua_config_disable_symbol(lua_State *L) { struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym = luaL_checkstring(L, 2); if (!sym || !cfg) { return luaL_error(L, "invalid arguments"); } rspamd_symcache_disable_symbol_static(cfg->cache, sym); return 0; } static int lua_config_register_regexp(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_lua_regexp *re = NULL; rspamd_regexp_t *cache_re; const char *type_str = NULL, *header_str = NULL; gsize header_len = 0; GError *err = NULL; enum rspamd_re_type type = RSPAMD_RE_BODY; gboolean pcre_only = FALSE; /* * - `re`* : regular expression object * - `type`*: type of regular expression: * + `mime`: mime regexp * + `rawmime`: raw mime regexp * + `header`: header regexp * + `rawheader`: raw header expression * + `body`: raw body regexp * + `url`: url regexp * - `header`: for header and rawheader regexp means the name of header * - `pcre_only`: allow merely pcre for this regexp */ if (cfg != NULL) { if (!rspamd_lua_parse_table_arguments(L, 2, &err, RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, "*re=U{regexp};*type=S;header=S;pcre_only=B", &re, &type_str, &header_str, &pcre_only)) { msg_err_config("cannot get parameters list: %e", err); if (err) { g_error_free(err); } } else { type = rspamd_re_cache_type_from_string(type_str); if ((type == RSPAMD_RE_HEADER || type == RSPAMD_RE_RAWHEADER || type == RSPAMD_RE_MIMEHEADER) && header_str == NULL) { msg_err_config( "header argument is mandatory for header/rawheader regexps"); } else { if (pcre_only) { rspamd_regexp_set_flags(re->re, rspamd_regexp_get_flags(re->re) | RSPAMD_REGEXP_FLAG_PCRE_ONLY); } if (header_str != NULL) { /* Include the last \0 */ header_len = strlen(header_str) + 1; } cache_re = rspamd_re_cache_add(cfg->re_cache, re->re, type, (gpointer) header_str, header_len, -1); /* * XXX: here are dragons! * Actually, lua regexp contains internal rspamd_regexp_t * and it owns it. * However, after this operation we have some OTHER regexp, * which we really would like to use. * So we do the following: * 1) Remove old re and unref it * 2) Replace the internal re with cached one * 3) Increase its refcount to share ownership between cache and * lua object */ if (cache_re != re->re) { rspamd_regexp_unref(re->re); re->re = rspamd_regexp_ref(cache_re); if (pcre_only) { rspamd_regexp_set_flags(re->re, rspamd_regexp_get_flags(re->re) | RSPAMD_REGEXP_FLAG_PCRE_ONLY); } } } } } return 0; } static int lua_config_replace_regexp(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_lua_regexp *old_re = NULL, *new_re = NULL; gboolean pcre_only = FALSE; GError *err = NULL; if (cfg != NULL) { if (!rspamd_lua_parse_table_arguments(L, 2, &err, RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, "*old_re=U{regexp};*new_re=U{regexp};pcre_only=B", &old_re, &new_re, &pcre_only)) { int ret = luaL_error(L, "cannot get parameters list: %s", err ? err->message : "invalid arguments"); if (err) { g_error_free(err); } return ret; } else { if (pcre_only) { rspamd_regexp_set_flags(new_re->re, rspamd_regexp_get_flags(new_re->re) | RSPAMD_REGEXP_FLAG_PCRE_ONLY); } rspamd_re_cache_replace(cfg->re_cache, old_re->re, new_re->re); } } return 0; } static int lua_config_register_worker_script(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *worker_type = luaL_checkstring(L, 2), *wtype; struct rspamd_worker_conf *cf; GList *cur; struct rspamd_worker_lua_script *sc; gboolean found = FALSE; if (cfg == NULL || worker_type == NULL || lua_type(L, 3) != LUA_TFUNCTION) { return luaL_error(L, "invalid arguments"); } for (cur = g_list_first(cfg->workers); cur != NULL; cur = g_list_next(cur)) { cf = cur->data; wtype = g_quark_to_string(cf->type); if (g_ascii_strcasecmp(wtype, worker_type) == 0) { sc = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*sc)); lua_pushvalue(L, 3); sc->cbref = luaL_ref(L, LUA_REGISTRYINDEX); DL_APPEND(cf->scripts, sc); found = TRUE; } } lua_pushboolean(L, found); return 1; } static int lua_config_add_on_load(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_config_cfg_lua_script *sc; if (cfg == NULL || lua_type(L, 2) != LUA_TFUNCTION) { return luaL_error(L, "invalid arguments"); } sc = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*sc)); lua_pushvalue(L, 2); sc->cbref = luaL_ref(L, LUA_REGISTRYINDEX); DL_APPEND(cfg->on_load_scripts, sc); return 0; } static inline int rspamd_post_init_sc_sort(const struct rspamd_config_cfg_lua_script *pra, const struct rspamd_config_cfg_lua_script *prb) { /* Inverse sort */ return prb->priority - pra->priority; } static int lua_config_add_post_init(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_config_cfg_lua_script *sc; unsigned int priority = 0; lua_Debug d; char tmp[256], *p; if (cfg == NULL || lua_type(L, 2) != LUA_TFUNCTION) { return luaL_error(L, "invalid arguments"); } if (lua_type(L, 3) == LUA_TNUMBER) { priority = lua_tointeger(L, 3); } if (lua_getstack(L, 1, &d) == 1) { (void) lua_getinfo(L, "Sl", &d); if ((p = strrchr(d.short_src, '/')) == NULL) { p = d.short_src; } else { p++; } if (strlen(p) > 200) { rspamd_snprintf(tmp, sizeof(tmp), "%10s...]:%d", p, d.currentline); } else { rspamd_snprintf(tmp, sizeof(tmp), "%s:%d", p, d.currentline); } } sc = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*sc)); lua_pushvalue(L, 2); sc->cbref = luaL_ref(L, LUA_REGISTRYINDEX); sc->priority = priority; sc->lua_src_pos = rspamd_mempool_strdup(cfg->cfg_pool, tmp); DL_APPEND(cfg->post_init_scripts, sc); DL_SORT(cfg->post_init_scripts, rspamd_post_init_sc_sort); return 0; } static int lua_config_add_config_unload(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_config_cfg_lua_script *sc; lua_Debug d; char tmp[256], *p; if (cfg == NULL || lua_type(L, 2) != LUA_TFUNCTION) { return luaL_error(L, "invalid arguments"); } if (lua_getstack(L, 1, &d) == 1) { (void) lua_getinfo(L, "Sl", &d); if ((p = strrchr(d.short_src, '/')) == NULL) { p = d.short_src; } else { p++; } if (strlen(p) > 20) { rspamd_snprintf(tmp, sizeof(tmp), "%10s...]:%d", p, d.currentline); } else { rspamd_snprintf(tmp, sizeof(tmp), "%s:%d", p, d.currentline); } } sc = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*sc)); lua_pushvalue(L, 2); sc->cbref = luaL_ref(L, LUA_REGISTRYINDEX); sc->lua_src_pos = rspamd_mempool_strdup(cfg->cfg_pool, tmp); DL_APPEND(cfg->config_unload_scripts, sc); return 0; } static void lua_periodic_callback_finish(struct thread_entry *thread, int ret); static void lua_periodic_callback_error(struct thread_entry *thread, int ret, const char *msg); struct rspamd_lua_periodic { struct ev_loop *event_loop; struct rspamd_config *cfg; char *lua_src_pos; lua_State *L; double timeout; ev_timer ev; int cbref; gboolean need_jitter; ref_entry_t ref; }; static void lua_periodic_dtor(struct rspamd_lua_periodic *periodic) { luaL_unref(periodic->L, LUA_REGISTRYINDEX, periodic->cbref); ev_timer_stop(periodic->event_loop, &periodic->ev); } static void lua_periodic_fin(gpointer p) { struct rspamd_lua_periodic *periodic = (struct rspamd_lua_periodic *) p; REF_RELEASE(periodic); } static void lua_periodic_callback(struct ev_loop *loop, ev_timer *w, int revents) { struct rspamd_lua_periodic *periodic = (struct rspamd_lua_periodic *) w->data; struct rspamd_config **pcfg, *cfg; struct ev_loop **pev_base; struct thread_entry *thread; lua_State *L; REF_RETAIN(periodic); thread = lua_thread_pool_get_for_config(periodic->cfg); thread->cd = periodic; thread->finish_callback = lua_periodic_callback_finish; thread->error_callback = lua_periodic_callback_error; L = thread->lua_state; lua_rawgeti(L, LUA_REGISTRYINDEX, periodic->cbref); pcfg = lua_newuserdata(L, sizeof(*pcfg)); rspamd_lua_setclass(L, rspamd_config_classname, -1); cfg = periodic->cfg; *pcfg = cfg; pev_base = lua_newuserdata(L, sizeof(*pev_base)); rspamd_lua_setclass(L, rspamd_ev_base_classname, -1); *pev_base = periodic->event_loop; lua_pushnumber(L, ev_now(periodic->event_loop)); lua_thread_call(thread, 3); } static void lua_periodic_callback_finish(struct thread_entry *thread, int ret) { lua_State *L; struct rspamd_lua_periodic *periodic = thread->cd; gboolean plan_more = FALSE; double timeout = 0.0; L = thread->lua_state; ev_now_update(periodic->event_loop); if (ret == 0) { if (lua_type(L, -1) == LUA_TBOOLEAN) { plan_more = lua_toboolean(L, -1); timeout = periodic->timeout; } else if (lua_type(L, -1) == LUA_TNUMBER) { timeout = lua_tonumber(L, -1); plan_more = timeout > 0 ? TRUE : FALSE; } lua_pop(L, 1); /* Return value */ } if (periodic->cfg->cur_worker) { if (periodic->cfg->cur_worker->state != rspamd_worker_state_running) { /* We are terminating, no more periodics */ plan_more = FALSE; } } if (plan_more) { if (periodic->need_jitter) { timeout = rspamd_time_jitter(timeout, 0.0); } periodic->ev.repeat = timeout; ev_timer_again(periodic->event_loop, &periodic->ev); } else { ev_timer_stop(periodic->event_loop, &periodic->ev); } REF_RELEASE(periodic); } static void lua_periodic_callback_error(struct thread_entry *thread, int ret, const char *msg) { struct rspamd_config *cfg; struct rspamd_lua_periodic *periodic = thread->cd; cfg = periodic->cfg; msg_err_config("call to periodic script (registered at %s) failed: %s", periodic->lua_src_pos, msg); lua_periodic_callback_finish(thread, ret); } static int lua_config_add_periodic(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct ev_loop *ev_base = lua_check_ev_base(L, 2); double timeout = lua_tonumber(L, 3); struct rspamd_lua_periodic *periodic; gboolean need_jitter = FALSE; lua_Debug d; char tmp[256], *p; if (cfg == NULL || timeout < 0 || lua_type(L, 4) != LUA_TFUNCTION) { return luaL_error(L, "invalid arguments"); } if (lua_type(L, 5) == LUA_TBOOLEAN) { need_jitter = lua_toboolean(L, 5); } if (lua_getstack(L, 1, &d) == 1) { (void) lua_getinfo(L, "Sl", &d); if ((p = strrchr(d.short_src, '/')) == NULL) { p = d.short_src; } else { p++; } if (strlen(p) > 20) { rspamd_snprintf(tmp, sizeof(tmp), "%10s...]:%d", p, d.currentline); } else { rspamd_snprintf(tmp, sizeof(tmp), "%s:%d", p, d.currentline); } } periodic = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*periodic)); periodic->timeout = timeout; periodic->L = L; periodic->cfg = cfg; periodic->event_loop = ev_base; periodic->need_jitter = need_jitter; periodic->lua_src_pos = rspamd_mempool_strdup(cfg->cfg_pool, tmp); lua_pushvalue(L, 4); periodic->cbref = luaL_ref(L, LUA_REGISTRYINDEX); if (need_jitter) { timeout = rspamd_time_jitter(timeout, 0.0); } ev_timer_init(&periodic->ev, lua_periodic_callback, timeout, 0.0); periodic->ev.data = periodic; ev_timer_start(ev_base, &periodic->ev); REF_INIT_RETAIN(periodic, lua_periodic_dtor); rspamd_mempool_add_destructor(cfg->cfg_pool, lua_periodic_fin, periodic); return 0; } static int lua_config_get_symbols_count(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); unsigned int res = 0; if (cfg != NULL) { res = rspamd_symcache_stats_symbols_count(cfg->cache); } else { return luaL_error(L, "invalid arguments"); } lua_pushinteger(L, res); return 1; } static int lua_config_get_symbols_cksum(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); uint64_t res = 0, *pres; if (cfg != NULL) { res = rspamd_symcache_get_cksum(cfg->cache); } else { return luaL_error(L, "invalid arguments"); } pres = lua_newuserdata(L, sizeof(res)); *pres = res; rspamd_lua_setclass(L, rspamd_int64_classname, -1); return 1; } static int lua_config_get_symbols_counters(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); ucl_object_t *counters; if (cfg != NULL) { counters = rspamd_symcache_counters(cfg->cache); ucl_object_push_lua(L, counters, true); ucl_object_unref(counters); } else { return luaL_error(L, "invalid arguments"); } return 1; } struct lua_metric_symbols_cbdata { lua_State *L; struct rspamd_config *cfg; bool is_table; }; static void lua_metric_symbol_inserter(gpointer k, gpointer v, gpointer ud) { struct lua_metric_symbols_cbdata *cbd = (struct lua_metric_symbols_cbdata *) ud; lua_State *L; const char *sym = k; struct rspamd_symbol *s = (struct rspamd_symbol *) v; struct rspamd_symbols_group *gr; int i; L = cbd->L; if (cbd->is_table) { lua_pushstring(L, sym); /* Symbol name */ } lua_createtable(L, 0, 6); lua_pushstring(L, "score"); lua_pushnumber(L, s->score); lua_settable(L, -3); lua_pushstring(L, "description"); lua_pushstring(L, s->description); lua_settable(L, -3); lua_pushstring(L, "flags"); lua_createtable(L, 0, 3); if (s->flags & RSPAMD_SYMBOL_FLAG_IGNORE_METRIC) { lua_pushstring(L, "ignore"); lua_pushboolean(L, true); lua_settable(L, -3); } if (s->flags & RSPAMD_SYMBOL_FLAG_ONEPARAM) { lua_pushstring(L, "oneparam"); lua_pushboolean(L, true); lua_settable(L, -3); } if (s->flags & RSPAMD_SYMBOL_FLAG_UNGROUPED) { lua_pushstring(L, "ungrouped"); lua_pushboolean(L, true); lua_settable(L, -3); } if (s->flags & RSPAMD_SYMBOL_FLAG_DISABLED) { lua_pushstring(L, "disabled"); lua_pushboolean(L, true); lua_settable(L, -3); } if (s->cache_item) { unsigned int sflags = rspamd_symcache_get_symbol_flags(cbd->cfg->cache, sym); lua_push_symbol_flags(L, sflags, LUA_SYMOPT_FLAG_USE_MAP); unsigned int nids; const unsigned int *allowed_ids = rspamd_symcache_get_allowed_settings_ids(cbd->cfg->cache, sym, &nids); if (allowed_ids && nids > 0) { lua_createtable(L, nids, 0); for (i = 0; i < nids; i++) { lua_pushinteger(L, allowed_ids[i]); lua_rawseti(L, -2, i + 1); } lua_setfield(L, -2, "allowed_ids"); } const unsigned int *forbidden_ids = rspamd_symcache_get_forbidden_settings_ids( cbd->cfg->cache, sym, &nids); if (forbidden_ids && nids > 0) { lua_createtable(L, nids, 0); for (i = 0; i < nids; i++) { lua_pushinteger(L, forbidden_ids[i]); lua_rawseti(L, -2, i + 1); } lua_setfield(L, -2, "forbidden_ids"); } } lua_settable(L, -3); /* Flags -> flags_table */ lua_pushstring(L, "nshots"); lua_pushinteger(L, s->nshots); lua_settable(L, -3); if (s->gr) { lua_pushstring(L, "group"); lua_pushstring(L, s->gr->name); lua_settable(L, -3); } if (s->groups && s->groups->len > 0) { lua_pushstring(L, "groups"); lua_createtable(L, s->groups->len, 0); PTR_ARRAY_FOREACH(s->groups, i, gr) { lua_pushstring(L, gr->name); lua_rawseti(L, -2, i + 1); /* Groups[i + 1] = group_name */ } lua_settable(L, -3); /* Groups -> groups_table */ } else { lua_createtable(L, 0, 0); lua_setfield(L, -2, "groups"); } if (cbd->is_table) { lua_settable(L, -3); /* Symname -> table */ } } static int lua_config_get_symbols(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL) { struct lua_metric_symbols_cbdata cbd; cbd.L = L; cbd.cfg = cfg; cbd.is_table = true; lua_createtable(L, 0, g_hash_table_size(cfg->symbols)); g_hash_table_foreach(cfg->symbols, lua_metric_symbol_inserter, &cbd); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_symbol(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym_name = luaL_checkstring(L, 2); if (cfg != NULL && sym_name != NULL) { struct lua_metric_symbols_cbdata cbd; struct rspamd_symbol *s = g_hash_table_lookup(cfg->symbols, sym_name); if (s) { cbd.L = L; cbd.cfg = cfg; cbd.is_table = false; lua_metric_symbol_inserter((void *) sym_name, s, &cbd); } else { /* No config for a symbol */ lua_pushnil(L); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_symbol_callback(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym = luaL_checkstring(L, 2); struct rspamd_abstract_callback_data *abs_cbdata; struct lua_callback_data *cbd; if (cfg != NULL && sym != NULL) { abs_cbdata = rspamd_symcache_get_cbdata(cfg->cache, sym); if (abs_cbdata == NULL || abs_cbdata->magic != rspamd_lua_callback_magic) { lua_pushnil(L); } else { cbd = (struct lua_callback_data *) abs_cbdata; if (cbd->cb_is_ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->callback.ref); } else { lua_getglobal(L, cbd->callback.name); } } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_set_symbol_callback(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym = luaL_checkstring(L, 2); struct rspamd_abstract_callback_data *abs_cbdata; struct lua_callback_data *cbd; if (cfg != NULL && sym != NULL && lua_type(L, 3) == LUA_TFUNCTION) { abs_cbdata = rspamd_symcache_get_cbdata(cfg->cache, sym); if (abs_cbdata == NULL || abs_cbdata->magic != rspamd_lua_callback_magic) { lua_pushboolean(L, FALSE); } else { cbd = (struct lua_callback_data *) abs_cbdata; if (cbd->cb_is_ref) { luaL_unref(L, LUA_REGISTRYINDEX, cbd->callback.ref); } else { cbd->cb_is_ref = TRUE; } lua_pushvalue(L, 3); cbd->callback.ref = luaL_ref(L, LUA_REGISTRYINDEX); lua_pushboolean(L, TRUE); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_symbol_stat(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym = luaL_checkstring(L, 2); double freq, stddev, tm; unsigned int hits; if (cfg != NULL && sym != NULL) { if (!rspamd_symcache_stat_symbol(cfg->cache, sym, &freq, &stddev, &tm, &hits)) { lua_pushnil(L); } else { lua_createtable(L, 0, 4); lua_pushstring(L, "frequency"); lua_pushnumber(L, freq); lua_settable(L, -3); lua_pushstring(L, "sttdev"); lua_pushnumber(L, stddev); lua_settable(L, -3); lua_pushstring(L, "time"); lua_pushnumber(L, tm); lua_settable(L, -3); lua_pushstring(L, "hits"); lua_pushinteger(L, hits); lua_settable(L, -3); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_symbol_parent(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *sym = luaL_checkstring(L, 2), *parent; if (cfg != NULL && sym != NULL) { parent = rspamd_symcache_get_parent(cfg->cache, sym); if (parent) { lua_pushstring(L, parent); } else { lua_pushnil(L); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_group_symbols(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *gr_name = luaL_checkstring(L, 2); if (cfg != NULL && gr_name != NULL) { struct rspamd_symbols_group *group; group = g_hash_table_lookup(cfg->groups, gr_name); if (group == NULL) { lua_pushnil(L); } else { unsigned int i = 1; gpointer k, v; GHashTableIter it; lua_createtable(L, g_hash_table_size(group->symbols), 0); g_hash_table_iter_init(&it, group->symbols); while (g_hash_table_iter_next(&it, &k, &v)) { lua_pushstring(L, k); lua_rawseti(L, -2, i); i++; } } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_groups(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); gboolean need_private; struct rspamd_symbols_group *gr; GHashTableIter it; gpointer k, v; if (cfg) { if (lua_isboolean(L, 2)) { need_private = lua_toboolean(L, 2); } else { need_private = !(cfg->public_groups_only); } lua_createtable(L, 0, g_hash_table_size(cfg->groups)); g_hash_table_iter_init(&it, cfg->groups); while (g_hash_table_iter_next(&it, &k, &v)) { gr = (struct rspamd_symbols_group *) v; if (need_private || (gr->flags & RSPAMD_SYMBOL_GROUP_PUBLIC)) { lua_createtable(L, 0, 4); lua_pushstring(L, gr->description); lua_setfield(L, -2, "description"); lua_pushnumber(L, gr->max_score); lua_setfield(L, -2, "max_score"); lua_pushboolean(L, (gr->flags & RSPAMD_SYMBOL_GROUP_PUBLIC) != 0); lua_setfield(L, -2, "is_public"); /* TODO: maybe push symbols as well */ /* Parent table indexed by group name */ lua_setfield(L, -2, gr->name); } } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_register_finish_script(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_config_cfg_lua_script *sc; if (cfg != NULL && lua_type(L, 2) == LUA_TFUNCTION) { sc = rspamd_mempool_alloc0(cfg->cfg_pool, sizeof(*sc)); lua_pushvalue(L, 2); sc->cbref = luaL_ref(L, LUA_REGISTRYINDEX); DL_APPEND(cfg->on_term_scripts, sc); } else { return luaL_error(L, "invalid arguments"); } return 0; } static inline bool rspamd_lua_config_check_settings_symbols_object(const ucl_object_t *obj) { if (obj == NULL) { /* Semantically valid */ return true; } if (ucl_object_type(obj) == UCL_OBJECT) { /* Key-value mapping - should be okay */ return true; } if (ucl_object_type(obj) == UCL_ARRAY) { /* Okay if empty */ if (obj->len == 0) { return true; } } /* Everything else not okay */ return false; } static int lua_config_register_settings_id(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *settings_name = luaL_checkstring(L, 2); if (cfg != NULL && settings_name) { ucl_object_t *sym_enabled, *sym_disabled; enum rspamd_config_settings_policy policy = RSPAMD_SETTINGS_POLICY_DEFAULT; sym_enabled = ucl_object_lua_import(L, 3); if (!rspamd_lua_config_check_settings_symbols_object(sym_enabled)) { ucl_object_unref(sym_enabled); return luaL_error(L, "invalid symbols enabled"); } sym_disabled = ucl_object_lua_import(L, 4); if (!rspamd_lua_config_check_settings_symbols_object(sym_disabled)) { ucl_object_unref(sym_enabled); ucl_object_unref(sym_disabled); return luaL_error(L, "invalid symbols enabled"); } /* Check policy */ if (lua_isstring(L, 5)) { const char *policy_str = lua_tostring(L, 5); if (strcmp(policy_str, "default") == 0) { policy = RSPAMD_SETTINGS_POLICY_DEFAULT; } else if (strcmp(policy_str, "implicit_allow") == 0) { policy = RSPAMD_SETTINGS_POLICY_IMPLICIT_ALLOW; } else if (strcmp(policy_str, "implicit_deny") == 0) { policy = RSPAMD_SETTINGS_POLICY_IMPLICIT_DENY; } else { return luaL_error(L, "invalid settings policy: %s", policy_str); } } else { /* Apply heuristic */ if (!sym_enabled) { policy = RSPAMD_SETTINGS_POLICY_IMPLICIT_ALLOW; } } rspamd_config_register_settings_id(cfg, settings_name, sym_enabled, sym_disabled, policy); if (sym_enabled) { ucl_object_unref(sym_enabled); } if (sym_disabled) { ucl_object_unref(sym_disabled); } } else { return luaL_error(L, "invalid arguments"); } return 0; } static int lua_config_register_monitored(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_monitored *m, **pm; const char *url, *type; ucl_object_t *params = NULL; url = lua_tostring(L, 2); type = lua_tostring(L, 3); if (cfg != NULL && url != NULL && type != NULL) { if (g_ascii_strcasecmp(type, "dns") == 0) { lua_Debug ar; if (lua_type(L, 4) == LUA_TTABLE) { params = ucl_object_lua_import(L, 4); } /* Get lua line and source */ lua_getstack(L, 1, &ar); lua_getinfo(L, "nSl", &ar); m = rspamd_monitored_create_(cfg->monitored_ctx, url, RSPAMD_MONITORED_DNS, RSPAMD_MONITORED_DEFAULT, params, ar.short_src); if (m) { pm = lua_newuserdata(L, sizeof(*pm)); *pm = m; rspamd_lua_setclass(L, rspamd_monitored_classname, -1); } else { lua_pushnil(L); } if (params) { ucl_object_unref(params); } } else { return luaL_error(L, "invalid monitored type: %s", type); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_add_doc(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg; const char *path = NULL, *option, *doc_string; const char *type_str = NULL, *default_value = NULL; ucl_type_t type = UCL_NULL; gboolean required = FALSE; GError *err = NULL; cfg = lua_check_config(L, 1); if (lua_type(L, 2) == LUA_TSTRING) { path = luaL_checkstring(L, 2); } option = luaL_checkstring(L, 3); doc_string = luaL_checkstring(L, 4); if (cfg && option && doc_string) { if (lua_type(L, 5) == LUA_TTABLE) { if (!rspamd_lua_parse_table_arguments(L, 5, &err, RSPAMD_LUA_PARSE_ARGUMENTS_DEFAULT, "type=S;default=S;required=B", &type_str, &default_value, &required)) { msg_err_config("cannot get parameters list: %e", err); if (err) { g_error_free(err); } if (type_str) { if (!ucl_object_string_to_type(type_str, &type)) { msg_err_config("invalid type: %s", type_str); } } } } rspamd_rcl_add_doc_by_path(cfg, path, doc_string, option, type, NULL, 0, default_value, required); } else { return luaL_error(L, "invalid arguments"); } return 0; } static int lua_config_add_example(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg; const char *path = NULL, *option, *doc_string, *example; gsize example_len; cfg = lua_check_config(L, 1); if (lua_type(L, 2) == LUA_TSTRING) { path = luaL_checkstring(L, 2); } option = luaL_checkstring(L, 3); doc_string = luaL_checkstring(L, 4); example = luaL_checklstring(L, 5, &example_len); if (cfg && option && doc_string && example) { rspamd_rcl_add_doc_by_example(cfg, path, doc_string, option, example, example_len); } else { return luaL_error(L, "invalid arguments"); } return 0; } static int lua_config_get_cpu_flags(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); struct rspamd_cryptobox_library_ctx *crypto_ctx; if (cfg != NULL) { crypto_ctx = cfg->libs_ctx->crypto_ctx; lua_newtable(L); if (crypto_ctx->cpu_config & CPUID_SSSE3) { lua_pushstring(L, "ssse3"); lua_pushboolean(L, true); lua_settable(L, -3); } if (crypto_ctx->cpu_config & CPUID_SSE41) { lua_pushstring(L, "sse41"); lua_pushboolean(L, true); lua_settable(L, -3); } if (crypto_ctx->cpu_config & CPUID_SSE42) { lua_pushstring(L, "sse42"); lua_pushboolean(L, true); lua_settable(L, -3); } if (crypto_ctx->cpu_config & CPUID_SSE2) { lua_pushstring(L, "sse2"); lua_pushboolean(L, true); lua_settable(L, -3); } if (crypto_ctx->cpu_config & CPUID_SSE3) { lua_pushstring(L, "sse3"); lua_pushboolean(L, true); lua_settable(L, -3); } if (crypto_ctx->cpu_config & CPUID_AVX) { lua_pushstring(L, "avx"); lua_pushboolean(L, true); lua_settable(L, -3); } if (crypto_ctx->cpu_config & CPUID_AVX2) { lua_pushstring(L, "avx2"); lua_pushboolean(L, true); lua_settable(L, -3); } } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_has_torch(lua_State *L) { msg_warn("use of the obsoleted `has_torch` function"); lua_pushboolean(L, false); return 1; } static int lua_config_experimental_enabled(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL) { lua_pushboolean(L, cfg->enable_experimental); } else { return luaL_error(L, "invalid arguments"); } return 1; } struct rspamd_lua_include_trace_cbdata { lua_State *L; int cbref; }; static void lua_include_trace_cb(struct ucl_parser *parser, const ucl_object_t *parent, const ucl_object_t *args, const char *path, size_t pathlen, void *user_data) { struct rspamd_lua_include_trace_cbdata *cbdata = (struct rspamd_lua_include_trace_cbdata *) user_data; int err_idx; lua_State *L; L = cbdata->L; lua_pushcfunction(L, &rspamd_lua_traceback); err_idx = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, cbdata->cbref); /* Current filename */ lua_pushstring(L, ucl_parser_get_cur_file(parser)); /* Included filename */ lua_pushlstring(L, path, pathlen); /* Params */ if (args) { ucl_object_push_lua(L, args, true); } else { lua_newtable(L); } /* Parent */ if (parent) { lua_pushstring(L, ucl_object_key(parent)); } else { lua_pushnil(L); } if (lua_pcall(L, 4, 0, err_idx) != 0) { msg_err("lua call to local include trace failed: %s", lua_tostring(L, -1)); } lua_settop(L, err_idx - 1); } #define LUA_TABLE_TO_HASH(htb, idx) \ do { \ lua_pushstring(L, (idx)); \ lua_gettable(L, -2); \ if (lua_isstring(L, -1)) { \ g_hash_table_insert((htb), (idx), g_strdup(lua_tostring(L, -1))); \ } \ lua_pop(L, 1); \ } while (0) static int lua_config_load_ucl(lua_State *L) { struct rspamd_config *cfg = lua_check_config(L, 1); const char *filename; GHashTable *paths = g_hash_table_new_full(rspamd_str_hash, rspamd_str_equal, NULL, g_free); GError *err = NULL; if (cfg) { if (lua_isstring(L, 2)) { filename = lua_tostring(L, 2); } else { filename = RSPAMD_CONFDIR "/rspamd.conf"; } /* Convert rspamd_paths */ lua_getglobal(L, "rspamd_paths"); if (lua_istable(L, -1)) { LUA_TABLE_TO_HASH(paths, RSPAMD_CONFDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_LOCAL_CONFDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_RUNDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_DBDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_LOGDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_WWWDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_PLUGINSDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_RULESDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_LUALIBDIR_INDEX); LUA_TABLE_TO_HASH(paths, RSPAMD_PREFIX_INDEX); } lua_pop(L, 1); if (lua_isfunction(L, 3)) { struct rspamd_lua_include_trace_cbdata cbd; lua_pushvalue(L, 3); cbd.cbref = luaL_ref(L, LUA_REGISTRYINDEX); cbd.L = L; if (!rspamd_config_parse_ucl(cfg, filename, paths, lua_include_trace_cb, &cbd, lua_toboolean(L, 4), &err)) { luaL_unref(L, LUA_REGISTRYINDEX, cbd.cbref); lua_pushboolean(L, false); lua_pushfstring(L, "failed to load config: %s", err->message); g_error_free(err); g_hash_table_unref(paths); return 2; } luaL_unref(L, LUA_REGISTRYINDEX, cbd.cbref); } else { if (!rspamd_config_parse_ucl(cfg, filename, paths, NULL, NULL, lua_toboolean(L, 3), &err)) { lua_pushboolean(L, false); lua_pushfstring(L, "failed to load config: %s", err->message); g_error_free(err); g_hash_table_unref(paths); return 2; } } rspamd_rcl_maybe_apply_lua_transform(cfg); rspamd_config_calculate_cksum(cfg); } else { return luaL_error(L, "invalid arguments"); } g_hash_table_unref(paths); lua_pushboolean(L, true); return 1; } #undef IDX_TO_HASH static int lua_config_parse_rcl(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); GHashTable *excluded = g_hash_table_new_full(rspamd_str_hash, rspamd_str_equal, g_free, NULL); GError *err = NULL; struct rspamd_rcl_sections_map *top; if (cfg) { if (lua_istable(L, 2)) { lua_pushvalue(L, 2); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { g_hash_table_insert(excluded, g_strdup(lua_tostring(L, -1)), GINT_TO_POINTER(-1)); } lua_pop(L, 1); } top = rspamd_rcl_config_init(cfg, excluded); if (!rspamd_rcl_parse(top, cfg, cfg, cfg->cfg_pool, cfg->cfg_ucl_obj, &err)) { lua_pushboolean(L, false); lua_pushfstring(L, "failed to load config: %s", err->message); g_error_free(err); g_hash_table_unref(excluded); rspamd_rcl_sections_free(top); return 2; } } else { return luaL_error(L, "invalid arguments"); } g_hash_table_unref(excluded); rspamd_rcl_sections_free(top); lua_pushboolean(L, true); return 1; } static int lua_config_init_modules(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL) { rspamd_lua_post_load_config(cfg); lua_pushboolean(L, rspamd_init_filters(cfg, false, false)); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_init_subsystem(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *subsystem = luaL_checkstring(L, 2); char **parts; unsigned int nparts, i; if (cfg != NULL && subsystem != NULL) { parts = g_strsplit_set(subsystem, ";,", -1); nparts = g_strv_length(parts); for (i = 0; i < nparts; i++) { if (strcmp(parts[i], "filters") == 0) { rspamd_lua_post_load_config(cfg); rspamd_init_filters(cfg, false, false); } else if (strcmp(parts[i], "langdet") == 0) { if (!cfg->lang_det) { cfg->lang_det = rspamd_language_detector_init(cfg); rspamd_mempool_add_destructor(cfg->cfg_pool, (rspamd_mempool_destruct_t) rspamd_language_detector_unref, cfg->lang_det); } } else if (strcmp(parts[i], "stat") == 0) { rspamd_stat_init(cfg, NULL); } else if (strcmp(parts[i], "dns") == 0) { struct ev_loop *ev_base = lua_check_ev_base(L, 3); if (ev_base) { cfg->dns_resolver = rspamd_dns_resolver_init(rspamd_log_default_logger(), ev_base, cfg); } else { g_strfreev(parts); return luaL_error(L, "no event base specified"); } } else if (strcmp(parts[i], "symcache") == 0) { rspamd_symcache_init(cfg->cache); } else { int ret = luaL_error(L, "invalid param: %s", parts[i]); g_strfreev(parts); return ret; } } g_strfreev(parts); } else { return luaL_error(L, "invalid arguments"); } return 0; } static int lua_config_register_re_selector(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); const char *name = luaL_checkstring(L, 2); const char *selector_str = luaL_checkstring(L, 3); const char *delimiter = ""; bool flatten = false; int top = lua_gettop(L); bool res = false; if (cfg && name && selector_str) { if (lua_gettop(L) >= 4) { delimiter = luaL_checkstring(L, 4); if (lua_isboolean(L, 5)) { flatten = lua_toboolean(L, 5); } } if (luaL_dostring(L, "return require \"lua_selectors\"") != 0) { msg_warn_config("cannot require lua_selectors: %s", lua_tostring(L, -1)); } else { if (lua_type(L, -1) != LUA_TTABLE) { msg_warn_config("lua selectors must return " "table and not %s", lua_typename(L, lua_type(L, -1))); } else { lua_pushstring(L, "create_selector_closure"); lua_gettable(L, -2); if (lua_type(L, -1) != LUA_TFUNCTION) { msg_warn_config("create_selector_closure must return " "function and not %s", lua_typename(L, lua_type(L, -1))); } else { int err_idx, ret; struct rspamd_config **pcfg; lua_pushcfunction(L, &rspamd_lua_traceback); err_idx = lua_gettop(L); /* Push function */ lua_pushvalue(L, -2); pcfg = lua_newuserdata(L, sizeof(*pcfg)); rspamd_lua_setclass(L, rspamd_config_classname, -1); *pcfg = cfg; lua_pushstring(L, selector_str); lua_pushstring(L, delimiter); lua_pushboolean(L, flatten); if ((ret = lua_pcall(L, 4, 1, err_idx)) != 0) { msg_err_config("call to create_selector_closure lua " "script failed (%d): %s", ret, lua_tostring(L, -1)); } else { if (lua_type(L, -1) != LUA_TFUNCTION) { msg_warn_config("create_selector_closure " "invocation must return " "function and not %s", lua_typename(L, lua_type(L, -1))); } else { ret = luaL_ref(L, LUA_REGISTRYINDEX); rspamd_re_cache_add_selector(cfg->re_cache, name, ret); res = true; } } } } } } else { return luaL_error(L, "invalid arguments"); } lua_settop(L, top); lua_pushboolean(L, res); if (res) { msg_info_config("registered regexp selector %s", name); } return 1; } static int lua_config_get_tld_path(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL) { lua_pushstring(L, cfg->tld_file); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_dns_max_requests(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL) { lua_pushinteger(L, cfg->dns_max_requests); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_config_get_dns_timeout(lua_State *L) { LUA_TRACE_POINT; struct rspamd_config *cfg = lua_check_config(L, 1); if (cfg != NULL) { lua_pushnumber(L, cfg->dns_timeout); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_monitored_alive(lua_State *L) { LUA_TRACE_POINT; struct rspamd_monitored *m = lua_check_monitored(L, 1); if (m) { lua_pushboolean(L, rspamd_monitored_alive(m)); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_monitored_offline(lua_State *L) { LUA_TRACE_POINT; struct rspamd_monitored *m = lua_check_monitored(L, 1); if (m) { lua_pushnumber(L, rspamd_monitored_offline_time(m)); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_monitored_total_offline(lua_State *L) { LUA_TRACE_POINT; struct rspamd_monitored *m = lua_check_monitored(L, 1); if (m) { lua_pushnumber(L, rspamd_monitored_total_offline_time(m)); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_monitored_latency(lua_State *L) { LUA_TRACE_POINT; struct rspamd_monitored *m = lua_check_monitored(L, 1); if (m) { lua_pushnumber(L, rspamd_monitored_latency(m)); } else { return luaL_error(L, "invalid arguments"); } return 1; } void luaopen_config(lua_State *L) { rspamd_lua_new_class(L, rspamd_config_classname, configlib_m); lua_pop(L, 1); rspamd_lua_new_class(L, rspamd_monitored_classname, monitoredlib_m); lua_pop(L, 1); } void lua_call_finish_script(struct rspamd_config_cfg_lua_script *sc, struct rspamd_task *task) { struct rspamd_task **ptask; struct thread_entry *thread; thread = lua_thread_pool_get_for_task(task); thread->task = task; lua_State *L = thread->lua_state; lua_rawgeti(L, LUA_REGISTRYINDEX, sc->cbref); ptask = lua_newuserdata(L, sizeof(struct rspamd_task *)); rspamd_lua_setclass(L, rspamd_task_classname, -1); *ptask = task; lua_thread_call(thread, 1); }