Email Processing in Lua

In this section

General Information

Script for Milter

Requirements for the Script

Examples

Tables in Use (MilterContext, MilterSender, MilterResult, MilterModifications, MilterAddedField, MilterChangedField, MilterGenerator, MilterModifier)

Script for Spamd

Requirements for the Script

Tables in Use (SpamdContext, SpamdReportResult)

Script for Rspamd

Requirements for the Script

Example

Tables in Use (RspamdContext, RspamdSender, RspamdResult, RspamdSymbol)

Script for SMTP

Requirements for the Script

Example

Tables in Use (SmtpContext, SmtpResult)

Tables Describing the Message Structure:

RcptTo,

MimeMessage,

MimePart,

From,

To,

ContentType,

ContentDisposition,

Spam,

Virus,

Url,

RawUrl,

ThreatFilter (with examples),

UrlFilter (with examples),

FileFilter (with an example),

PartFilter (with examples),

ScanReportFilter (with examples),

MimeHeader,

HeaderField,

HeaderFieldValue,

MimeBody,

ScanReport,

Archive,

DKIM,

DKIMSignature,

DKIMResult,

DKIMSignatureFilter,

SPF,

SPFResult,

VxcubeAnalysis,

VxcubeTask

Available Auxiliary Modules:

drweb,

drweb.lookup,

drweb.dnsxl,

drweb.regex,

drweb.subprocess,

drweb.config,

drweb.store

General Information

The Dr.Web MailD component supports interaction via the Lua program interpreter (version 5.3.4 is used; it is bundled with Dr.Web for UNIX Mail Servers). Scripts written in Lua can be used by the component to analyze and process email messages.

A message received via Milter, Spamd, Rspamd, or in the SMTP mode is analyzed using a Lua script specified in the settings of Dr.Web MailD as the value of the MilterHook, SpamdHook, RspamdHook or Smtphook parameter respectively. The value of this parameters can be either the full code of the script or a path to it.

More examples of Lua scripts for processing emails are available at:
https://github.com/DoctorWebLtd/drweb-lua-examples/tree/master/maild.

Script for Message Processing for the Milter Interface

Requirements for the Script

The script must contain a global function that is an entry point in the message scanning module (Dr.Web MailD calls this function to process all received messages). The processing function must comply with the following call conventions:

1.Function name is milter_hook;

2.The only argument is the MilterContext table (provides access from the function to the information about the processed email message);

3.The only returned value is the MilterResult completed table. The returned value defines a verdict about the scanned message (accept, reject, change, or discard), as well as potential actions to be applied to the message if it is accepted.

An example below illustrates a function which always returns to Dr.Web MailD the Accept verdict for all messages received for scanning via the Milter interface (here and elsewhere the ctx argument is an instance of the MilterContext table):

function milter_hook(ctx)
  return {action = "accept"}
end

Examples

1.Detecting threats and checking for signs of spam

The script below performs the following operations:

adds names of threats detected in an email message to the X-Found header values;

adds the [SPAM] prefix to a message subject (the Subject header value) if the spam score exceeds 100 points;

sends the message to the recipient after it is processed.

function milter_hook (ctx)

 -- Add names of detected threats to the header
 for threat in ctx.message.threats() do
   ctx.modifier.add_header_field("X-Found", threat.name)
 end

 -- Change the Subject header value, if the message spam score exceeds 100 points
 if ctx.message.spam.score > 100 then
   local old_value = ctx.message.header.value("Subject") or ""
   local new_value = "[SPAM] " .. old_value
   ctx.modifier.change_header_field("Subject", new_value)
 end

 -- Send the message to the recipient having applied the pending changes
 return {
   action = "accept",
   modifications = ctx.modifier.modifications()
 }

end

2.Placing detected threats and the message itself (if its spam score is exceeded) in a protected archive

The script below performs the following operations:

places the detected threats in a protected archive;

places an email message in a protected archive if the spam score exceeds 100 points.

function milter_hook(ctx)

 ctx.modifier.repack_password = "xxx"
 ctx.modifier.repack_message = ""

 -- Place all message parts where threats were found
 -- in a password-protected archive
 for threat, path in ctx.message.threats() do
   ctx.modifier.repack(path)
   local msg = " Threat found: " .. threat.name
   ctx.modifier.repack_message = ctx.modifier.repack_message .. msg
 end

 -- Repack the whole message if its spam score
 -- exceeds 100 points
 if ctx.message.spam.score > 100 then
   ctx.modifier.repack()
   local msg = " Spam score: " .. ctx.message.spam.score
   ctx.modifier.repack_message = ctx.modifier.repack_message .. msg
 end

 -- Send the message to the recipient having applied the pending changes
 -- Note that if the modification table is not specified,
 -- it will be returned automatically
 return {action = "accept"}

end

The message being scanned will be modified: all unwanted parts will be removed, archived and added to an attachment.
The archive in which the unwanted elements of the message are enclosed is protected with the password specified as the value of the ctx.modifier.repack_password variable. The password specified in the RepackPassword parameter in the configuration file is not used in this case.

Tables in Use

MilterContext Table

This table is used as an input argument of the milter_hook function. It contains the information about the message being processed (fields, structure, headers, body, information about a sender and recipients, information about the SMTP session).

Field

Description

Data type

session_id

Identifier of the client session during which messages from this client are processed

String

sender

Information about the message sender.

MilterSender Table

helo

The welcome HELO/EHLO string received from the SMTP client that sent the message, or nil if the string is missing/unknown (not provided by the MTA)

String

from

Sender’s email address without angle brackets (for example: user@domain.com)

String

to

Email addresses of recipients (without angle brackets).

An example of a hook to process messages having multiple recipients:

if re.match("(eve|neil)@(example\\.com)$", ctx.from or '', re.ignore_case) and ctx.to.all_match("(tom|kane)@example\\.com$")
   then
       dw.notice("no_checking")
   else
       dw.notice("do something")
   end

RcptTo Table

message

Email message (with the body and all headers)

MimeMessage Table

modifier

The MilterModifier table that contains all changes to be applied to the message if the "accept" action is generated according to the scanning results in the MilterResult table.

MilterModifier Table

gen

The MilterGenerator table used for generating custom headers, such as X-Antivirus

MilterGenerator Table

spf

The SPF table used for performing an SPF check of the sender

SPF Table

Overridden metamethods: None

MilterSender Table

This table is used as the sender field of the MilterContext table. Contains the information about the message sender.

Field

Description

Data type

hostname

Sender’s host name (FQDN)

String

family

Connection type as a string:

"U"—unknown type

"L"—connection via a UNIX socket

"4"—connection via IPv4

"6"—connection via IPv6

String

port

Port number

Integer

ip

IP address of the sender’s host, or nil if the IP address is absent or unknown (not provided by the MTA)

IpAddress Table

Overridden metamethods: None

MilterResult Table

The table represents the result returned by the milter_hook function.

Field

Description

Data type

action

String with description of the action that should be applied to the message:

"accept"—accept (i.e. allow MTA to send the message to the recipient);

"discard"—discard the message without notifying the sender

"reject"—reject the message and return the SMTP 5** code to the sender;

"tempfail"—reject the message and return the SMTP 4** code to the sender;

"replycode"—send the SMTP response, that is specified in the code field, to the sender.

Required field.

String

code

A three-digit SMTP response code to be sent to the sender, for example, "541".

Optional field. Only used if action = "replycode".

String

text

Text of a response sent to the sender, for example, "User not local or invalid address - Relay denied".

Optional field. Only used if action = "replycode".

String

message

String with a response text sent to the sender, for example, "Message rejected as spam".

Optional field. Only used if action = "reject".

String

modifications

Table describing changes to be applied to the message before sending it to the recipient.

Optional field. Only used if action = "accept".

MilterModifications Table

added_recipients

List of email addresses of additional message recipients.

Optional field. Only used if action = "accept".

String array

deleted_recipients

List of email addresses to be excluded from the list of message recipients.

Optional field. Only used if action = "accept".

String array

incident

Description of the incident in the registered event of the Mail type.

If the field is a string, the content of this string will be transmitted to the incident_text field of the Mail event and the event will be registered;

If the field is boolean and is equal to false, then the incident_text field of the Mail event will be absent and the event will not be registered;

If the field is boolean and is equal to true, then the incident_text field of the Mail event will be filled in automatically and the event will be registered, if the reported value of incident_text is not empty.

String or boolean value

Overridden metamethods: None

MilterModifications Table

The table is used for description of all changes to be made to the message in case of delivering it to the recipient (the accept action is chosen). All fields of the table are optional; if a corresponding field does not have a value, the action specified in this field is not applied to the message.

Field

Description

Data type

new_body

New message body (without headers) to replace the body of the message being processed

String

added_fields

Headers to be added to the message being processed

Array of MilterAddedField tables

changed_fields

Headers to be modified or removed from the message being processed

Array of MilterChangedField tables

Overridden metamethods: None

MilterAddedField Table

The table contains the description of the headers to be added to the message.

Field

Description

Data type

name

Header name

String

value

Header value

String

Overridden metamethods: None

MilterChangedField Table

The table contains the description of the headers to be modified in the message (or removed from it).

Field

Description

Data type

name

Header name

String

index

Ordinal number of the header with the name name in the message to be changed (counting from 1)

Number

value

New value of the header (or an empty string "" to remove the header).

String

Overridden metamethods: None

For description of the changes to be made to the message after processing, it is recommended not to fill in the MilterModifications table directly. You should use methods from the MilterModifier custom table received from the context.

