metrics - a global table of metrics definitions. This variable is a table that is indexed by metric name and provides the ability to set up symbols’ properties:

metrics['default']={-- Set weight and descriptionSYMBOL={weight=9.0,description='description'},-- Just set weightSYMBOL2=9.0,}-- Add symbol definitionmetrics['default']['SYMBOL3']={weight=1,description='description'}

classifiers - a table of classifiers pre-filters. Pre-filter must be a function that accepts 4 parameters: classifier, task, is_learn and is_spam. Pre-filter must return a table of statfiles to be checked or learned for this message or nil if all suitable statfiles must be learned or checked. Here is an example of language detection for classification:

Also it is possible to declare functions and use closures when defining Rspamd rules:

-- Here is a sample of using closure function inside rulelocalfunctioncheck_headers_tab(task,header_name)-- Extract raw headers from messagelocalraw_headers=task:get_raw_header(header_name)-- Make match of headers, that are separated with tabs, not spacesifraw_headersthenfor_,rhinipairs(raw_headers)doifrh['tab_separated']then-- We have header value separated by tab symbolreturntrue,rh['name']endendendreturnfalseendrspamd_config.HEADER_TAB_FROM_WHITELISTED=function(task)returncheck_headers_tab(task,"From")endrspamd_config.HEADER_TAB_TO_WHITELISTED=function(task)returncheck_headers_tab(task,"To")endrspamd_config.HEADER_TAB_DATE_WHITELISTED=function(task)returncheck_headers_tab(task,"Date")end-- Table form of rule definitionrspamd_config.R_EMPTY_IMAGE={callback=function(task)localtp=task:get_text_parts()-- get text parts in a messagefor_,pinipairs(tp)do-- iterate over text parts array using `ipairs`ifp:is_html()then-- if the current part is html partlocalhc=p:get_html()-- we get HTML contextlocallen=p:get_length()-- and part's lengthiflen<50then-- if we have a part that has less than 50 bytes of textlocalimages=hc:get_images()-- then we check for HTML imagesifimagesthen-- if there are imagesfor_,iinipairs(images)do-- then iterate over images in the partifi['height']+i['width']>=400then-- if we have a large imagereturntrue-- add symbolendendendendendendend,score=10.0,condition=function(task)iftask:get_header('Subject')thenreturntrueendreturnfalseend,description='No text parts and a large image',score=3.1,}

Using Lua in rules provides many abilities to write complex mail filtering rules.

Writing Lua plugins

Plugins are more complex filters than ordinary rules. Plugins can have their own configuration parameters and multiple callbacks. Plugins can make DNS requests, read from Rspamd maps and insert custom results.

Structure of the typical plugin

Each Rspamd plugin has a common structure:

Registering configuration parameters

Reading configuration parameters and set up callbacks

Callbacks that are called by Rspamd during message processing

Here is a simple plugin example:

localconfig_param='default'localfunctionsample_callback(task)end-- Reading configuration-- Get all options for this pluginlocalopts=rspamd_config:get_all_opt('sample')ifoptsthenifopts['config']thenconfig_param=opts['config']-- Register callbackrspamd_config:register_symbol('some_symbol',sample_callback)endend

This plugin uses global variable rspamd_config to extract configuration options. Then it registers function sample_callback that will be called for processing symbol some_symbol.

Using DNS requests inside plugins

It is often required to make DNS requests for messages checks. Here is an example of making asynchronous DNS request from Rspamd Lua plugin:

-- Function-callback of Rspamd rulelocalfunctionsymbol_cb(task)-- Task is now local variablelocalfunctiondns_cb(resolver,to_resolve,results,err,str)-- Increase total count of dns requeststask:inc_dns_req()ifresultsthentask:insert_result('symbol',1,str)endend-- Resolve 'example.com' using primitives from the task passedtask:get_resolver():resolve_a(task:get_session(),task:get_mempool(),'example.com',dns_cb,'sample string')end

Using maps from Lua plugin

Maps hold dynamically loaded data like lists or IP trees. It is possible to use 3 types of maps:

radix_tree stores IP addresses

hash_map stores plain strings (domains usually)

callback call for a specified Lua callback when a map is loaded or changed, map’s content is passed to that callback as a parameter

Conclusions

Lua plugins are a powerful tool for creating complex filters that can access practically all features of Rspamd. Lua plugins can be used for writing custom rules which could interact with Rspamd in many ways such as using maps and making DNS requests. Rspamd is shipped with a number of Lua plugins that could be used as examples while writing your own plugins.