Skip to content

Content

Some policies require changing the content of a message as it passes through the server: adding a disclaimer to outgoing mail, for example, or removing a dangerous attachment. In Stalwart, these changes are made by a system Sieve script at the DATA stage, before the message is queued. Such a script can add text to the body, replace or remove MIME parts, or wrap the message.

The script is a SieveSystemScript, selected by the script field of MtaStageData (found in the WebUI under Settings › MTA › Session › DATA Stage). It works with the MIME extensions of RFC 5703, which let Sieve treat a message as a set of parts:

  • foreverypart visits each part of the message in turn.
  • header :mime tests the type of a part.
  • replace replaces a part.
  • The ${part.text} variable holds the decoded text of the current part.

These extensions matter because most messages are multipart. A typical message holds a plain-text and an HTML version of the body, and often attachments as well. A script that changes the body must therefore change the right parts and leave the others alone. Replacing the whole message with text would destroy its HTML version and its attachments.

The direction of the mail matters too. The same DATA-stage script runs for mail received from other servers, so a script meant for outgoing mail must either test that the session is authenticated, or be selected only on the submission ports.

The following script appends a disclaimer to the plain-text and to the HTML version of every message sent by an authenticated user. It leaves attachments unchanged, including attachments that are themselves text:

require ["variables", "mime", "foreverypart", "replace"];
if not string :is "${env.authenticated_as}" "" {
foreverypart {
if header :mime :type :is "Content-Disposition" "attachment" {
# Attachments are left unchanged
} elsif header :mime :contenttype "Content-Type" "text/plain" {
replace "${part.text}\r\n\r\n--\r\nThis message is confidential.\r\n";
} elsif header :mime :contenttype "Content-Type" "text/html" {
replace :mime "Content-Type: text/html; charset=utf-8\r\n\r\n${part.text}<p>This message is confidential.</p>\r\n";
}
}
}

Try this script in Sievepad

The script uses replace in two forms, and they differ in how the new part is written.

Without :mime, replace replaces the part with a plain-text part that holds the given text. The text may need encoding for transport. When it contains 8-bit characters or a line longer than 998 octets, it is encoded as quoted-printable. When quoted-printable would be clearly larger, which is mostly the case for non-Latin text, it is encoded as base64 instead. In either case, the part is given the matching Content-Transfer-Encoding header field.

With :mime, the text is a complete MIME part, here an HTML part, and its header fields are written as given. Its body is encoded in the same way as above, unless the script supplies a Content-Transfer-Encoding header field itself, in which case the part is written exactly as given. Because the new HTML version holds the decoded text of the original part, an HTML body with non-ASCII characters is sent as quoted-printable. A multipart or message part supplied with :mime is always written as given, because MIME does not allow those types to be encoded.

The disclaimer follows the end of the HTML document, which is where mail clients display it. Messages attached as message/rfc822, such as a forwarded message, are not modified.

In a message with a single text part, that part is the message itself. In that case replace changes the body and the Content-* header fields. It keeps every other header field in its original order, including Message-ID, In-Reply-To and References, as RFC 5703 requires.

The script still has to be selected. The following script value selects it for authenticated sessions, here under the name disclaimer:

{
"script": {
"match": {"0": {"if": "!is_empty(authenticated_as)", "then": "'disclaimer'"}},
"else": "false"
}
}

The disclaimer is added before the message is signed with DKIM, so the signature covers it.

The following script replaces executable attachments with a short notice and keeps the rest of the message:

require ["mime", "foreverypart", "replace"];
foreverypart {
if anyof (
header :mime :contenttype :is "Content-Type" "application/x-msdownload",
header :mime :param "filename" :matches ["Content-Type", "Content-Disposition"] "*.exe" )
{
replace "Executable attachment removed by the mail server.";
}
}

Try this script in Sievepad

Each matching part is replaced with a plain-text part that holds the notice, and the other parts are unchanged.

Altering the message is one of several ways to deal with dangerous attachments. A script that refuses such messages instead of altering them is shown in Sieve examples. The spam filter also scores dangerous attachments, without changing the message.