MilterGenerator Table

The table contains auxiliary methods for generating X-Antivirus and X-Authentication-Results standard headers.

Field

Description

Data type

х_antivirus_header_field

The function is used for generating the X-Antivirus header that contains the information about the anti-virus components involved in the scanning of the message. The HeaderField table or nil (if the message has not been scanned yet) is returned.
The name field in the HeaderField table has a fixed value (X-Antivirus).

Function

authentication_results_header_field

The function is used for generating the Authentication-Results header which contains the information about the results of DKIM and SPF checks. Takes the authserv_id optional argument (a string) which is the ID of the authenticang server in the generated header. By default the authserv_id value matches the name of the host on which Dr.Web MailD is running.
The function returns the HeaderField table.
The name field in the HeaderField table has a fixed value (Authentication-Results).

Function

Overridden metamethods: None

MilterModifier Table

The table is used for describing changes to be made in the message after it is processed (if it is sent to the recipient).

Field

Description

Data type

add_header_field

Function which schedules an action to add a new header to the message.

Receives two mandatory arguments:

name is a name of the header to be added in the form of a string;

value is the header value in the form of a string.

The value is encoded in accordance with RFC 2047.

Function

change_header_field

Function that schedules an action to change (or remove) the specified header.

Receives two mandatory arguments:

name is a name of the header to be changed in the form of a string;

value is the header value in the form of a string.

If the message has multiple headers with the specified name, the function will change the value of the first header with such name. In case of multiple value changes of the same header, only the last value is kept. If the value is an empty string "", the header name is removed from the message.

The value is encoded in accordance with RFC 2047.

Function

modifications

Function that returns the MilterModifications table containing the entire list of changes scheduled to be made in the message. Does not accept any arguments.

Function

repack

Function that schedules the repacking of the specified message part (or the entire message, if the part is not specified or the specified part does not exist). During repacking, the specified parts are added to a password-protected archive.

Accepts the path or iterator optional argument:

path is a path to the scanned attachment to be archived. If the path is not specified or the specified path is invalid, the entire message is archived.

iterator is a message part iterator, returned by the functions threats, urls, attachments, files, parts, leaf_parts, and text_parts of the MimePart table. In this case, all parts of the message provided by the returned iterator are scheduled for repacking.

If the function argument is not specified or the specified path is invalid, the entire message is archived.

Examples:

-- Schedule packing to a password-protected archive:
-- the entire message
ctx.modifier.repack()

-- Schedule all message parts that contain executables
-- a message part at the specified path (or
-- the entire message if the part does not exist)
ctx.modifier.repack('/1/2/3')

-- Schedule all message parts that contain executables
-- all parts that contain executables
ctx.modifier.repack(ctx.message.files{name='*.exe'})

-- Schedule moving to a password-protected archive
-- all attachments that are .zip archives
ctx.modifier.repack(ctx.message.attachments{name='*.zip'})

Function

repack_archive_name

Name of the archive for packing malicious or unwanted items of the message. The default value is "quarantine.zip".

String

repack_password

Password for the archive protection. If it is not specified, the password specified in the configuration file is used (the RepackPassword parameter).

String

repack_message

Arbitrary message about the reasons for repacking the message (or its parts). It is added to the final message (can be absent).

String

templates_dir

Path to a directory where a repacking template is stored. The path is relative referring to the path specified in the configuration file (the TemplatesDir parameter). The default value is "milter" (that is, templates from the milter subdirectory are used).

String

cure

Function that schedules curing an attachment.

Accepts the path or iterator optional argument:

path is a path to an attachment of the message being scanned. The cure(path) function returns true if the attachment is harmless or has been cured. If the attachment cannot be cured, the function returns false.

iterator is a message part iterator returned by the functions threats, urls, attachments, files, parts, leaf_parts, and text_parts of the MimePart table. In this case, all parts of the message returned by the iterator will be scheduled for curing. The cure(iterator) function returns true if all attachments are harmless or have been cured. If at least one attachment cannot be cured, the function returns false.

Identical to calling cure(ctx.message.leaf_parts()) if the function argument is not specified. The function returns true if all attachments are harmless or have been cured. If at least one attachment cannot be cured, the function returns false.

Function

cure_or_repack

Function that schedules the curing of an attachment. If it cannot be cured, the attachment is repacked. During the repacking, the specified parts will be added to an archive with a password.

Accepts the path or iterator optional argument:

path is the path to the scanned email attachment. The cure_or_repack(path) function returns true if the attachment is harmless or has been cured. If the attachment cannot be cured, the function returns false and schedules the attachment to be repacked.

iterator is a message part iterator returned by the functions threats, urls, attachments, files, parts, leaf_parts, and text_parts of the MimePart table. In this case, all parts of the message returned by the iterator will be scheduled for curing. The cure_or_repack(iterator) function returns true if all the attachments are harmless or have been cured. If at least one attachment cannot be cured, the function returns false and schedules all incurable attachments to be repacked.

Identical to calling cure_or_repack(ctx.message.leaf_parts()) if the function argument is not specified. The function returns true if all attachments are harmless or have been cured. If at least one attachment cannot be cured, the function returns false and schedules all incurable attachments to be repacked.

Function

Overridden metamethods: None

To access this table, you should use the modifier field of the MilterContext table. For example:

function milter_hook(ctx)

 -- Schedule adding a new header
 -- at the end of the list of headers
 ctx.modifier.add_header_field("X-Name", "Value")

 -- Schedule changing the "Subject" field value to "New value"
 ctx.modifier.change_header_field("Subject", "New value")

 -- Schedule repacking messages to a password-protected archive
 ctx.modifier.repack()

 -- Return a verdict via the milter protocol (MilterResult) table)
 -- and apply all pending changes of the message
 return {action = "accept"}
end

In the example below you can see how to fill in the MilterResult table (and its modifications fields, that is the MilterModifications tables) directly, without using the MilterModifier table:

-- Enable message sending to recipients by adding
-- the "X-Checked: True" header to them

function milter_hook(ctx)
 return {
  action = "accept",
  modifications = {
    added_fields = {
      {
        name = "X-Checked",
        value = "True"
      }
    }
  }
 }
end

The next example shows how to return the accept verdict despite the changes made in the MilterModifier table:

function milter_hook(ctx)

 …

 -- Schedule adding the message header
 ctx.modifier.add_header_field('X-Header', 'some value')

 …

 -- Force return of an empty MilterModifications table
 return {action = "accept", modifications = {}}
end

Script for Message Processing for the Spamd Interface

Requirements for the Script

The script must contain a global function that is an entry point in the message scanning module (Dr.Web MailD calls this function to process all received messages). The processing function must comply with the following call conventions:

1.Function name is spamd_report_hook;

2.The only argument is the SpamdContext table (provides access from the function to the information about the processed message);

3.The only return value is the completed SpamdReportResult table. The return value defines the response via the Spamd protocol.

An example below illustrates a function which returns the verdict to Dr.Web MailD on whether the message should be marked as spam (spam score: 200, minimum score for classifying as spam: 100, message: The message was recognized as spam; here and elsewhere the ctx argument is an instance of the SpamdContext table):

-- A trivial example

function spamd_report_hook(ctx)
 return {
  score = 200,
  threshold = 100,
  report = "The message was recognized as spam"
 }
end

Tables in Use

SpamdContext Table

The table is used as an input argument of the spamd_report_hook function. It contains the information about the message being processed (fields, structure, headers, body, information about the sender and recipients, information about the SMTP session).

Field

Description

Data type

session_id

Identifier of the client session during which messages from this client are processed.

String

message

Email message

MimeMessage Table

Overridden metamethods: None

SpamdReportResult Table

The table represents the result returned by the spamd_report_hook function. The results of scanning for spam are passed to Dr.Web MailD and sent by MTA.

Field

Description

Data type

score

Spam score assigned to the message during scanning (the $spam_score and $spam_score_int variables are expanded for Exim)

Number

threshold

Threshold point at which the message is classified as spam

Number

report

Text result of the message scanning (the $spam_report variable is expanded for Exim)

String

incident

Description of the incident in the registered event of the Mail type.

If the field is a string, the content of this string will be transmitted to the incident_text field of the Mail event and the event will be registered;

If the field is boolean and is equal to false, then the incident_text field of the Mail event will be absent and the event will not be registered;

If the field is boolean and is equal to true, then the incident_text field of the Mail event will be filled in automatically and the event will be registered, if the reported value of incident_text is not empty.

String or boolean value

Overridden metamethods: None

Script for Message Processing for the Rspamd Interface

Requirements for the Script

The script must contain a global function that is an entry point in the message scanning module (Dr.Web MailD calls this function to process all received messages). The processing function must comply with the following call conventions:

1.Function name is rspamd_hook;

2.The only argument is the RspamdContext table (provides access from the function to the information about the message being processed; see the table description below);

3.The only return value is the completed RspamdResult table (see the table description below). The return value defines the response via the RspamD protocol.

An example of the correct definition of the script (here and elsewhere the ctx argument is an instance of the RspamdContext table):

-- A trivial example

function rspamd_hook(ctx)
 return {
  score = 200,
  threshold = 100
 }
end

Example

The script below returns the recommended actions for the MTA as well as the RspamdSymbol table containing the spam score with a brief comment (signs of spam detected in the message and the number of points corresponding to each sign):

function rspamd_hook(ctx)
 return {
  score = 1080,
  threshold = 100,
  action = "REJECT:Malicious message"
  symbols = {
   {
     name = "Threat found",
     score = 1000
   },
   {
     name = "Spam score by the anti-spam library",
     score = 80
   }
  }
 }
