HSL Mail Content
These functions are easily distinguished from core functions functions as their names begin with uppercase letters. They are only available in the Mail Content context, in other words the mailscand process. It is executed once for each recipient of a mail message, once the SMTP-command DATA (with contents) has finished. The results of the execution is communicated with the sending server, and it is thus possible to reject messages (as opposed to receiving them) using the Reject() function. If a scripting error occurs Defer() will be called.
Pre-defined variables
These are the read-only pre-defined SMTP-derived variables that mailscand makes available for each message that is scanned.
| Name | Example | Description |
|---|---|---|
| $sender | "test@example.com" | Mail address of sender |
| $senderdomain | "example.com" | Domain part of sender address |
| $recipient | "user@example.com" | Mail address of recipient |
| $recipientdomain | "example.com" | Domain part of recipient address |
| $senderip | "10.0.0.1" | IP address of the sending server |
| $senderhelo | "mail.example.com" | HELO message of sender |
| $saslauthed | false | Value of true or false if user is authenticated |
| $saslusername | "mailuser" | Current authenticated user, or empty |
| $directprocessing | false | Boolean value if current mail is running in Direct Processing mode. |
| $serverid | "mailserver:1" | ID of the incoming listener. |
| $transportid | "mailtransport:1" | ID of the outgoing transport to be used listener. |
| $messageid | "18c190a3-93fb-47d7-bd4c-c83a4c421832" | Current ID of scanned message |
Message functions
These functions relate to the mail message itself.
GetID()
→ String
Returns the internal message ID of the message. This can be useful for debugging, since the ID is always shown in the mail log.
GetDSN()
→ Associative Array of Strings
Returns an multi-dimensional associative array containing interpretated DSN (delivery status notification) information if the message is in fact a DSN. For normal messages, this functions returns false.
The function is useful for detecting DSN-spam; where the spammer sends a message to a non-existing account at another legitimate mail server, "from" of your addresses. The legitimate mail server like to notify you, the "sender", and a DSN containing the spam is sent to you. By using GetDSN() to check if your SMTP server is the first "route" for the original message, these spam messages are effectively detected. Also, if the legitimate server sending you these DSN:s had SPF checking, they would have detected that it was not you sending the message in the first place.
For a DSN message, the following could have been returned:
$tmp = GetDSN();
echo $tmp["global"]["reporting-mta"];
echo $tmp["global"]["received-from-mta"];
echo $tmp["recipients"][0]["final-recipient"];
echo $tmp["recipients"][0]["action"];
echo $tmp["recipients"][0]["status"];
echo $tmp["recipients"][0]["remote-mta"];
echo $tmp["route"][0];
echo $tmp["route"][1];
where "global" contains the message-specific informations, such as from whom the original message was received (Received-from-MTA). The "recipients" fields contains information per recipient, such as to which server the delivery was failed (Remote-MTA). The "route" fields contains the same server route trace back as the GetRoute() commands provides, but for the originally sent message, not the DSN itself. Not all of these fields has to be present.
GetDSNHeader($header, $raw = false)
→ String
Returns the value of the header in the attached message-part in a DSN message. If it does not exist an empty string is returned. If there is multiple occurrences, the first one will be returned. This function will convert the header to UTF-8 using the appropriate encoding and character set used (quoted-printable or base64). If $raw is true the header will not be decoded as expected.
// This is probably not a good real-world script, but it serves as an example.
if (is_array(GetDNS()))
{
if (GetDSNHeader("X-Outbound-Tag") != "I tag all my outbound message with this text")
{
Reject("I didn't send this DSN (Backscatter Protection)");
}
}
SetSender($sender)
→ Number
This functions should always return true (1). It changes the sender of a message, which may be useful when operating for outbound delivery.
SetRecipient($recipient)
→ Number
This functions should always return true (1). It changes the recipient of a message. Can be used with SetMailTransport().
SetMailTransport($transportid)
→ Number
This functions should always return true (1). It changes the transport of a message.
Example
if ($saslauthed)
SetMailTransport("mailtransport:2");
else
SetMailTransport("mailtransport:1");
Message header functions
These function lets work with headers in a mail message. Since there is no connection between the original message and modified headers, multiple calls to these function may or may not give the expected result if you are not aware of this. A given example of this behavior can be shown like this;
The most simple example of this behavior.
SetHeader("X-Test", "Hello World");
echo GetHeader("X-Test"); // Will not return "Hello World", instead it will return the value of the original X-Test header.
If the X-Test header does not exist. It will be added to the message, since the X-Test header did not exist in the original message it will not be removed.
SetHeader("X-Test", "Hello World");
DelHeader("X-Test"); // Will NOT undo the previous SetHeader; the header will be set.
If the X-Test header exists in the original message. It will be removed.
SetHeader("X-Test", "Hello World");
DelHeader("X-Test"); // The header will be removed since it existed in the original message.
GetHeader($header, $wordencode = true)
→ String
Returns the value of a header. If it does not exist an empty string is returned. If there is multiple occurrences, the first one will be returned. This function will convert the header to UTF-8 using the appropriate encoding and character set used (quoted-printable or base64). If $raw is true the header will not be decoded as expected.
// Returns the message subject
echo GetHeader("Subject"); // Returns eg. "Hi"
// Return raw message subject, (this is good for charset-matching etc.)
echo GetHeader("Subject", false); // Returns eg. "=?utf-8?b?SGk=?="
SetHeader($header, $value)
→ Number
Adds or replaces a header if it exists, or if the header does not exist in the original mail; a new header will be added for each SetHeader call. If the header exist all occurrences will be replaced. If the header is added it returns 0, if its replaced it will return the number of occurrences replaced.
// Sets the header to "Hello"
SetHeader("Subject", "Hello");
PrependHeader($header, $value)
→ Number
Prepends a message header, but only if it exists. In order to set the header also if its missing use a SetHeader/GetHeader combination. If there is multiple occurrences, then they all will be replaced with the first header value in a combination with the given value. If the header exists it will return the number of occurrences replaced.
// Prepends "[SPAM] " to Subject header
PrependHeader("Subject", "[SPAM] ");
AppendHeader($header, $value)
→ Number
Appends a message header, but only if it exists. In order to set the header also if its missing use a SetHeader/GetHeader combination. If there is multiple occurrences, then they all will be replaced with the first header value in a combination with the given value. If the header exists it will return the number of occurrences replaced.
// Appends " [SPAM]" to Subject header
AppendHeader("Subject", " [SPAM]");
DelHeader($header)
→ Number
Deletes all occurrences of a message header. If the header exists it will return the number of occurrences removed.
// Delete the header X-Tag
DelHeader("X-Tag");
GetRoute($extended)
→ Array of Strings or Array of Array
Returns an array containing the servers which the mail message has passed through, starting with the first server. Note that this order is reversed compared to the order of the "Received" headers. It might be handy to reverse this array using array_reverse.
// Print the route information to the mail log
echo GetRoute();
// Print extended information (more information)
echo GetRoute(true);
Attachment functions
Each attachment in a mail message is assigned an unique identification this identifier is impossible to foresee therefore there is a set of function to return these IDs.
GetAttachmentsByName($filename)
→ Array of String
Returns an array of attachment IDs. The $filename parameter may be a string or an array of regular expressions. The matching is partial and does only include mime parts which do have a Content-Disposition/filename or Content-Type/name attribute.
// Returns all attachments having a filename (see above)
$attachments = GetAttachmentsByName("");
// Returns all attachments having an extension of .exe
$attachments = GetAttachmentsByName("\\.exe$");
// Returns all attachments having an extension of .exe or .zip
$attachments = GetAttachmentsByName("\\.(exe|zip)$");
GetAttachmentsByType($filetype)
→ Array of String
Returns an array of attachment IDs. The $filename parameter may be a string or an array of regular expressions. The matching is partial and does only include MIME parts which do have a Content-Type attribute.
// Returns atatchments of type application/* and image/*
$attachments = GetAttachmentsByName(array("application/.*","image/.*"));
GetAttachmentName($attachmentid)
→ Array of String
Returns the filename of an attachment ID
// Returns the attachment name of an attachment ID
echo GetAttachmentName($attachmentid);
GetAttachmentType($attachmentid)
→ Array of String
Returns the filename of an attachment ID
// Returns the attachment type of an attachment ID
echo GetAttachmentType($attachmentid);
GetAttachmentSize($attachmentid)
→ Array of Number
Returns the filename of an attachment ID
// Returns the attachment size of an attachment ID...
echo GetAttachmentSize($attachmentid);
RemoveAttachments($attachmentids)
→ Number
Removed all attachments for IDs
RemoveAttachments($attachmentids);
GuessAttachmentType($attachmentids)
→ Array of Strings
Guesses file attachment type, so it should not be considered an exact science.
// Block audio/mpeg attachments (mp3, and such)
$blockedContentType = array(
"audio/mpeg"
);
// Only check files with a filename
foreach(GetAttachmentsByName(".+") as $file) {
// Get the file name, which is used in reject message
$name = GetAttachmentName($file);
// First, look at what the mail agent reported
$types = GetAttachmentType($file);
foreach ($types as $type)
if (in_array($type, $blockedContentType))
Reject("Message contains forbidden attachments ("+$name[0]+")");
// Then, do the guessing
$types = GuessAttachmentType($file);
foreach ($types as $type)
if (in_array($type, $blockedContentType))
Reject("Message contains forbidden attachments ("+$name[0]+")");
}
Action functions
These functions perform actions on the mail message itself.
Deliver($recipient, $transportid)
This is a final action, placing the message in the outgoing delivery queue. The execution of the script will terminate after a final action.
DirectDeliver($recipient, $transportid)
This is a final action, delivering the message inline (by mailscand). Before using this feature, read about the implementation considerations. The execution of the script will terminate after a final action.
DeliverAsSpam($recipient, $transportid)
This is a final action, placing the message in the outgoing delivery queue, and also recording it as spam in the mail gateway statistics. This command is identical to Deliver(), except from how it is reported to the reporting and statistics sub-systems. The execution of the script will terminate after a final action.
Reject($reason)
Do not use this function without Direct Processing activated, if you don't fully understand the consequences of DSN reporting for all rejected messages.
This is a final action, which rejects the message in the SMTP session (by an error), or DSN if direct processing is disabled. Read more about Reject() and Delete() in the chapter about direct processing. The execution of the script will terminate after a final action.
$reason is a optional parameter.
if (count($viruslist=ScanKAV()))
{
Reject("Virus detected: " + implode(",", $viruslist));
}
Defer($reason)
If this function is used without Direct Processing it will bounce messages in the inbound queue.
This is a final action, which rejects the message in the SMTP session (by an temporary error). Read more about Reject() and Delete() in the chapter about direct processing. The execution of the script will terminate after a final action.
$reason is a optional parameter.
Use this function wisely, since it shouldn't be used.
Defer("Try again later");
Delete()
This is a final action, removing the message. It will not be delivered, and unless any other action has copied the message, it will be lost without any other notification than an entry in the logging facilities. The execution of the script will terminate after a final action.
Quarantine($quarantinepolicy, $recipient, $transportid, $finalaction = true)
This is a final action, moving the message to the quarantine defined by the $quarantinepolicy. The quarantine policy decides whether the message is to be placed in a shared (by an administrator managed) or a per-user quarantine. If the message is the first one to be placed in a specific quarantine box, an auto-generated message with account information will be sent to the owner of the newly created quarantine. The quarantine policy also defines the retention policy; for how long messages will be held in the quarantine. The execution of the script will terminate after a final action, if not $finalaction is set to false.
if (ScanSA() < 5) Deliver();
else if (ScanSA() < 10) Quarantine("mailquarantine:1");
else Delete();
WrapMessage($subject, $body)
→ Number
Wrap the original mail message, as an attachment in a new mail message, with subject $subject containing the message body text $body.
WrapMessage can be executed multiple times on a message. The first time, a new message is created, the subject is set to $subject, the body is set to $body, and the original message is made an attachment. The next, and following times, WrapMessage is executed, the $body will be appended to the existing body. It is only the first WrapMessage that can set the subject (and create the attachment).
// if both Kaspersky and Clam AV detects a virus, both will be mentioned in the message
if (ScanKAV()) WrapMessage("Virus Detected", "Kaspersky detected a virus in the attached message.");
if (ScanCLAM()) WrapMessage("Virus Detected", "Clam AV detected a virus in the attached message.");
WrapMessageAddHeader($header, $value)
→ Number
This function will add a custom header to the new message which was/is to be created by WrapMessage.
WrapMessageAddHeader("X-Header", "Wrapped-By-SPG");
WrapMessage("Hi", "I wrapped a message for you!");
CopyMail($recipient, $transportid)
→ Number
Create a copy of the original mail message and send it. In the SMTP transaction, $recipient is used as "RCPT TO" address, and the SMTP server is defined by the $transport. If no $transport is specified it will try to resolve it as of the message arrived on the same transport, but to the $recipient domain. If recommended to always specify a transport to prevent misconfiguration.
if ($spamscore > 80) {
CopyMail("archive@example.org", "mailtransport:1");
Delete();
}
Scan functions
These functions perform different scanning on the mail message. You can test the Kaspersky and CLAM AntiVirus by sending a [eicar test virus] file.
ScanRPD()
→ Number
Scan the message using Commtouch RPD. This function requires license, if no license is available it will return a value of 0 (no spam found). It returns the score returned by the RPD engine.
| Result | Description |
|---|---|
| 0 | Unknown |
| 10 | Suspect |
| 50 | Bulk |
| 100 | Spam |
This modules affects the $result and $spamscore variables.
PrependHeader("Subject", "[SPG:"+ScanRPD()+"] ");
ScanSA()
→ Number
This function returns the result from the Pattern Analysis (SpamAssassin). The value returned from ScanSA() may be in the range of infinite numbers; positive as well as negative. Most people accept a value of 5 or more to be considered spam. It is possible to add custom rules and adjust the scores of existing rules by typing them into the field "Custom Rules" on the Mail Gateway section Content Flows, at the Advanced Options tab.
It is possible to find out which rules that contributed to the score, by looking at the mail log (via Web Administration, Syslog or CLI), or browsing the history (the History tab of the Activity section).
| Result | Description |
|---|---|
| < 5 | Not likely spam |
| >= 5 | Possibly spam |
This module affects the $result variable.
if (ScanSA() >= 5)
{
PrependHeader("Subject", "[*** SPAM ***] ");
}
ScanSARules()
→ Array of String
This function pattern names matches by the Pattern Analysis (SpamAssassin). It is possible to define your own SpamAssassin rules for regular-expression body IDS and DLP scanning. Under Mail Gateway -> Advanced, insert the following "Customized Rules"
body DLP_PATTERN1 /my salary is/i score DLP_PATTERN1 0.0001 body DLP_PATTERN2 /my boss is/i score DLP_PATTERN2 0.0001
and in your flow you can match any of these rules
if (in_array("DLP_PATTERN1", ScanSARules()) or
in_array("DLP_PATTERN2", ScanSARules()))
{
Reject("Forbidden Content");
}
ScanKAV($attachmentids)
→ Array of String
Scan the message using Kaspersky AntiVirus. This function requires license, if no license is available it will return an empty array (no viruses found). If $attachmentids is not specified, the message in whole is scanned.
| Result | Description |
|---|---|
| ("virus name","virus name") | array of virus names |
if (ScanKAV()) WrapMessage("Virus Detected", "Kaspersky detected a virus in the attached message.");
ScanCLAM($attachmentids)
→ Array of String
Scan the message using CLAM AntiVirus. This function requires license, if no license is available it will return an empty array (no viruses found). If $attachmentids is not specified, the message in whole is scanned.
| Result | Description |
|---|---|
| ("virus name","virus name") | array of virus names |
if (ScanCLAM()) WrapMessage("Virus Detected", "CLAM detected a virus in the attached message.");
ScanRPDAV()
→ Number
Scan the message using RPD AntiVirus. This function requires license, if no license is available it will return an empty array (no viruses found). It returns a score representing the type and probability of virus.
| Result | Description |
|---|---|
| 0 | Unknown |
| 50 | Medium probability |
| 100 | High probability |
This module affects the $result variable.
if (ScanRPDAV()) WrapMessage("Virus Detected", "RPDAV detected a virus in the attached message.");
ScanDLP($patterns = array())
→ Array of Strings
Scan the message with all (no parameters) or only the pattern specified (array of patterns) with the DLP engine. The result will be all matches found.
if (count(ScanDLP("PROFANITY")) > 10) Reject("Message was not sent due to excessive profanity"); // more than 10 matches of profanity
ScanBWList()
→ Number
/!\ The blacklist part of this function is deprecated in favor of Blacklist() which should be used instead, since it may block the mail before it's accepted.
This scan module is mabye not a scan module, in the way that it analyzes the message. But it do apply the user-defined black and whitelist. This black and white list is maintained by users and there black and white list in there quarantines.
| Result | Description |
|---|---|
| 0 | Whitelisted |
| 50 | Neutral |
| 100 | Blacklisted |
This module affects the $result variable.
switch(ScanBWList())
{
case 100:
Delete();
break;
case 0:
Deliver();
break;
}
ScanSPF()
→ Number
/!\ This function is deprecated in favor of spf() which should be used instead
Applies SPF the message, that is analyzing sender IP and sender helo message against the SPF record of the sending domain.
| Result | Description |
|---|---|
| 0 | Genuine (Pass) |
| 20 | Softfail (Suspected) |
| 50 | Unknown (Neutral) |
| 100 | Fail (Spam) |
This module affects the $result variable.
switch(ScanSPF())
{
case 100:
Delete();
break;
case 0:
Deliver();
break;
}
Security and encryption functions
DKIM Signing
DeliverWithDKIM($selector, $domain, $key, $options)
This is a final action, placing the message in the outgoing delivery queue. The execution of the script will terminate after a final action.
| Name | Default | Parameter |
|---|---|---|
| canonicalization_header | simple | Header Canonicalization |
| canonicalization_body | simple | Body Canonicalization |
| algorithm | sha256 | sha1 or sha256 |
| additional_headers | undefined | If set to an all additional headers will be signed (additional to the RFC recommendation). |
| headers | undefined | If set to an empty array, all headers will be signed, if header specified, those will be signed. |
(use with caution, subject to be changed without notification) use our "Flow Block" for now.
DKIM Verification/Assessment
DKIMSDID($explicitDomainList = array(), $options = array('signature_limit'=>5))
→ Associative Array [domain] => "result"
DKIMSDID() returns the SDID (Signing Domain IDentifier) status of a scanned message. It will return a associative array containing the domain as result.
$sdid = DKIMSDID(); // array("halon.se" => "pass")
echo $sdid;
SetHeader("X-DKIMSDID-Result", $sdid);
| Result | Description |
|---|---|
| skip | Message was not verified due to signature limit or not in explicit domain list. |
| pass | Message was verified and signature was valid. |
| temperror | Indicates a temporary error (DNS failures) |
| passerror | Signature was not valid, but domain had "testing" (softfail) published in their _domainkey record (t=y) |
| permerror | Signature was not valid. |
DKIMADSP()
→ Associative Array [domain] => "result"
DKIMADSP() returns the ADSP (Author Domain Signing Practices) policy of a scanned message. It will return a associative array containing the domain as result.
$adsp = DKIMADSP(); // array("halon.se" => "pass", "forged-sender.example.org" => "fail")
echo $adsp;
SetHeader("X-DKIMADSP-Result", $adsp);
| Result | Description |
|---|---|
| none | No DKIM Author Domain Signing Practices (ADSP) record was published. |
| pass | This message had an Author Domain Signature that was validated. (An ADSP check is not strictly required to be performed for this result since a valid Author Domain Signature satisfies all possible ADSP policies.) |
| unknown | No valid Author Domain Signature was found on the message and the published ADSP was "unknown". |
| fail | No valid Author Domain Signature was found on the message and the published ADSP was "all". |
| discard | No valid Author Domain Signature was found on the message and the published ADSP was "discardable". |
| nxdomain | Evaluating the ADSP for the Author's DNS domain indicated that the Author's DNS domain does not exist. |
| temperror | An ADSP record could not be retrieved due to some error that is likely transient in nature, such as a temporary DNS error. A later attempt may produce a final result. |
| permerror | An ADSP record could not be retrieved due to some error that is likely not transient in nature, such as a permanent DNS error. A later attempt is unlikely to produce a final result. |
As defined in RFC-5617 http://tools.ietf.org/search/rfc5617