end

Tables in Use

RspamdContext Table

The table is used as an input argument of the rspamd_hook function and contains the following information about the message being processed:

Field

Description

Data type

session_id

Identifier of the client session during which messages from this client are processed.

String

sender

Information about the message sender

RspamdSender Table

helo

The HELO/EHLO string received from the SMTP client, or nil if the string is missing/unknown (not provided by the MTA)

String

from

Email address of the sender (without angle brackets, for example: "user@domain.com") or nil if the address is absent or unknown (not provided by the MTA)

String

to

Email addresses of recipients without angle brackets

RcptTo Table

message

Email message

MimeMessage Table

spf

The SPF table used for performing an SPF check of the sender

SPF Table

Overridden metamethods: None

RspamdSender Table

The table contains the information about the message sender.

Field

Description

Data type

hostname

Name (FQDN) of the sender’s host, or nil if the name is absent or unknown (not provided by the MTA)

String

ip

IP address of the sender’s host, or nil if the IP address is absent or unknown (not provided by the MTA)

IpAddress Table

Overridden metamethods: None

RspamdResult Table

The table represents the result returned by the rspamd_hook function. Contains the message scanning report.

Field

Description

Data type

score

Spam score assigned to the message after scanning (the $spam_score and $spam_score_int variables are expanded for Exim)

Number

threshold

Minimum spam score for the message to be classified as spam

Number

action

Optional field. Action recommended for the MTA as a result of the message scanning (the $spam_action variable is expanded for Exim)

String

symbols

Optional field. Array of RspamdSymbol tables for adding points specified in the score field to the spam score

Array of RspamdSymbol tables

incident

Description of the incident in the registered event of the Mail type.

If the field is a string, the content of this string will be transmitted to the incident_text field of the Mail event and the event will be registered;

If the field is boolean and is equal to false, then the incident_text field of the Mail event will be absent and the event will not be registered;

If the field is boolean and is equal to true, then the incident_text field of the Mail event will be filled in automatically and the event will be registered, if the reported value of incident_text is not empty.

String or boolean value

Overridden metamethods: None

RspamdSymbol Table

The table describes the signs of spam detected in the message (for example, file with a threat, unwanted URL, and so on) for which points have been added to the spam score.

Field

Description

Data type

name

Name of the detected sign of spam

String

score

Number of points added to the spam score upon detection of this sign

Number

description

A brief description of the detected sign of spam (optional field)

String

Overridden metamethods: None

Script for Message Processing in SMTP Mode

Requirements for the Script

The script must contain a global function that is an entry point in the message scanning module (Dr.Web MailD calls this function to process all received messages). The processing function must comply with the following call conventions:

1.Function name is smtp_hook;

2.The only argument is the SmtpContext table (provides access from the function to the information about the processed email message);

3.The only returned value is the SmtpResult completed table. The returned value defines a verdict about the scanned message: accept, reject, change, or discard, as well as actions to be applied (possibly) to the message if it is accepted.

Below you can see an example of a correctly defined function that always returns to Dr.Web MailD the Accept verdict for all messages received for scanning via SMTP mode (here and after the ctx argument is an instance of the SmtpContext table):

function smtp_hook(ctx)
 return {action = "accept"}
end

Example

The script below returns the Accept verdict for all email messages to Dr.Web MailD and adds the "X-Checked: True" header field to all messages:

function smtp_hook(ctx)
 return {
  action = "accept",
  modifications = {
   added_fields = {
     {
      name = "X-Checked",
      value = "True"
     }
   }
  }
 }
end

In order to form the modifications table, you can use the auxiliary MilterModifier object of the SmtpContext table, for example:

function smtp_hook(ctx)
 local modifier = ctx.modifier

 -- Schedule appending a new field to the end of the header
 modifier.add_header_field("X-Name", "Value")

 -- Schedule changing the "Subject" field value to "New value"
 modifier.change_header_field("Subject", "New value")

 -- Schedule repacking messages to a password-protected archive
 modifier.repack()

 -- Apply all pending changes to the message and send it
 -- modifications do not have to be specified, the changes will be taken directly from modifier
 return { action = "accept", modifications = modifier.modifications() }
end

Tables in Use

SmtpContext Table

The table is used as an input argument of the smtp_hook function. It contains the information about the email message being processed.

Field

Description

Data type

session_id

Identifier of the client session during which messages from this client are processed

String

sender

Information about the message sender.

MilterSender Table

helo

The HELO/EHLO string received from the SMTP client, or nil if the string is missing/unknown (not provided by the MTA)

String

from

Sender’s address (without angle brackets, for example: "user@domain.com")

String

to

Email addresses of recipients without angle brackets

RcptTo Table

message

Email message

MimeMessage Table

modifier

The MilterModifier table contains all changes to be made to a message if the "accept" action is generated according to the scanning results in the MilterResult table.

MilterModifier Table

gen

The MilterGenerator table used for generating custom headers, such as X-Antivirus

MilterGenerator Table

spf

The SPF table used for performing an SPF check of the sender

SPF Table

Overridden metamethods: None

SmtpResult Table

The result returned by the smtp_hook function. Contains the descriptions of actions to be applied to the message being scanned.

Field

Description

Data type

action

String with description of the action that should be applied to the message:

"accept"—accept (i.e. allow MTA to send the message to the recipient);

"discard"—discard the message without notifying the sender;

"tempfail"—reject the message and return the SMTP 4** code to the sender.

Required field.

String

modifications

Table describing changes to be applied to the message before sending it to the recipient.

Optional field. Only used if action = "accept".

MilterModifications Table

added_recipients

List of email addresses of additional message recipients.

Optional field. Only used if action = "accept".

String array

deleted_recipients

List of email addresses to be excluded from the list of message recipients.

Optional field. Only used if action = "accept".

String array

message

String with a response text sent to the sender about the message being rejected. Is returned to the sender with the 541 code is the message is being checked synchronously.

Optional field. Only used if action = "reject".

String

incident

Description of the incident in the registered event of the Mail type.

If the field is a string, the content of this string will be transmitted to the incident_text field of the Mail event and the event will be registered;

If the field is boolean and is equal to false, then the incident_text field of the Mail event will be absent and the event will not be registered;

If the field is boolean and is equal to true, then the incident_text field of the Mail event will be filled in automatically and the event will be registered, if the reported value of incident_text is not empty.

String or boolean value

Overridden metamethods: None

Tables Describing the Message Structure

RcptTo Table

The table contains an array of email addresses of the message recipients (without angle brackets) that encountered in the RCPT TO command of the SMTP protocol as well as the following additional information:

Field

Description

Data type

search

The function that checks for the presence of at least one address corresponding to at least one of the specified templates in the address array.

Accepts one mandatory patterns argument, i.e. search patterns, i.e. one (string) or several (array of strings) regular expressions in the Perl syntax (PCRE).

Returns a Boolean value:

true—if an address corresponding to at least one template has been found;

false—if no address corresponding to at least one template has been found.

Not case-sensitive.

Function

all_match

The function that checks whether all addresses correspond to at least one of the specified templates in the address array.

Accepts one mandatory patterns argument, i.e. search patterns, i.e. one (string) or several (array of strings) regular expressions in the Perl syntax (PCRE).

Returns a Boolean value:

true—all addresses fully correspond to at least one template;

false—no address fully corresponds to any of the templates

Not case-sensitive.

Function

Overridden metamethods: None

MimeMessage Table

The table describes the email message being processed as a whole (includes the same fields as the MimePart table and some additional information)

Field

Description

Data type

dkim

DKIM signatures (see RFC 6376) of the email message

The DKIM table

raw

Email message received from the client

String

spam

Report about the results of the message scanning for spam signs

Spam Table

from

The From header value, or nil if From is absent in the message

From Table

to

The To header value, or nil if To is absent in the message

To Table

date

The Date header value, or nil if Date is absent in the message

String

message_id

The Message-ID header value, or nil if Message-ID is absent in the message

String

subject

The Subject header value, or nil if Subject is absent in the message

String

user_agent

The User-Agent header value, or nil if User-Agent is absent in the message

String

vxcube_analysis

Message analysis result from Dr.Web vxCube. The field is present only when operating in SMTP mode.

Array of VxcubeAnalysis tables

(the next fields are similar to those of the MimePart table; they describe the root MIME part).

Overridden metamethods: None

MimePart Table

The table describes a part of the email message:

Field

Description

Data type

header

Header of the message

MimeHeader Table

body

Body of the part, or nil if there are attached parts.

MimeBody Table

part

Attached (to the current message part) parts as an array of tables. If there are no attached parts, the array is empty.

Array of MimePart tables

content_disposition

Contents of the Content-Disposition header, or nil, if this header is absent in the part.

ContentDisposition Table

content_id

Contents of the Content-ID header, or nil, if this header is absent in the part.

String

content_type

Contents of the Content-Type header, or nil, if this header is absent in the part.

ContentType Table

name

Attachment name, or nil if the part is not an attachment

String

part_at

Function that receives the path argument—the path to a child part of the message. The function returns an attached message part (table MimePart) that is located at the specified path.

path is a string that looks like "/1/2/3" and expands as root_part.part[1].part[2].part[3]. Paths that look like "", "/", "//", and so on (without numbers) correspond to the message part that this function is called from (i.e. root_part). If there is no child part at the specified path, as well as when the path is incorrect, the function returns nil.

Function

threats

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all threats that are located in this part of the message and in its attached parts and that meet the specified filter condition. The iterator function does not have any arguments and returns two values:

the Virus table;

relative path to the message part that contains the detected threat.

As the filter argument, you can use:

the ThreatFilter table;

arbitrary predicate function that receives the only Virus argument and returns a Boolean value:

otrue—if the argument meets the condition (being a threat);

ofalse—if the argument does not meet the condition (being a threat)

Function

urls

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all URLs that are located in this part of the message and in its attached parts and that meet the specified filter condition. The iterator function does not have any arguments and returns two values:

the Url table;

relative path to the message part that contains the found URL.

As the filter argument, you can use:

the UrlFilter table;

arbitrary predicate function that receives the only Url argument and returns a Boolean value:

otrue—if the argument meets the condition (is unwanted);

ofalse—if the argument does not meet the condition (is not unwanted)

Function

attachments

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all attachments that are located in this part of the message and in its attached parts and that meet the specified filter condition. The iterator function does not have any arguments and returns two values:

the MimePart table;

relative path to the message part that contains the found attachment.

As the filter argument, you can use:

the PartFilter table;

arbitrary predicate function that receives the only MimePart argument and returns a Boolean value:

otrue—if the argument meets the condition;

ofalse—if the argument does not meet the condition

Function

files

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all files that are located in this part of the message and in its attached parts (including archives) and that meet the specified filter condition. The iterator function does not have any arguments and returns two values:

file name as a string;

relative path to the message part that contains the found file.

As the filter argument, you can use:

the FileFilter table;

arbitrary predicate function that receives a file name as a string and returns a Boolean value:

otrue—if the argument meets the condition;

ofalse—if the argument does not meet the condition

Function

parts

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all message parts that are located in this part of the message and in its attached parts and that meet the specified filter condition. The iterator function does not have any arguments and returns two values:

the MimePart table;

relative path to the message part.

As the filter argument, you can use:

the PartFilter table;

arbitrary predicate function that receives the MimePart table and returns a Boolean value:

otrue—if the argument meets the condition;

ofalse—if the argument does not meet the condition

Function

leaf_parts

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all leaf parts that are located in this part of the message and in its attached parts and that meet the specified filter condition. The iterator function does not have any arguments and returns two values:

the MimePart table;

relative path to the message part.

As the filter argument, you can use:

the PartFilter table;

arbitrary predicate function that receives the MimePart table and returns a Boolean value:

otrue—if the argument meets the condition;

ofalse—if the argument does not meet the condition.

Function

text_parts

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all text parts that are located in this part of the message and in its attached parts and that meet the specified filter condition. The iterator function does not have any arguments and returns two values:

the MimePart table;

relative path to the message part.

As the filter argument, you can use:

the PartFilter table;

arbitrary predicate function that receives the MimePart table and returns a Boolean value:

otrue—if the argument meets the condition;

ofalse—if the argument does not meet the condition.

Function

scan_reports

Function that takes filter as an optional argument. The function returns a single value—an iterator function. Using this iterator, it is possible to go through all reports of scanning this part of the message and its attached parts, that meet the specified filter condition.

The iterator function does not have any arguments and returns single value: the ScanReport table.

As the filter argument, you can use:

the ScanReportFilter table;

arbitrary predicate function that receives the ScanReport table and returns a Boolean value:

otrue—if the argument meets the condition;

ofalse—if the argument does not meet the condition.

Function

has_url

Function that takes filter as an optional argument. (see description of the urls function above).

Returns a Boolean value:

true—if this part of the message and its attached parts contain a URL that meets the condition filter;

false—if neither the part of the message nor the attached parts contain a URL that meets the condition filter.

Examples:

if ctx.message.has_url() then
  -- at least one URL (any) has been found in the message
end

if ctx.message.has_url{category = "adult_content"} then
  -- an adult content link has been found
end

if ctx.message.has_url{category = {"adult_content", "social_networks"}} then
  -- a link to "adult_content" or "social_networks" has been found
end

if ctx.message.has_url{category = "black_list"} then
  -- a link to a blacklisted resource been found
end

if ctx.message.has_url{host = "example.com"} then
  -- a link to "example.com" with respect to host has been found
end

if ctx.message.has_url{host_not = "*example.com"} then
  -- a link has been detected with a host that does not correspond to the "*example.com" template
end

if ctx.message.has_url(function(url) return port > 80 end) then
  -- a link with a port number more than 80 has been found
end

Function

has_threat

Function that takes filter as an optional argument (see the description of the threats function above).

Returns a Boolean value:

true—if this part of the message and its attached parts contain a threat that meets the filter condition;

false—if this part of the message or its attached parts do not contain a threat that meets the filter condition.

Examples:

if ctx.message.has_threat() then
  -- the message contains at least one threat of any category
end

if ctx.message.has_threat({category = "known_virus"}) then
  -- the message contains at least one threat of the "known_virus" category
end

if ctx.message.has_threat{category = "known_virus"} then
  -- the same
end

if ctx.message.has_threat({category = {"known_virus", "joke"}}) then
  -- the message contains at least one threat of the "known_virus" or "joke" category
end

if ctx.message.has_threat{category_not = "joke"} then
  -- the message contains a threat of any category except "joke"
end

Function

has_file

Function that takes filter as an optional argument (see the description of the files function above).

Returns a Boolean value:

true—if this part of the message and its attached parts contain a file that meets the filter condition (including files in archives);

false—if this part of the message and its attached parts do not contain a URL that meets the filter condition (including files in archives).

Examples:

if ctx.message.has_file() then
  -- at least one file has been found in the message
end

if ctx.message.has_file{name = "*.exe"} then
  -- at least one exe file has been found in the message
end

Function

has_part

Function that takes filter as an optional argument (see the description of the parts function above).

Returns a Boolean value:

true—if this part of the message and its attached parts contain a part that meets the condition filter;

false—if neither the part of the message nor the attached parts contain a part that meets the condition filter.

Function

has_scan_report

Function that takes filter as an optional argument (see the description of the scan_reports function above).

Returns a Boolean value:

true—if this part of the message and its attached parts contain a scan report that meets the condition filter;

falseif neither the part of the message nor the attached parts contain a scan report that meets the condition filter.

For usage examples, see the description of the ScanReportFilter table.

Function

search

Function that searches for text in this message section using a regular expression (PCRE). Receives a regular expression (string). Note that if you use strings in the quotation marks, the slash character must be escaped.

Returns a Boolean value:

true—if a match with the specified regular expression has been found in this section or in a child part;

false—if a match with the specified regular expression has not been found in this section or in a child part

Function

Overridden metamethods: None

From Table

A table describing the From message header. It contains the list of email addresses extracted from the header (array of strings) and the following additional information.

Field

Description

Data type

search

The function that checks for the presence of at least one address corresponding to at least one of the specified templates in the address array.

Accepts one mandatory patterns argument, i.e. search patterns, i.e. one (string) or several (array of strings) regular expressions in the Perl syntax (PCRE).

Returns a Boolean value:

true—an address that fully corresponds to at least one template has been found;

false—no address that fully corresponds to at least one template has been found.

Not case-sensitive.

Function

all_match

The function that checks whether all addresses correspond to at least one of the specified templates in the address array.

Accepts one mandatory patterns argument, i.e. search patterns, i.e. one (string) or several (array of strings) regular expressions in the Perl syntax (PCRE).

Returns a Boolean value:

true—all addresses fully correspond to at least one template;

false—no address fully corresponds to at least one template.

Not case-sensitive.

Function

Overridden metamethods:

__tostring is the function that returns the decoded header value;

__concat is the function that concatenates the decrypted value of the header and the string.

To Table

The table describes the To message header. Contains the same fields and methods as the From table.

ContentType Table

The table describes the Content-Type header of the message part.

Field

Description

Data type

type

MIME type of the message part

String

subtype

Subtype of the message part

String

param

Header parameters in the form of a table array with the following fields:

name is the name of a parameter (string);

value is the value of a parameter (string).

Table array

Overridden metamethods:

__tostring is the function that returns the decoded header value;

__concat is the function that concatenates the decrypted value of the header and the string.

ContentDisposition Table

The table describes the Content-Disposition header of the message part.

Field

Description

Data type

type

View type of the message part

String

param

Header parameters in the form of a table array with the following fields:

name is the name of a parameter (string);

value is the value of a parameter (string).

Table array

Overridden metamethods:

__tostring is the function that returns the decoded header value;

__concat is the function that concatenates the decrypted value of the header and the string.

Spam Table

Table contains the spam check report for the specified message.

Field

Description

Data type

type

Message type (spam status). Possible values:

"legit"—the message is not spam;

"spam"—the message is spam;

"virus"—a third-party heuristic analyzer has detected a virus in the message body;

"bounce"—the message contains a report on a negative delivery (DSN) sent to the sender of the original message;

"suspicious"—a suspicious message;

"pce"—a “professional” commercial (advertising) message, send by a valid subscription service;

"mce"—a commercial (advertising) message, not sent by valid subscription services, but with a way to unsubscribe;

"dce"—“dirty” commercial (advertising) message with no way of unsubscribing;

"community"—a message from a social network;

"transactional"—a transaction-related message (registration, purchase of services or goods);

"phishing"—a fraudulent message;

"scam"—a fraudulent message (a scam message).

String

score

Spam score assigned to the message

Number

normalized_score

Number of spam points score normalized in the interval [0, 1)

Number

reason

Encrypted string that contains an explanation why the email message is spam

String

version

The anti-spam library version

String

Overridden metamethods: None

Virus Table

The table describes a threat.

Field

Description

Data type

type

Threat type (according to the Doctor Web classification). Possible values:

"known_virus"—a known threat (a threat that has a description in the virus databases);

"virus_modification"—a modification of the known threat;

"unknown_virus"—an unknown threat, suspicious object;

"adware"—an advertising program;

"dialer"—a dialer program;

"joke"—a joke program;

"riskware"—a potentially dangerous program;

"hacktool"—a hacktool.

String

name

Threat type (according to the Doctor Web classification)

String

Overridden metamethods: None

Url Table

Table that describes URLs found in the message.

Field

Description

Data type

scheme

Scheme (protocol) prefix, for example, "http"

String

host

Host name or IP address, for example, "example.com"

String

port

Port number, for example, 80. If it is absent in the URL, the value is nil.

Number

path

Path to a resource, for example, "index.html". If it is absent in the URL, the value is nil.

String

raw

A raw, undecoded URL

RawUrl Table

categories

Array of categories to which the URL is placed based on the result of scanning. Possible values:

"infection_source"—an infection source;

"not_recommended"—a source that is not recommended for visiting;

"adult_content"—adult content;

"violence"—violence;

"weapons"—weapons;

"gambling"—gambling;

"drugs"—drugs;

"obscene_language"—obscene language;

"chats"—chats;

"terrorism"—terrorism;

"free_email"—free email;

"social_networks"—social networks;

"owners_notice"—websites added due to a notice from copyright owner;

"online_games"—online games;

"anonymizers"—anonymizers;

"cryptocurrency_mining_pools"—cryptocurrency mining pools;

"jobs"—job search sites;

"black_list"—black list (resources considered non-recommended by the mail server administrator).

Table of strings

legal_url

If the URL belongs to the owners_notice category, the field contains a URL to the owner’s website; otherwise, it is nil.

String

in_categories

The function that accepts the categories mandatory argument—the URL category list (a string or an array of strings). The function returns a boolean value:

true—if the URL belongs to at least one of the specified categories;

false—if the URL does not belong to any of the categories.

Function

Overridden metamethods:

__tostring—the function returns the Url content as a string (in the UTF-8 encoding);

__concat—the function concatenates the URL string value and another string.

RawUrl Table

The table contains the undecoded URL data.

Field

Description

Data type

scheme

Scheme (protocol) prefix, for example, "http". If the prefix is absent, the value is nil.

String

host

Host name or IP address, for example, "example.com". If it is absent, the value is nil.

String

port

Port number, for example, 80. If it is absent, the value is nil.

Number

path

Path to a resource, for example, "index.html". If it is absent, the value is nil.

String

Overridden metamethods:

__tostring—the function returns the RawUrl content as a string (in the UTF-8 encoding);

__concat—the function concatenates the RawUrl string value and another string.

ThreatFilter Table

The table describes a filter for threats. All fields are optional.

Field

Description

Data type

category

List of categories that the threat must match (not case-sensitive). See the list of categories in description of the type field of the Virus table.

String or table of strings

category_not

List of categories that the threat cannot match (not case-sensitive).

String or table of strings

Overridden metamethods: None

If the filter field is not specified (the value is nil), any threat matches the filter. If several filter fields are specified, then the condition is combined by a conjunction (logical AND). If the filter field is a table (list), the object must match at least one of the table (list) items.

Usage examples:

1.Write to the log all the names of the threats detected in the message:

function milter_hook(ctx)

 …

 for virus in ctx.message.threats() do
  dw.notice("threat found: " .. virus.name)
 end

 …

end

2.Write to the log the names the of threats that match the category filter, and the names of the message parts where the threats have been detected:

function milter_hook(ctx)

 …

 for v, p in ctx.message.threats({category = "known_virus"}) do
  dw.notice("found " .. v.name .. " in " .. ctx.message.part_at(p).name(p))
 end

 …

end

3.Write to the log the threat names that match the predicate function, and the names of the message parts where the threats have been detected:

function milter_hook(ctx)

 …

 local function eicar_filter(v)
  return v.name == "EICAR Test File (NOT a Virus!)"
 end

 for v, p in ctx.message.threats(eicar_filter) do
  dw.notice("found " .. v.name .. " in " .. ctx.message.part_at(p).name(p))
 end

 …

end

UrlFilter Table

The table describes the filter applied to URLs (similar to the table ThreatFilter above); all its fields are optional:

Field

Description

Data type

category

List of categories that the URL must match (not case-sensitive). See the list of categories in description of the categories field of the Url table.

String or table of strings

category_not

The list of categories that the URL may not match (not case-sensitive).

String or table of strings

text

Text that must match the URL

String or table of strings

text_not

Text that cannot match the URL

String or table of strings

host

Host (domain) that must be present in the URL

String or table of strings

host_not

Host (domain) that cannot be present in the URL

String or table of strings

Overridden metamethods: None

If the filter field is not specified (the value is nil), any threat matches the filter. If several filter fields are specified, then the condition is combined by a conjunction (logical AND). If the filter field is a table (list), the object must match at least one of the table (list) items.

Usage examples:

1.Write to the log all the URLs found in the message:

function milter_hook(ctx)

 …

 for url in ctx.message.urls() do
  dw.notice("url found: " .. url)
 end

 …

end

2.Write to the log the URLs that match the category, and names of the message parts where the URLs have been found:

function milter_hook(ctx)

 …

 for u, p in ctx.message.urls{category = "adult_content"} do
  dw.notice("found " .. u.text .. " in " .. ctx.message.part_at(p).name(p))
 end

 …

end

FileFilter Table

The table describes the filter applied to files (similar to the table ThreatFilter above). All fields are optional.

Field

Description

Data type

name

A character set or a wildcard that the name of the file is expected to match. For example: "*.exe", "eicar.txt".

Not case-sensitive.

String or table of strings

name_re

A regular expression (PCRE) that the file name is expected to match. For example: ".*\\.zip", [[.*\.zip]].

Not case-sensitive. Note that if you use strings in the quotation marks, the slash character must be escaped.

String or table of strings

name_not

A character set or a wildcard that the name of the file is not expected to match.

Not case-sensitive.

String or table of strings

name_re_not

A regular expression (PCRE) that the file name is expected to match.

Not case-sensitive. Note that if you use strings in the quotation marks, the slash character must be escaped.

String or table of strings

Overridden metamethods: None

If several filter fields are specified, the condition is combined by conjunction (logical AND). If the filter field is a table (array), the object must match at least one of the table (array) items. If the filter field is not specified (the value is nil), any file matches the filter.

Usage example:

Output to the log the names of the parts that contain files with the .exe extension.

function milter_hook(ctx)

 …

 for f, p in ctx.message.files{name = "*.exe"} do
  local where = ctx.message.part_at(p).name
  if not where or where == "" then where = p end
  dw.notice("EXE found in " .. where)
 end

 …

end

PartFilter Table

The table describes the filter for message parts (similar to the table FileFilter above); all its fields are optional.

Field

Description

Data type

name

A character set or a wild card that the name of the part is expected to match. For example: "*.exe", "eicar.txt".

Not case-sensitive.

String or table of strings

name_re

A regular expression (PCRE) that the name of the part is expected to match. For example: ".*\\.zip", [[.*\.zip]].

Not case-sensitive. Note that if you use strings in the quotation marks, the slash character must be escaped.

String or table of strings

content_type

A character set that the Content-Type value of the part is expected to match. For example: "image/*".

Not case-sensitive.

String or table of strings

content_disposition

A character set that the Content-Disposition value of the part (attachment) is expected to match. For example: "inline", "attachment".

Not case-sensitive.

String or table of strings

name_not

A character set that the name of the file is not expected to match.

Not case-sensitive.

String or table of strings

name_re_not

A regular expression (PCRE) that the name of the part is not expected to match.

Not case-sensitive. Note that if you use strings in the quotation marks, the slash character must be escaped.

String or table of strings

content_type_not

A character set that the Content-Type value of the part is not expected to match.

Not case-sensitive.

String or table of strings

content_disposition_not

A character set that the Content-Disposition value of the part is not expect to match.

Not case-sensitive.

String or table of strings

Overridden metamethods: None

If the filter field is not specified (the value is nil), any part (attachment) matches the filter. If several filter fields are specified, then the condition is combined by a conjunction (logical AND). If the filter field is a table (array), the object must match at least one of the table (array) items.

Usage examples:

1.Write to the log all the attachments and their MD5 hashes:

function milter_hook(ctx)

 …

 for a, p in ctx.message.attachments() do
  -- the attachment name may be an empty string if
  -- it is not specified in Content-Type and Content-Disposition
  local name = a.name
  if name == "" then name = "at path " .. p end
  dw.notice("Attachment: " .. name .. "; md5=" .. a.body.md5)
 end

 …

end

2.Write to the log all the attachments with an .exe extension:

function milter_hook(ctx)

 …

 for a in ctx.message.attachments{name = "*.exe"} do
  dw.notice("EXE attachment: " .. a.name)
 end

 …

end

3.Count the images in the message by their type:

function milter_hook(ctx)

 …

 local images = {}
 for part, path in ctx.message.parts{content_type = "image/*"} do
  local subtype = part.content_type.subtype
  images[subtype] = (images[subtype] or 0) + 1
 end

 for t, c in pairs(images) do
  dw.notice("Found " .. t .. " images: " .. c)
 end

 …

end

4.Write to the log the list of audio files found in the attachments:

function milter_hook(ctx)

 …

 for p, path in ctx.message.parts{
  content_type = "audio/*",
  content_disposition = {"inline", "attachment"}
 } do
    local name = p.name
    if name == "" then name = "<unnamed>" end
    dw.notice("Audio file: " .. name)
 end

 …

end

ScanReportFilter Table

The table describes the filter for reports of scanning message parts for threats (similar to the table FileFilter above). Contains the following fields (all fields are optional):

Field

Description

Data type

error

Error name to be added to the ScanReport; for instance: "password_protected", "scan_timeout".

Not case-sensitive.

String or table of strings

error_not

Error name that must not be added ScanReport; for instance: "password_protected", "scan_timeout".

Not case-sensitive.

String or table of strings

Overridden metamethods: None

If several filter fields are specified, then the condition is combined by a conjunction (logical AND). If the filter field is a table (array), the scan report must match at least one of the table (array) items. If the filter field is not specified (the value is nil), any scan report matches the filter.

Usage examples:

1.Quarantine the message in case of failed attempts to scan a password-protected archive:

function milter_hook(ctx)

 …

 if ctx.message.has_scan_report{error = 'password_protected'} then
   return
    {
      action = 'accept', deleted_recipients = ctx.to,
      added_recipients = {'quarantine@mail.domain.com'}
    }
 end

 …

end

2.Quarantine the message if the scan limits have been exceeded:

function milter_hook(ctx)

 …

 local limit_errors = {
  'archive_level_limit', 'compression_limit',
  'container_level_limit', 'mail_level_limit',
  'packer_level_limit', 'report_size_limit'
 }

 if ctx.message.has_scan_report{error = limit_errors} then
   return
    {
      action = 'accept', deleted_recipients = ctx.to,
      added_recipients = {'quarantine@mail.domain.com'}
    }
 end

 …

end

3.Reject the message in case of scan errors:

function milter_hook(ctx)

 …

 if ctx.message.has_scan_report{error = '*'} then
   return {action = 'reject'}
 end

 …

end

MimeHeader Table

The table describes the message part headers.

Field

Description

Data type

field

List of headers and their values

Array of HeaderField tables

search

Function that searches for header by regular expression (PCRE). It takes a regular expression (string) as an argument. The search is performed in all the headers of the message part. Note that if you use strings in the quotation marks, the slash character must be escaped.

Returns a Boolean value:

true—if the field.name .. ": " .. field.value.decoded string matches the specified regular expression for at least one of the headers;

false—if the field.name .. ": " .. field.value.decoded string does not match the specified regular expression for at least one of the headers

Function

value

Function that returns the value of the specified header. It takes the name of header (string) as an argument.

The function returns the HeaderFieldValue table that corresponds to the first header with the specified name found, or nil if the header has not been found.

Function

Overridden metamethods: None

HeaderField Table

The table describes the message part header.

Field

Description

Data type

name

Header name

String

value

Header value

HeaderFieldValue Table

url

List of URLs found in the header value (only for the Subject header), for all other headers—nil

Array of Url tables

Overridden metamethods: None

HeaderFieldValue Table

The table describes the value of the email message header.

Field

Description

Data type

raw

Raw (undecoded) header value

String

decoded

Decoded header value

String

Overridden metamethods:

__tostring—the function returns the HeaderFieldValue content (the decoded field value) as a string;

__concat—the function concatenates HeaderFieldValue (the decoded field value) and another string.

MimeBody Table

The table describes the message part body.

Field

Description

Data type

raw

Raw (undecoded) body of the message part

String

decoded

Decoded value of the message part body (according to the value of the Content-Transfer-Encoding and Content-Type headers)

String

text

Decoded value of the message part body in the UTF-8 according to the charset parameter of the Content-Type header.

It is present only for the parts with "Content-Type: text/*" or with an empty Content-Type. Otherwise—nil.

String

scan_report

Threat scanning report

ScanReport Table

url

URLs found in the part text as an array of Url tables.

If the text field is absent (nil), the field is empty.

Array of Url tables

search

Function that searches for text in this body using a regular expression (PCRE). Takes a regular expression (string) as an argument. Note that if you use strings in the quotation marks, the slash character must be escaped.

Returns a Boolean value:

true—if the body is a text and a match has been found;

false—if no match has not been found in the body

Function

md5

MD5 hash of the email message body.

String

sha1

SHA1 hash of the email message body.

String

sha256

SHA256 hash of the email message body.

String

vxcube_analysis

Message analysis result from Dr.Web vxCube. The field is present only when operating in SMTP mode.

Array of VxcubeAnalysis tables

Overridden metamethods: None

ScanReport Table

The table contains a report about scanning for threats.

Field

Description

Data type

object

Name of the scanned object

String

archive

Information about the container, if the scanned object is a container. If the object is not a container, it is nil

The Archive table

virus

List of detected threats

Array of Virus tables

error

In case of an error, it contains a string with a scan error. Otherwise, it is nil. Allowed values:

"path_not_absolute"—the path indicated is not absolute;

"file_not_found"—file was not found;

"file_not_regular"—the file is not a regular file;

"file_not_block_device"—it is not a block device;

"name_too_long"—the name is too long;

"no_access"—access denied;

"read_error"—reading error occurred;

"write_error"—a writing error;

"file_too_large"—the file is too large;

"file_busy"—file is being used;

"unpacking_error"—an unpacking error;

"password_protected"—the archive is password protected;

"arch_crc_error"—CRC archive error;

"arch_invalid_header"—invalid archive header;

"arch_no_memory"—not enough memory to unpack archive;

"arch_incomplete"—incomplete archive;

"can_not_be_cured"—file cannot be cured;

"packer_level_limit"—packed object nesting level limit exceeded;

"archive_level_limit"—archive nesting level limit exceeded;

"mail_level_limit"—mail file nesting level limit exceeded;

"container_level_limit"—container nesting level limit exceeded;

"compression_limit"—compression rate limit exceeded;

"report_size_limit"—report size limit exceeded;

"scan_timeout"—scan timeout limit exceeded;

"engine_crash"—scan engine failure;

"engine_hangup"—scan engine hangup;

"engine_error"—scan engine error;

"no_license"—no active license found;

"multiscan_too_late"—multiscanning error;

"curing_limit_reached"—cure attempts limit exceeded;

"non_supported_disk"—disk type is not supported;

"unexpected_error"—an unexpected error.

String

item

Reports on the scanning of embedded items if the scanned object is a container (an archive, attached MIME object, and so on)

Overridden metamethods: None

Archive Table

The table describes archives and other compound objects.

Field

Description

Data type

type

Archive type:

"archive"—archive;

"mail"—email file;

"container"—other container.

String

name

Archive name, for example, ZIP

String

Overridden metamethods: None

DKIM Table

The table describes all DKIM signatures in the message.

Field

Description

Data type

signature

List of DKIM signatures present in the message

Array of DKIMSignature tables

has_valid_signature

A function that takes filter as an argument and returns

the first found DKIM signature with the scan result that equals "pass" in the form of the DKIMSignature table;

nil appears if no signatures failing verification have been detected.

As the filter argument, you can use:

the DKIMSignatureFilter table;

arbitrary predicate function, that receives the only DKIMSignature argument and returns a Boolean value

otrue—if the argument meets the condition;

ofalse—if the argument does not meet the condition.

Function

Overridden metamethods: None

DKIMSignature Table

The table describes the properties of each DKIM signature in the message.

Field

Description

Data type

auid

The value Agent or User Identifier (AUID), obtained from the "i" tag of the DKIM signature, takes the default value into account

String

data

Text (base64) value of the signature extracted from the "b" tag of the DKIM signature

String

result

DKIM signature verification result

The DKIMResult table

sdid

The value Signing Domain Identifier (SDID), obtained from the "d" tag of the DKIM signature

String

selector

A selector, obtained from the "s" tag of the DKIM signature

String

Overridden metamethods: None

DKIMResult Table

The table describes the results of scanning for all DKIM signatures of the message.

Field

Description

Data type

type

DKIM signature verification result in the text format. It may have the following values:

pass indicates that signature verification has been successful;

fail indicates that signature verification has failed (i.e. it does not correspond with the hash of the email body or the signature could not be verified);

neutral is a syntax error of the DKIM signature;

temperror indicates a failed attempt at obtaining the domain key (DNS error);

permerror indicates other types of errors (signature format, key format, inconsistencies between the key and the signature, and so on).

String

comment

Comment on the scan result (can be used as a comment in Authentication-Results)

String

key_size

The key size used during the scan is either nil, or neutral if the attempt to obtain the key or the scan result has failed

Number

Overridden metamethods: None

DKIMSignatureFilter Table

The table describes the filter for DKIM message signatures (similar to the FileFilter table above). All fields are optional.

Field

Description

Data type

domain

A character set or a wildcard that the domain from the SDID field of the DKIM signature is expected to match

String or table of strings

domain_not

A character set or a wildcard that the domain from the SDID field of the DKIM signature is not expected to match

String or table of strings

Overridden metamethods: None

If the filter field is not specified (i.e. it contains the nil value), any DKIM signature of this message matches the filter. If several filter fields are specified, then the conditions are combined by conjunction (logical “AND”). If the filter field type is a table (array), then the filtered object must match at least one of the table (array) elements.

SPF Table

Contains all data necessary for checking SPF.

Field

Description

Data type

helo

The result of HELO check

from

The result of MAIL FROM check

check()

An auxiliary function; first performs the MAIL FROM check and then (if no verdict has been received)—the HELO check. The function returns the result of the check as a string that may have the same values as that of the status field in the SPFResult table.

The function is designed for transmitting the results to OpenDMARC via the generated Authentication-Results header

Function

Overridden metamethods: None

SPFResult Table

Contains the result of checking SPF.

Field

Description

Data type

status

The result of the check represented as a string. It can have one of the following values (in accordance with RFC 7208): none, neutral, pass, fail, softfail, temperror, permerror

String

explanation

The explanation of the result in case the fail response has been received.

String or nil if no explanation was received or it was impossible to get it

Overridden metamethods: None

VxcubeAnalysis Table

The table contains the result of analyzing the object in Dr.Web vxCube.

Field

Description

Data type

filename

The name of the analyzed attachment

String

id

Analysis ID

String

sample_id

Analyzed file ID

String

format_name

Analyzed file format

String

tasks

Analysis result

Array of VxcubeTask tables

max_maliciousness

Maximum value of the maliciousness field from the array of VxcubeTask tables. Can be a float from 0 to 100, or nil if there was an error.

Number or nil

Overridden metamethods: None

VxcubeTask Table

Contains the result of object analysis performed on a separate platform in Dr.Web vxCube. The table structure is roughly equivalent to the TaskFinished object received using the Dr.Web vxCube API.

Field

Description

Data type

id

Task ID

String

status

Analysis status, such as "failed" or "finished"

String

platform_code

Code of the platform that the analysis was performed on. nil if there was an error.

String or nil

maliciousness

Maliciousness of the object. Can be a float from 0 to 100, or nil if there was an error.

Number or nil

verdict

Overall file maliciousness score corresponding to one of the three categories in the format of <category><degree>, where <category> is one of the following values: "neutral", "suspicious", "malware"; <degree> is an integer that represents the maliciousness degree (1 to 3). Examples of the verdict value: "malware1", "suspicious3".

String

Overridden metamethods: None

Available Auxiliary Modules

For interconnection with Dr.Web for UNIX Mail Servers in program space in Lua the following specific modules can be imported.

Name of the module

Function

Provides functions to record messages from the Lua program to the log of the Dr.Web for UNIX Mail Servers component which has launched the Lua program and the means of asynchronous execution of Lua procedures

Provides tools to request data from external sources by calling the Dr.Web LookupD module

Provides tools to check if the hosts’ addresses are in the DNSxL black lists

Provides an interface to match strings and regular expressions

Provides an interface to run external applications (processes)

Provides a table with the Dr.Web MailD configuration parameter values

Provides functions to store data between MailD runs

Contents of the drweb Module

1.Functions

The module provides a set of functions.

Storing messages from the Lua program in the Dr.Web for UNIX Mail Servers component log:

log(<level>, <message>) writes the <message> string to the Dr.Web for UNIX Mail Servers log on the <level> level (the required level is defined using one of the following values (string): debug, info, notice, warning, error);

debug(<message>) writes the <message> string to the Dr.Web for UNIX Mail Servers log at the debug level;

info(<message>) writes the <message> string to the Dr.Web for UNIX Mail Servers log at the info level;

notice(<message>) writes the <message> string to the Dr.Web for UNIX Mail Servers log at the notice level;

warning(<message>) writes the <message> string to the Dr.Web for UNIX Mail Servers log at the warning level;

error(<message>) writes the <message> string to the Dr.Web for UNIX Mail Servers log at the error level.

Managing the synchronization of Lua procedures:

sleep(<sec.>) pauses the execution of a Lua procedure instance for a specified number of seconds;

async(<Lua function>[, <argument list>]) launches the specified function asynchronously and passes to it the specified argument list. The async function call completes immediately, and the return value (the Future table) allows you to obtain the result of the <Lua function>.

Storing the information about an IP address in the form of the IpAddress table:

ip(<address>) indicates an IP address sent as the <address> string in the form of the IpAddress table. Either IPv4 or IPv6 addresses can be used.

Uploading external data from a text file:

load_set(<file path>) generates a table with the true values from the contents of a specified text file; strings read from the file are used as keys. Empty strings and strings with white spaces will be ignored;

load_array(<path to file>) generates a string array from the contents of a specified text file. Empty strings and strings consisting only of white spaces are ignored.

2.Tables

The Future table describes the pending result of performing a function using the async function.

Field

Description

Data type

wait

A function that returns the result of the function started using the async function. If the function has not completed its execution yet, it waits for the completion and returns its result. If the function is completed before wait is called, the result is returned immediately. If the started function fails, the wait call generates the same error.

Function

Overridden metamethods: None

The IpAddress table describes an IP address.

Field

Description

Data type

belongs

Function checks an IP address stored in the IpAddress table for belonging to specified subnets (IP address ranges).

Accepts a single argument—a string that looks like: "<IP address>" or "<IP address>/<mask>", where <IP address>—a host address or a network address (for example, 127.0.0.1), and <mask>—a subnetwork mask (can be specified as an IP address, for example, 255.0.0.0, or in the numerical form, for example, 8).

Returns a Boolean value:

true indicates that the address matches at least one of the specified IP addresses or belongs to at least one of the specified subnets (the range of IP addresses);

false—if the address does not match any one of the specified addresses or does not belong to any of the specified subnets

Function

Overridden metamethods:

__tostring is a function that transforms IpAddress into a string, for example, 127.0.0.1 (IPv4) or ::1 (IPv6);

__concat is a function that joins IpAddress to a string;

__eq is a function that checks the equality of two IpAddress;

__band is a function that allows to apply a mask, for example: dw.ip('192.168.1.2') & dw.ip('255.255.254.0')

3.Examples

Logging messages generated by a procedure initiated asynchronously:

local dw = require "drweb"

-- This function returns a string received as an argument
-- after the delay of two seconds
function out_msg(message)
 dw.sleep(2)
 return message
end

-- "Main" function
function intercept(ctx)
 -- Output the string at the notice level to the log of Dr.Web for UNIX Mail Servers
 dw.notice("Intercept function started.")

 -- Run two instances of the out_msg function asynchronously
 local f1 = dw.async(out_msg, "Hello,")
 local f2 = dw.async(out_msg, " world!")

 -- Wait for the completion of the instances of the function
 -- out_msg and log their results
 -- Dr.Web for UNIX Mail Servers at the debug level
 dw.log("debug", f1.wait() .. f2.wait())
end

Creating a scheduled procedure:

local dw = require "drweb"

-- Store the Future table as a futurе global variable to
-- prevent its removal by the garbage collector
future = dw.async(function()
   while true do
     -- Log the following message each day
     dw.sleep(60 * 60 * 24)
     dw.notice("A brand new day began")
   end
end)

Transform the string to the IP address:

local dw = require "drweb"

local ipv4 = dw.ip("127.0.0.1")
local ipv6 = dw.ip("::1")
local mapped = dw.ip("::ffff:127.0.0.1")

 

Contents of the drweb.lookup Module

1.Functions

The module provides the following functions:

lookup(<request>, <parameters>) requests data from an external storage available via the Dr.Web LookupD module. The <request> argument must correspond to a section in the Dr.Web LookupD settings (the <type>@<tag>string). The <parameters> argument is optional and describes substitutions to be used to generate a request. The following automatically permitted markers can be used:

$u, $U is automatically replaced with user—the user name sent by the client component;

$d, $D is automatically replaced with domain—the domain name sent by the client component.

Arguments are set as a table, which keys and values must be strings. The function returns an array of strings that are results of the request;

check(<checked string>, <request>, <parameters>) returns true if <checked string> is found in the external repository, available via the Dr.Web LookupD module. The <request> and <parameters> arguments are equivalent to the arguments of the lookup function (see above). The <checked string> argument must be a string or a table with a __tostring metamethod (i.e. that can be transformed into a string).

2.Examples

Log a list of users retrieved from the LookupD.LDAP.users data source:

local dw = require "drweb"
local dwl = require "drweb.lookup"

-- "Main" function
function intercept(ctx)
 -- Store the string in the Dr.Web for UNIX Mail Servers log at the notice level
 dw.notice("Intercept function started.")

 -- Store in the Dr.Web for UNIX Mail Servers log results of requesting
 -- the 'ldap@users' data source
 for _, s in ipairs(dwl.lookup("ldap@users", {user="username"})) do
   dw.notice("Result of request to 'ldap@users': " .. s)
 end

end

 

Contents of the drweb.dnsxl Module

1.Functions

The module provides the following functions:

ip(<IP address>, <DNSxL server>) requests DNS records of an A type from the DNSxL server <DNSxL server> that correspond to the specified IP address <IP address>.

If an IP address that is being checked is registered in the lists of the DNSxL server, then the result is a list of fictitious IP addresses. At that, each of the returned fictitious IP addresses can contain the reason for which the checked <IP address> is listed in the lists of this server (usually the reason type is determined by the value of the last octet of a returned fictitious IP address). If the DNSxL server does not contain the DNS records of an A type, for the IP address <IP address>, the function returns nil.

url(<URL>, <SURBL server>) requests DNS records of an A type from the <SURBL server> server that correspond to the <URL> domain part (HTTP redirecting is not processed).

If the domain that is being checked, retrieved from <URL>, is registered in server lists of the SURBL server, then the result is a list of fictitious IP addresses. At that, each of the returned fictitious IP addresses can contain the reason for which the checked domain is listed in the lists of this server (usually the reason type is determined by the value of the last octet of a returned fictitious IP address). If the SURBL server does not contain the DNS records of an A type for domains from <URL>, the function returns nil.

Function arguments are strings or objects casted to strings (for example, as <IP address >, the IpAddress table can be used, and as <URL>—the Url table). IP addresses are returned as an array of the IpAddress tables.

2.Tables

The IpAddress table describes the IP address. You can find the description of the table above.

3.Examples

Output the results of the IP address scanning by the DNSxL server to the log:

local dw = require "drweb"
local dwxl = require "drweb.dnsxl"

-- "Main" function
function intercept(ctx)
 -- Output of the string at the NOTICE level to the Dr.Web for UNIX Mail Servers log
 dw.notice("Intercept function started.")

 -- Output to the Dr.Web for UNIX Mail Servers log the scanning results of
 -- 10.20.30.40 IP address from the DNSxL server black list
 -- dnsxl.server1.org
 local records = dwxl.ip("10.20.30.40", "dnsxl.server1.org")
 if records then
   for _, ip in ipairs(records) do
    dw.notice("DNSxL A record for 10.20.30.40: " .. ip)
   end
 end

end

 

Contents of the drweb.regex Module

1.Functions

The module provides the following functions:

search(<<template>>, <<text>>[, <<flags>>]) returns true if the <<text>> string contains a substring that matches the <<template>> regular expression. The optional <<flags>> parameter (integer) is a set of flags affecting the function behavior connected with the logical OR.

match(<<template>>, <<text>>[, <<flags>>])—the same as search except that the <<template>> regular expression must match the entire <<text>> string, not only its substring.

2.Available flags

ignore_case ignores text case.

3.Examples

local rx = require "drweb.regex"

rx.search("te.?t", "some TexT") -- false
rx.search("te.?t", "some TexT", rx.ignore_case) -- true

rx.match("some.+", "some TexT") -- true

 

Contents of the drweb.subprocess Module

1.Functions

The module provides the function:

run(<<parameters >>) runs the specified process (application) in the synchronous mode (the function gives control back only after the process exit). The <parameters> argument is a table that contains an executable path to the file, all arguments passing to the application at its launch (array argv), and optional parameters associated with the application input/output streams (stdin, stdout, stderr) that specify the working (current) directory of the application and environment variables.

The function result is a table that contains results of the process operation after its exit: exit code or signal number, at which the process was terminated. Moreover, returned table can contain fields with data that are read from the stdout and stderr output streams if they are specified in the table of the process running parameters.

2.Tables

Table of input run parameters

Field

Description

Data type

(without name)

File path and application run arguments (array argv).

Required field. Field is repeated the number of the command-line arguments, the first value corresponds to argv[0], i.e. executable path.

String

stdin

Text that the application (process) will receive from the input stream (stdin) after its run. Optional field (if not specified, stdin will not receive anything).

String

stdout

Name of the field of the returned table. The field will receive the text that the process output to the stdout stream. Optional field (if not specified, the output will not be saved in stdout).

String

stderr

Name of the field of the returned table. The field will receive the text that the process output to the stderr stream. Optional field (if not specified, the output will not be saved in stderr).

String

env

Table which fields are the environment variables that will be sent to the process environment. The environment variables are specified as pairs "<variable name>"="<value>". Optional field (if not specified, the environment variables are not set).

Table

workdir

Working (current) directory for the running process. Optional field (if not specified, the working directory is not set).

String

Table of run results (the return value of the run function)

Field

Description

Data type

exit_status

Return code, with which the process exited successfully. Otherwise, it is nil.

Number

exit_signal

Signal number at which the process was terminated. Otherwise, it is nil.

Number

(value of the stdout field of the table of input parameters)

Data read from the stdout stream of an exited process. Field is available only if the stdout field is specified in the table of input parameters.

String

(value of the stderr field of the table of input parameters)

Data read from the stderr stream of an exited process. Field is available only if the stderr field is specified in the table of input parameters.

String

3.Examples

To run the cat utility without arguments, to pass the 'some data' text to its input stream, and to pass the command output results to the stdout_field field of the returning table:

local sp = require 'drweb.subprocess'

local cat_result = sp.run({
   '/bin/cat',
   stdin = 'some data',
   stdout = 'stdout_field',
})

Table of the result stored in the cat_result variable contains the following fields:

Field

Valueexit_status

exit_status

0

exit_signal

nil

stdout_field

'some data'

To run the sh command (command interpreter) with the and env | grep TESTVAR 1>&2 parameters (command run by the interpreter), to add the TESTVAR variable with the VALUE value to the environment, and to pass the command stderr output result to the stderr_field field of the returning table:

local sp = require 'drweb.subprocess'

local env_result = sp.run({
   '/bin/sh', '-c', 'env | grep TESTVAR 1>&2',
   env = { TESTVAR = 'VALUE' },
   stderr = 'stderr_field'
})

Table of the result stored in the env_result variable contains the following fields:

Field

Valueexit_status

exit_status

0

exit_signal

nil

stderr_field

'TESTVAR=VALUE\n'

To run the pwd command and to set the working directory to the system root directory, and to pass the command stdout output results to the stdout_field field of the returning table:

local sp = require 'drweb.subprocess'

local pwd_result = sp.run{
   '/bin/pwd',
   workdir = '/',
   stdout = 'stdout_field'
}

Table of the result stored in the pwd_result variable includes the following fields:

Field

Valueexit_status

exit_status

0

exit_signal

nil

stdout_field

'/\n'

To run the kill command in bash and to pass the SIGKILL signal to the interpreter:

local sp = require 'drweb.subprocess'

local kill_result = sp.run{'/bin/bash', '-c', 'kill -9 $$'}

Table of the result stored in the kill_result variable includes the following fields:

Field

Valueexit_status

exit_status

nil

exit_signal

9

To run the process asynchronously, run the run function inside the async function call (see above).

Contents of the drweb.config Module

1.Functions

The module does not provide any functions.

2.Available tables

The module provides the table MailDConfig with the following fields:

Field

Description

Data type

version

Dr.Web MailD version

String

Overridden metamethods: None

The MailDConfig table is provided by the module as the maild field.

3.Examples

Output to log the current version of the Dr.Web MailD component:

local dw  = require 'drweb'
local cfg = require 'drweb.config'

-- "Main" function
function milter_hook(ctx)

 -- Output of the string at the NOTICE level to the Dr.Web for UNIX Mail Servers log
 dw.notice(cfg.maild.version)

end

 

Contents of the drweb.store Module

1.Functions

The module provides the following functions:

exists (<name>, <key>) checks if there is an entry with the specified key in the selected repository. Takes two arguments: <name> (string)—the name of the repository; <key> (string)—the key of the entry. Returns true if there is an entry, otherwise returns false;

get (<name>, <key>) obtains the value of the entry with the specified key from the selected storage. Takes two arguments: <name> (string)—the name of the repository; <key> (string)—the key of the entry. Returns a pair with parameters: value, ctime; or nil if there is no entry. The value parameter (string)—the value of the entry with the specified key; ctime (integer)—record modification timestamp;

put (<name>, <key>, <value>) adds an entry with the specified key to the selected storage. It takes three arguments: <name> (string)—the name of the repository; <key> (string)—the key of the entry, <value> (string)—the value of the entry;

remove (<name>, <key>) removes the entry with the specified key from the selected storage. Takes two arguments: <name> (string)—the name of the repository; <key> (string)—the key of the entry;

count (<name>) returns the number of records in the selected storage. Takes one argument: <name> (string)—the name of the repository. Returns an integer;

drop (<name>, <ctime>) removes all entries that were modified before the specified timestamp from the selected storage. Takes two arguments: <name> (string)—the name of the repository; ctime (integer)—timestamp of the entry modification.

2.Examples

Creating a white list for anti-spam scans:local store = require "drweb.store"

local store = require "drweb.store"

local antispam_whitelist = "antispam_whitelist"
local intra_domain_mask = ".*@test%.test$"

-- "Main" function
function milter_hook(ctx)

 -- Add all recipients of outgoing messages
 -- to the anti-spam white list
 if ctx.from:match(intra_domain_mask) then
   for _, to in ipairs(ctx.to) do
    store.put(antispam_whitelist, to, "")
   end
   return {action="accept"}
 end

 if not store.exists(antispam_whitelist, ctx.from) then
   if ctx.message.spam.score > 300 then
    return {action="reject"}
   end
 end

 return {action="accept"}

end

Temporary addition to the white list:

local store = require "drweb.store"

local antispam_whitelist = "antispam_whitelist"
local antispam_whitelist_timeout = 604800 -- 1 week
local intra_domain_mask = ".*@test%.test$"

-- "Main" function
function milter_hook(ctx)

 -- Add all recipients of outgoing messages
 -- to the anti-spam white list
 if ctx.from:match(intra_domain_mask) then
   for _, to in ipairs(ctx.to) do
    store.put(antispam_whitelist, to, "")
   end
   return {action="accept"}
 end

 local _, ctime = store.get(antispam_whitelist, ctx.from)
 -- Is on the list and was added within the week
 local in_whitelist = ctime and os.time() + ctime < antispam_whitelist_timeout

 if not in_whitelist and ctime then
   store.remove(antispam_whitelist, ctx.from)
 end

 -- You can also update a non-expired entry:
 -- if in_whitelist then
 --  store.put(antispam_whitelist, ctx.from, "")
 -- end

 if not in_whitelist then
   if ctx.message.spam.score > 300 then
    return {action="reject"}
   end
 end

 return {action="accept"}

end