## 5 The MFL Library Functions ¶

This chapter describes library functions available in Mailfromd version 9.1. For the simplicity of explanation, we use the word ‘`boolean`’ to indicate variables of numeric type that are used as boolean values. For such variables, the term ‘`False`’ stands for the numeric 0, and ‘`True`’ for any non-zero value.

### 5.1 Sendmail Macro Access Functions ¶

Built-in Function: `string` getmacro `(string macro)` ¶

  Returns the value of Sendmail macro macro. If macro is not defined, raises the `e_macroundef` exception.

  Calling `getmacro(name)` is completely equivalent to referencing `${name}`, except that it allows to construct macro names programmatically, e.g.:

        if getmacro("auth_%var") = "foo"
          ...
        fi

Built-in Function: `boolean` macro_defined `(string name)` ¶

  Return true if Sendmail macro name is defined.

Notice, that if your MTA supports macro name negotiation22, you will have to export macro names used by these two functions using ‘`#pragma miltermacros`’ construct. Consider this example:

    func authcheck(string name)
    do
      string macname "auth_%name"
      if macro_defined(macname)
        if getmacro(macname)
          ...
        fi
      fi
    done

    #pragma miltermacros envfrom auth_authen

    prog envfrom
    do
      authcheck("authen")
    done

In this case, the parser cannot deduce that the `envfrom` handler will attempt to reference the ‘`auth_authen`’ macro, therefore the ‘`#pragma miltermacros`’ is used to help it.

### 5.2 `tr`, `dc`, and `sq` functions ¶

Built-in Function: `string` tr `(string subj, string set1, string set2)` ¶

  Translates characters in string subj and returns the resulting string.

  Translation rules are defined by two character sets: set1 is a set of characters which, when encountered in subj, must be replaced with the corresponding characters from set2. E.g.:

      tr('text', 'tx', 'ni') ⇒ 'nein'

  The source set set1 can contain character classes, sets of characters enclosed in square brackets. A character class matches the input character if that character is listed in the class. When a match occurs, the character is replaced with the corresponding character from set2:

      tr('abacus', '[abc]', '_') ⇒ ‘____us’

  An exclamation sign at the beginning of a character class reverses its meaning, i.e. the class matches any character not listed in it:

      tr('abacus', '[!abc]', '_') ⇒ ‘abac__’

  A character set can contain ranges, specified as the first and last characters from the range separated by a dash. A range ‘`x-y`’ comprises all characters between x and y inclusive. For example, ‘`[a-d]`’ is equivalent to ‘`[abcd]`’. Character sets must be ascending, i.e. ‘`[a-d]`’ is correct, but ‘`[d-a]`’ is not. You may include ‘`-`’ in its literal meaning by making it the first or last character between the brackets: ‘`[0-9-]`’ matches any digit or dash.

  Similarly, to include a closing bracket, make it the first character in the class (after negation character, for excluding ranges), e.g. ‘`[][!]`’ matches the three characters ‘`[`’, ‘`]`’ and ‘`!`’, whereas ‘`[!][]`’ matches any character except ‘`[`’ and ‘`]`’.

  Named character classes are special reserved names between ‘`[:`’ and ‘`:]`’ delimiters:

  `[:alnum:]`

    Matches any alphanumeric character. Equivalent to ‘`[:alpha:][:digit:]`’.

  `[:alpha:]`

    Matches any alphabetic character.

  `[:blank:]`

    Matches horizontal space or tab.

  `[:cntrl:]`

    Matches a control character, i.e. a character with ASCII code less than 32.

  `[:digit:]`

    Matches a decimal digit (0 through 9).

  `[:graph:]`

    Matches any printable character except space (horizontal space and tab).

  `[:lower:]`

    Matches any lowercase letter.

  `[:print:]`

    Matches any printable character including space.

  `[:punct:]`

    Matches any printable character which is not a space or an alphanumeric character.

  `[:space:]`

    Matches ‘`white-space`’ characters: horizontal space (ASCII 32), form-feed (ASCII 12, or ‘`\f`’), newline (ASCII 10, or ‘`\n`’), carriage return (ASCII 13, or ‘`\r`’), horizontal tab (ASCII 9, or ‘`\t`’), and vertical tab (ASCII 11, or ‘`\v`’).

  `[:upper:]`

    Matches any upper case letter.

  `[:xdigit:]`

    Matches any hexagesimal digit: ‘`0`’ through ‘`9`’, ‘`a`’ through ‘`f`’ and ‘`A`’ through ‘`F`’.

  Named classes can appear in character classes in set1 anywhere a regular character is allowed. Examples:

  ‘`[][:alpha:]-]`’

    Mathes alphabet letters (both cases), digits, closing bracket and dash.

  ‘`[!][:alpha:]-]`’

    A complement of the above: matches any character except the ones listed above.

  ‘`[[:xdigit:][:blank:]]`’

    Matches any hexagesimal digit or horizontal whitespace characters.

  The replacement set must not be empty. Its length must be equal to or less than that of set1 (character classes being counted as one character). If set1 contains more characters than set2, the surplus ones will be translated to the last character from set2:

      tr('lasted', 'alde', 'iL?') ⇒ 'List??'

  Both sets can contain character ranges, represented as ‘`c1-c2`’. Whenever a range appears in set1, a range must appear in the corresponding position of set2:

      tr('gnu', 'a-z', 'A-Z') ⇒ 'GNU'

  Character ranges are not to be confused with ranges in character classes: they are similiar, but quite distinct. Both match a single character, but while ranges translate to a corresponding character from the replacement range, ranges within character class translate to a single character:

      tr('gnu', '[a-z]', 'A') ⇒ 'AAA'

  Character ranges in set1 must always be in ascending order (i.e. ‘`a-z`’ is allowed, whereas ‘`z-a`’ is not). Ranges in set2 can be both ascending and descending, e.g.:

      tr('8029', '0-9', '9-0') ⇒ '1970'

  To translate a dash, place it as the first or last character in set1:

      tr('in-place', '-ilp', ' Irg') ⇒ 'In grace'

  The `tr` function will raise the `e_inval` exception if set2 is empty or set1 contains a range without matching range in set2. It will raise the `e_range` exception, if a descending range appears in set1 or number of characters in a range from set1 does not match that from the corresponding range in set2.

Built-in Function: `string` dc `(string subj, string set1)` ¶

  Deletes from subj characters that appear in set1. The syntax of set1 is as described in `tr`, except that character ranges are treated as if appearing within character class (e.g. ‘`a-z`’ is the same as ‘`[a-z]`’).

  For example, `dc(subj, '0-9')` removes decimal digits from first argument.

Built-in Function: `string` sq `(string subj, string set1)` ¶

  Squeezes repeats, i.e. replaces each sequence of a repeated character that is listed in set1, with a single occurrence of that character. The syntax of set1 is as described in `tr`, except that character ranges are treated as if appearing within character class (e.g. ‘`a-z`’ is the same as ‘`[a-z]`’).

  For example, `sq(subj, '[[:space:]]')` replaces multiple occurrences of whitespace characters with a single character.

### 5.3 The `sed` function ¶

The `sed` function allows you to transform a string by replacing parts of it that match a regular expression with another string. This function is somewhat similar to the `sed` command line utility (hence its name) and bears similarities to analogous functions in other programming languages (e.g. `sub` in `awk` or the `s//` operator in `perl`).

Built-in Function: `string` sed `(string subject, expr, …)` ¶

  The expr argument is an s-expressions of the the form:

      s/regexp/replacement/[flags]

  where regexp is a regular expression, and replacement is a replacement string for each part of the subject that matches regexp. When `sed` is invoked, it attempts to match subject against the regexp. If the match succeeds, the portion of subject which was matched is replaced with replacement. Depending on the value of flags (see global replace), this process may continue until the entire subject has been scanned.

  The resulting output serves as input for next argument, if such is supplied. The process continues until all arguments have been applied.

  The function returns the output of the last s-expression.

Both regexp and replacement are described in detail in The ‘s’ Command in GNU sed.

Supported flags are:

‘`g`’ ¶

  Apply the replacement to all matches to the regexp, not just the first.

‘`i`’ ¶

  Use case-insensitive matching. In the absence of this flag, the value set by the recent `#pragma regex icase` is used (see icase).

‘`x`’ ¶

  regexp is an extended regular expression (see Extended regular expressions in GNU sed). In the absence of this flag, the value set by the recent `#pragma regex extended` (if any) is used (see extended).

‘`number`’

  Only replace the numberth match of the regexp.

  Note: the POSIX standard does not specify what should happen when you mix the ‘`g`’ and number modifiers. `Mailfromd` follows the GNU `sed` implementation in this regard, so the interaction is defined to be: ignore matches before the numberth, and then match and replace all matches from the numberth on.

Any delimiter can be used in lieue of ‘`/`’, the only requirement being that it be used consistently throughout the expression. For example, the following two expressions are equivalent:

    s/one/two/
    s,one,two,

Changing delimiters is often useful when the regex contains slashes. For instance, it is more convenient to write `s,/,-,` than `s/\//-/`.

Here is an example of `sed` usage:

      set email sed(input, 's/^<(.*)>$/\1/x')

It removes angle quotes from the value of the ‘`input`’ variable and assigns the result to ‘`email`’.

To apply several s-expressions to the same input, you can either give them as multiple arguments to the `sed` function:

      set email sed(input, 's/^<(.*)>$/\1/x', 's/(.+@)(.+)/\1\L\2\E/x')

or give them in a single argument separated with semicolons:

      set email sed(input, 's/^<(.*)>$/\1/x;s/(.+@)(.+)/\1\L\2\E/x')

Both examples above remove optional angle quotes and convert the domain name part to lower case.

Regular expressions used in `sed` arguments are controlled by the `#pragma regex`, as another expressions used throughout the MFL source file. To avoid using the ‘`x`’ modifier in the above example, one can write:

      #pragma regex +extended
      set email sed(input, 's/^<(.*)>$/\1/', 's/(.+@)(.+)/\1\L\2\E/')

See Pragma regex, for details about that `#pragma`.

So far all examples used constant s-expressions. However, this is not a requirement. If necessary, the expression can be stored in a variable or even constructed on the fly before passing it as argument to `sed`. For example, assume that you wish to remove the domain part from the value, but only if that part matches one of predefined domains. Let a regular expression that matches these domains be stored in the variable `domain_rx`. Then this can be done as follows:

      set email sed(input, "s/(.+)(@%domain_rx)/\1/")

If the constructed regular expression uses variables whose value should be matched exactly, such variables must be quoted before being used as part of the regexp. Mailfromd provides a convenience function for this:

Built-in Function: `string` qr `(string str[; string delim])` ¶

  Quote the string str as a regular expression. This function selects the characters to be escaped using the currently selected regular expression flavor (see Pragma regex). At most two additional characters that must be escaped can be supplied in the delim optional parameter. For example, to quote the variable ‘`x`’ for use in double-quoted s-expression:

        qr(x, '/"')

### 5.4 String Manipulation Functions ¶

Built-in Function: `string` escape `(string str, [string chars])` ¶

  Returns a copy of str with the characters from chars escaped, i.e. prefixed with a backslash. If chars is not specified, ‘`\"`’ is assumed.

      escape('"a\tstr"ing') ⇒ '\"a\\tstr\"ing'
      escape('new "value"', '\" ') ⇒ 'new\ \"value\"'

Built-in Function: `string` unescape `(string str)` ¶

  Performs the reverse to ‘`escape`’, i.e. removes any prefix backslash characters.

      unescape('a \"quoted\" string') ⇒ 'a "quoted" string'

Built-in Function: `string` unescape `(string str, [string chars])` ¶

Built-in Function: `string` domainpart `(string str)` ¶

  Returns the domain part of str, if it is a valid email address, otherwise returns str itself.

      domainpart("gray") ⇒ "gray"
      domainpart("gray@gnu.org.ua") ⇒ "gnu.org.ua"

Built-in Function: `number` index `(string s, string t)` ¶
Built-in Function: `number` index `(string s, string t, number start)` ¶

  Returns the index of the first occurrence of the string t in the string s, or -1 if t is not present.

      index("string of rings", "ring") ⇒ 2

  Optional argument start, if supplied, indicates the position in string where to start searching.

      index("string of rings", "ring", 3) ⇒ 10

  To find the last occurrence of a substring, use the function rindex (see rindex).

Built-in Function: `number` interval `(string str)` ¶

  Converts str, which should be a valid time interval specification (see time interval specification), to seconds.

Built-in Function: `number` length `(string str)` ¶

  Returns the length of the string str in bytes.

      length("string") ⇒ 6

Built-in Function: `string` dequote `(string str)` ¶

  Removes ‘`<`’ and ‘`>`’ surrounding str. If str is not enclosed by angle brackets or these are unbalanced, the argument is returned unchanged:

      dequote("<root@gnu.org.ua>") ⇒ "root@gnu.org.ua"
      dequote("root@gnu.org.ua") ⇒ "root@gnu.org.ua"
      dequote("there>") ⇒ "there>"

Built-in Function: `string` localpart `(string str)` ¶

  Returns the local part of str if it is a valid email address, otherwise returns str unchanged.

      localpart("gray") ⇒ "gray"
      localpart("gray@gnu.org.ua") ⇒ "gray"

Built-in Function: `string` replstr `(string s, number n)` ¶

  Replicate a string, i.e. return a string, consisting of s repeated n times:

      replstr("12", 3) ⇒ "121212"

Built-in Function: `string` revstr `(string s)` ¶

  Returns the string composed of the characters from s in reversed order:

      revstr("foobar") ⇒ "raboof"

Built-in Function: `number` rindex `(string s, string t)` ¶
Built-in Function: `number` rindex `(string s, string t, number start)` ¶

  Returns the index of the last occurrence of the string t in the string s, or -1 if t is not present.

      rindex("string of rings", "ring") ⇒ 10

  Optional argument start, if supplied, indicates the position in string where to start searching. E.g.:

      rindex("string of rings", "ring", 10) ⇒ 2

  See also String manipulation.

Built-in Function: `string` substr `(string str, number start)` ¶
Built-in Function: `string` substr `(string str, number start, number length)` ¶

  Returns the at most length-character substring of str starting at start. If length is omitted, the rest of str is used.

  If length is greater than the actual length of the string, the `e_range` exception is signalled.

      substr("mailfrom", 4) ⇒ "from"
      substr("mailfrom", 4, 2) ⇒ "fr"

Built-in Function: `string` substring `(string str, number start, number end)` ¶

  Returns a substring of str between offsets start and end, inclusive. Negative end means offset from the end of the string. In other words, yo obtain a substring from start to the end of the string, use `substring(str, start, -1)`:

      substring("mailfrom", 0, 3) ⇒ "mail"
      substring("mailfrom", 2, 5) ⇒ "ilfr"
      substring("mailfrom", 4, -1) ⇒ "from"
      substring("mailfrom", 4, length("mailfrom") - 1) ⇒ "from"
      substring("mailfrom", 4, -2) ⇒ "fro"

  This function signals `e_range` exception if either start or end are outside the string length.

Built-in Function: `string` tolower `(string str)` ¶

  Returns a copy of the string str, with all the upper-case characters translated to their corresponding lower-case counterparts. Non-alphabetic characters are left unchanged.

      tolower("MAIL") ⇒ "mail"

Built-in Function: `string` toupper `(string str)` ¶

  Returns a copy of the string str, with all the lower-case characters translated to their corresponding upper-case counterparts. Non-alphabetic characters are left unchanged.

      toupper("mail") ⇒ "MAIL"

Built-in Function: `string` ltrim `(string str[, string cset)` ¶

  Returns a copy of the input string str with any leading characters present in cset removed. If the latter is not given, white space is removed (spaces, tabs, newlines, carriage returns, and line feeds).

      ltrim("  a string") ⇒ "a string"
      ltrim("089", "0") ⇒ "89"

  Note the last example. It shows how `ltrim` can be used to convert decimal numbers in string representation that begins with ‘`0`’. Normally such strings will be treated as representing octal numbers. If they are indeed decimal, use `ltrim` to strip off the leading zeros, e.g.:

      set dayofyear ltrim(strftime('%j', time()), "0")

Built-in Function: `string` rtrim `(string str[, string cset)` ¶

  Returns a copy of the input string str with any trailing characters present in cset removed. If the latter is not given, white space is removed (spaces, tabs, newlines, carriage returns, and line feeds).

Built-in Function: `number` vercmp `(string a, string b)` ¶

  Compares two strings as `mailfromd` version numbers. The result is negative if b precedes a, zero if they refer to the same version, and positive if b follows a:

      vercmp("5.0", "5.1") ⇒ 1
      vercmp("4.4", "4.3") ⇒ -1
      vercmp("4.3.1", "4.3") ⇒ -1
      vercmp("8.0", "8.0") ⇒ 0

Library Function: `string` sa_format_score `(number code, number prec)` ¶

  Format code as a floating-point number with prec decimal digits:

      sa_format_score(5000, 3) ⇒ "5.000"

  This function is convenient for formatting SpamAssassin scores for use in message headers and textual reports. It is defined in module `sa.mfl`.

  See SpamAssassin, for examples of its use.

Library Function: `string` sa_format_report_header `(string text)` ¶

  Format a SpamAssassin report text in order to include it in a RFC 822 header. This function selects the score listing from text, and prefixes each line with ‘`*` ’. Its result looks like:

      *  0.2 NO_REAL_NAME           From: does not include a real name
      *  0.1 HTML_MESSAGE           BODY: HTML included in message

  See SpamAssassin, for examples of its use.

Library Function: `string` strip_domain_part `(string domain, number n)` ¶

  Returns at most n last components of the domain name domain. If n is 0 the function returns domain.

  This function is defined in the module `strip_domain_part.mfl` (see Modules).

  Examples:

      require strip_domain_part
      strip_domain_part("puszcza.gnu.org.ua", 2) ⇒ "org.ua"
      strip_domain_part("puszcza.gnu.org.ua", 0) ⇒ "puszcza.gnu.org.ua"

Library Function: `string` verp_extract_user `(string email, string domain)` ¶

  If email is a valid VERP-style email address for domain, that corresponds to a valid local user name (see validuser), this function returns the local user name, corresponding to that email. Otherwise, it returns empty string.

  For example, assuming the local user ‘`gray`’ exists:

      verp_extract_user("gray=gnu.org.ua@tuhs.org", 'gnu\..*')
        ⇒ "gray"

### 5.5 String formatting ¶

Built-in Function: `string` sprintf `(string format, …)` ¶

  The function `sprintf` formats its argument according to format (see below) and returns the resulting string. It takes varying number of parameters, the only mandatory one being format.

#### Format string ¶

The format string is a simplified version of the format argument to C `printf`-family functions.

The format string is composed of zero or more directives: ordinary characters (not ‘`%`’), which are copied unchanged to the output stream; and conversion specifications, each of which results in fetching zero or more subsequent arguments. Each conversion specification is introduced by the character ‘`%`’, and ends with a conversion specifier. In between there may be (in this order) zero or more flags, an optional minimum field width, and an optional precision.

Notice, that in practice that means that you should use single quotes with the format arguments, to protect conversion specifications from being recognized as variable references (see singe-vs-double).

No type conversion is done on arguments, so it is important that the supplied arguments match their corresponding conversion specifiers. By default, the arguments are used in the order given, where each ‘`*`’ and each conversion specifier asks for the next argument. If insufficiently many arguments are given, `sprintf` raises ‘`e_range`’ exception. One can also specify explicitly which argument is taken, at each place where an argument is required, by writing ‘`%m$`’, instead of ‘`%`’ and ‘`*m$`’ instead of ‘`*`’, where the decimal integer m denotes the position in the argument list of the desired argument, indexed starting from 1. Thus,

        sprintf('%*d', width, num);

and

        sprintf('%2$*1$d', width, num);

are equivalent. The second style allows repeated references to the same argument.

#### Flag characters ¶

The character ‘`%`’ is followed by zero or more of the following flags:

‘`#`’

  The value should be converted to an alternate form. For ‘`o`’ conversions, the first character of the output string is made zero (by prefixing a ‘`0`’ if it was not zero already). For ‘`x`’ and ‘`X`’ conversions, a non-zero result has the string ‘`0x`’ (or ‘`0X`’ for ‘`X`’ conversions) prepended to it. Other conversions are not affected by this flag.

‘`0`’

  The value should be zero padded. For ‘`d`’, ‘`i`’, ‘`o`’, ‘`u`’, ‘`x`’, and ‘`X`’ conversions, the converted value is padded on the left with zeros rather than blanks. If the ‘`0`’ and ‘`-`’ flags both appear, the ‘`0`’ flag is ignored. If a precision is given, the ‘`0`’ flag is ignored. Other conversions are not affected by this flag.

‘`-`’

  The converted value is to be left adjusted on the field boundary. (The default is right justification.) The converted value is padded on the right with blanks, rather than on the left with blanks or zeros. A ‘`-`’ overrides a ‘`0`’ if both are given.

‘`' ' (a space)`’

  A blank should be left before a positive number (or empty string) produced by a signed conversion.

‘`+`’

  A sign (‘`+`’ or ‘`-`’) always be placed before a number produced by a signed conversion. By default a sign is used only for negative numbers. A ‘`+`’ overrides a space if both are used.

#### Field width ¶

An optional decimal digit string (with nonzero first digit) specifying a minimum field width. If the converted value has fewer characters than the field width, it will be padded with spaces on the left (or right, if the left-adjustment flag has been given). Instead of a decimal digit string one may write ‘`*`’ or ‘`*m$`’ (for some decimal integer m) to specify that the field width is given in the next argument, or in the m-th argument, respectively, which must be of numeric type. A negative field width is taken as a ‘`-`’ flag followed by a positive field width. In no case does a non-existent or small field width cause truncation of a field; if the result of a conversion is wider than the field width, the field is expanded to contain the conversion result.

#### Precision ¶

An optional precision, in the form of a period (‘`.`’) followed by an optional decimal digit string. Instead of a decimal digit string one may write ‘`*`’ or ‘`*m$`’ (for some decimal integer m) to specify that the precision is given in the next argument, or in the m-th argument, respectively, which must be of numeric type. If the precision is given as just ‘`.`’, or the precision is negative, the precision is taken to be zero. This gives the minimum number of digits to appear for ‘`d`’, ‘`i`’, ‘`o`’, ‘`u`’, ‘`x`’, and ‘`X`’ conversions, or the maximum number of characters to be printed from a string for the ‘`s`’ conversion.

#### Conversion specifier ¶

A character that specifies the type of conversion to be applied. The conversion specifiers and their meanings are:

d
i

  The numeric argument is converted to signed decimal notation. The precision, if any, gives the minimum number of digits that must appear; if the converted value requires fewer digits, it is padded on the left with zeros. The default precision is ‘`1`’. When ‘`0`’ is printed with an explicit precision ‘`0`’, the output is empty.

o
u
x
X

  The numeric argument is converted to unsigned octal (‘`o`’), unsigned decimal (‘`u`’), or unsigned hexadecimal (‘`x`’ and ‘`X`’) notation. The letters ‘`abcdef`’ are used for ‘`x`’ conversions; the letters ‘`ABCDEF`’ are used for ‘`X`’ conversions. The precision, if any, gives the minimum number of digits that must appear; if the converted value requires fewer digits, it is padded on the left with zeros. The default precision is ‘`1`’. When ‘`0`’ is printed with an explicit precision 0, the output is empty.

s

  The string argument is written to the output. If a precision is specified, no more than the number specified of characters are written.

%

  A ‘`%`’ is written. No argument is converted. The complete conversion specification is ‘`%%`’.

### 5.6 Character Type ¶

These functions check whether all characters of str fall into a certain character class according to the ‘`C`’ (‘`POSIX`’) locale23. ‘`True`’ (1) is returned if they do, ‘`false`’ (0) is returned otherwise. In the latter case, the global variable `ctype_mismatch` is set to the index of the first character that is outside of the character class (characters are indexed from 0).

Built-in Function: `boolean` isalnum `(string str)` ¶

  Checks for alphanumeric characters:

        isalnum("a123") ⇒ 1
        isalnum("a.123") ⇒ 0 (ctype_mismatch = 1)

Built-in Function: `boolean` isalpha `(string str)` ¶

  Checks for an alphabetic character:

        isalnum("abc") ⇒ 1
        isalnum("a123") ⇒ 0

Built-in Function: `boolean` isascii `(string str)` ¶

  Checks whether all characters in str are 7-bit ones, that fit into the ASCII character set.

        isascii("abc") ⇒ 1
        isascii("ab\0200") ⇒ 0

Built-in Function: `boolean` isblank `(string str)` ¶

  Checks if str contains only blank characters; that is, spaces or tabs.

Built-in Function: `boolean` iscntrl `(string str)` ¶

  Checks for control characters.

Built-in Function: `boolean` isdigit `(string str)` ¶

  Checks for digits (0 through 9).

Built-in Function: `boolean` isgraph `(string str)` ¶

  Checks for any printable characters except spaces.

Built-in Function: `boolean` islower `(string str)` ¶

  Checks for lower-case characters.

Built-in Function: `boolean` isprint `(string str)` ¶

  Checks for printable characters including space.

Built-in Function: `boolean` ispunct `(string str)` ¶

  Checks for any printable characters which are not a spaces or alphanumeric characters.

Built-in Function: `boolean` isspace `(string str)` ¶

  Checks for white-space characters, i.e.: space, form-feed (‘`\f`’), newline (‘`\n`’), carriage return (‘`\r`’), horizontal tab (‘`\t`’), and vertical tab (‘`\v`’).

Built-in Function: `boolean` isupper `(string str)` ¶

  Checks for uppercase letters.

Built-in Function: `boolean` isxdigit `(string str)` ¶

  Checks for hexadecimal digits, i.e. one of ‘`0`’, ‘`1`’, ‘`2`’, ‘`3`’, ‘`4`’, ‘`5`’, ‘`6`’, ‘`7`’, ‘`8`’, ‘`9`’, ‘`a`’, ‘`b`’, ‘`c`’, ‘`d`’, ‘`e`’, ‘`f`’, ‘`A`’, ‘`B`’, ‘`C`’, ‘`D`’, ‘`E`’, ‘`F`’.

### 5.7 I/O functions ¶

MFL provides a set of functions for writing to disk files, pipes or sockets and reading from them. The idea behind them is the same as in most other programming languages: first you open the resource with a call to `open` which returns a descriptor i.e. an integer number uniquely identifying the resource. Then you can write or read from it using this descriptor. Finally, when the resource is no longer needed, you can close it with a call to `close`.

The number of available resource descriptors is limited. The default limit is 1024. You can tailor it to your needs using the `max-streams` runtime configuration statement. See max-streams, for a detailed description.

By default, all I/O operations are unbuffered. This can be changed by setting the following global variables:

Built-in variable: `number` io_buffering ¶

  Sets the default buffering type. Allowed values are (symbolic names are defined in `status.mfl` module):

  `0`
  `BUFFER_NONE`

    No buffering. This is the default.

  `1`
  `BUFFER_FULL`

    Full buffering. Size of the buffer is set by the `io_buffer_size` global variable (see below).

  `2`
  `BUFFER_LINE`

    Line buffering. When reading, it is pretty much the same as `BUFFER_FULL`. When writing, the data are accumulated in buffer and actually sent to the underlying transport stream when the newline character is seen.

    The initial size of the buffer is set by the `io_buffer_size` variable. It will grow as needed during the I/O.

Built-in variable: `number` io_buffer_size ¶

  Set the buffer size if `io_buffering` is set to `BUFFER_FULL` or `BUFFER_LINE`. By default, this variable is set to the size of the system page.

Built-in Function: `number` open `(string name)` ¶

  The name argument specifies the name of a resource to open and the access rights you need to have on it. The function returns a descriptor of the opened stream, which can subsequently be used as an argument to other I/O operations.

  Buffering mode for the opened stream is defined by the `io_buffering` and `io_buffer_size` global variables. It can be changed using the `setbuf` function (see setbuf).

  First symbols of name determine the type of the resource to be opened and the access mode:

  ‘`>`’

    The rest of name is a name of a file. Open the file for read-write access. If the file exists, truncate it to zero length, otherwise create the file.

  ‘`>>`’

    The rest of name is a name of a file. Open the file for appending (writing at end of file). The file is created if it does not exist.

  ‘`|`’

    Treat the rest of name as the command name and its arguments. Run this command and open its standard input for writing. The standard error is closed before launching the program. This can be altered by using the following versions of this construct:

    |2>null: command

      Standard error is redirected to `/dev/null`.

    |2>file:name command

      Execute command with its standard error redirected to the file name. If the file exists, it will be truncated.

    |2>>file:name command

      Standard error of the command is appended to the file name. If file does not exist, it will be created.

      The ‘`|2>null:`’ construct described above is a shortcut for

          |2>>file:/dev/null command

    |2>syslog:facility[.priority] command

      Standard error is redirected to the given syslog facility and, optionally, priority. If the latter is omitted, ‘`LOG_ERR`’ is assumed.

      Valid values for facility are: ‘`user`’, ‘`daemon`’, ‘`auth`’, ‘`authpriv`’, ‘`mail`’, and ‘`local0`’ through ‘`local7`’. Valid values for priority are: ‘`emerg`’, ‘`alert`’, ‘`crit`’, ‘`err`’, ‘`warning`’, ‘`notice`’, ‘`info`’, ‘`debug`’. Both facility and priority may be given in upper, lower or mixed cases.

    Notice, that no whitespace characters are allowed between ‘`|`’ and ‘`2>`’.

  ‘`|<`’

    Treat the rest of name as the command name and its arguments. Run this command with its stdin closed and stdout open for reading.

    The standard error is treated as described above (see ‘`|`’).

  ‘`|&`’

    Treat the rest of name as the command name and its arguments. Run this command and set up for two-way communication with it, i.e writes to the descriptor returned by `open` will send data to the program’s standard input, reads from the descriptor will get data from the program’s standard output.

    The standard error is treated as described above (see ‘`|`’). For example, the following redirects it to syslog ‘`mail.debug`’:

        |&2>syslog:mail.debug command

  ‘`@`’

    Treat the rest of name as the URL of a socket to connect to. Valid URL forms are described in milter port specification.

  If none of these prefixes is used, name is treated as a name of an existing file and `open` will attempt to open this file for reading.

  The `open` function will signal exception `e_failure` if it is unable to open the resource or get the required access to it.

Built-in Function: `number` spawn `(string cmd [, number in, number out, number err])` ¶

  Runs the supplied command cmd. The syntax of the cmd is the same as for the name argument to `open` (see above), which begins with ‘`|`’, excepting that the ‘`|`’ sign is optional. That is:

      spawn("/bin/cat")

  has exactly the same effect as

      open("|/bin/cat")

  Optional arguments specify file stream descriptors to be used for the program standard input, output and error streams, correspondingly. If supplied, these should be the values returned by a previous call to `open` or `tempfile`. The value ‘`-1`’ means no redirection.

  Buffering mode for the opened stream is defined by the `io_buffering` and `io_buffer_size` global variables. It can be changed using the `setbuf` function (see setbuf).

  The example below starts the `awk` program with a simple expression as its argument and redirects the content of the file `/etc/passwd` to its standard input. The returned stream descriptor is bound to the command’s standard output (see the description of ‘`|<`’ prefix above). The standard error is closed:

      number fd spawn("<awk -F: '{print $1}'", open("/etc/passwd"))

Built-in Function: `void` close `(number rd)` ¶

  The argument rd is a resource descriptor returned by a previous call to `open`. The function `close` closes the resource and deallocates any memory associated with it.

  `close` will signal `e_range` exception if rd lies outside of allowed range of resource descriptors. See max-streams.

Notice that you are not required to close resources opened by `open`. Any unclosed resource will be closed automatically upon the termination of the filtering program.

Built-in Function: `void` shutdown `(number rd, number how)` ¶

  This function causes all or part of a full-duplex connection to be closed. The rd must be either a socket descriptor (returned by `open(@...)`) or a two-way pipe socket descriptor (returned by `open(|&...)`), otherwise the call to `shutdown` is completely equivalent to `close`.

  The `how` argument identifies which part of the connection to shut down:

  SHUT_RD ¶

    Read connection. All further receptions will be disallowed.

  SHUT_WR ¶

    Write connection. All further transmissions will be disallowed.

  SHUT_RDWR ¶

    Shut down both read and write parts.

Built-in Function: `number` tempfile `([string tmpdir])` ¶

  Creates a nameless temporary file and returns its descriptor. Optional tmpdir supplies the directory where to create the file, instead of the default `/tmp`.

Built-in Function: `void` rewind `(number rd)` ¶

  Rewinds the stream identified by rd to its beginning.

Built-in Function: `number` copy `(number dst, number src)` ¶

  Copies all data from the stream src to dst. Returns number of bytes copied.

The following functions provide basic read/write capabilities.

Built-in Function: `void` write `(number rd, string str [, number size])` ¶

  Writes the string str to the resource descriptor rd. If the size argument is given, writes this number of bytes.

  This function always attempts to write the requested amount of data. It will signal `e_range` exception if rd lies outside of allowed range of resource descriptors, `e_io` exception if an I/O error occurs, and `e_eof` exception if it wrote 0 bytes (e.g. because the underlying device is full).

Built-in Function: `void` write_body `(number rd, pointer bp , number size)` ¶

  Write the body segment of length size from pointer bp to the stream rd. This function can be used only in `prog body` (see body handler). Its second and third arguments correspond exactly to the parameters of the `body` handler, so the following construct writes the message body to the resource `fd`, which should have been open prior to invoking the `body` handler:

      prog body
      do
        write_body(fd, $1, $2)
      done

Built-in Function: `string` read `(number rd, number n)` ¶

  Read and return at most n bytes from the resource descriptor rd.

  If there are less than n bytes in the stream, the remaining bytes will be returned. Use `length()` to obtain the actual size of the returned data. If there are no bytes left, the `e_eof` exception will be signalled.

  The function may signal the following exceptions:

  e_range

    rd lies outside of allowed range of resource descriptors.

  e_eof

    End of file encountered.

  e_io

    An I/O error occurred.

Built-in Function: `string` getdelim `(number rd, string delim)` ¶

  Read and return the next string terminated by delim from the resource descriptor rd.

  The terminating delim string will be removed from the return value.

  When using this function, it is highly recommended to enable full buffering for fd, either by setting `io_buffering` before `open` (see io_buffering) or by calling `setbuf` after it (see setbuf). See getline, for an example.

  This function may signal the following exceptions:

  e_range

    rd lies outside of allowed range of resource descriptors.

  e_eof

    End of file encountered.

  e_io

    An I/O error occurred.

Built-in Function: `string` getline `(number rd)` ¶

  Read and return the next line from the resource descriptor rd. A line is any sequence of characters terminated with the default line delimiter. The default delimiter is a property of rd, i.e. different descriptors can have different line delimiters. The default value is ‘`\n`’ (ASCII 10), and can be changed using the `fd_set_delimiter` function (see below).

  When using this function, it is highly recommended to enable full buffering for fd, either by setting `io_buffering` before `open` (see io_buffering) or by calling `setbuf` after it (see setbuf), e.g.:

        set fd open(input)
        setbuf(fd, BUFFER_FULL)
        set line getline(fd)
        ...

  This function may signal the following exceptions:

  e_range

    rd lies outside of allowed range of resource descriptors.

  e_eof

    End of file encountered.

  e_io

    An I/O error occurred.

Built-in Function: `void` fd_set_delimiter `(number fd, string delim)` ¶

  Set new line delimiter for the descriptor fd, which must be in opened state.

  Default delimiter is a newline character (ASCII 10). The following example shows how to change it to CRLF sequence:

      fd_set_delimiter(fd, "\r\n")

Built-in Function: `string` fd_delimiter `(number fd)` ¶

  Returns the line delimiter string for fd.

The following example shows how `mailfromd` I/O functions can be used to automatically add IP addresses to an RBL zone:

    set nsupdate_cmd
      "/usr/bin/nsupdate -k /etc/bind/Kmail.+157+14657.private"

    func block_address(string addr)
    do
      number fd
      string domain

      set fd open "|%nsupdate_cmd"

      set domain reverse_ipstr(addr) . ".rbl.myzone.come"
      write(fd, "prereq nxrrset %domain A\n"
                 "update add %domain 86400 A %addr\n\n"
    done

The function `reverse_ipstr` is defined in Internet address manipulation functions.

Built-in Function: `void` setbuf(number `fd, [number type, number size])` ¶

  Changes the buffering mode of fd according to the remaining two arguments. The type specifies buffering type (see io_buffering), and size supplies the buffer size for buffering types `BUFFER_FULL` and `BUFFER_LINE`. If size is omitted, it defaults to `io_buffer_size` (see io_buffer_size). Omitted type defaults to `io_buffering` (see io_buffering).

Built-in Function: `number` getbuftype(number `fd)` ¶

  Returns the type of buffering currently in effect for the descriptor fd. See io_buffering, for a list of possible return values.

  If this function returns `BUFFER_FULL` or `BUFFER_LINE`, you can use `getbufsize` to get the associated buffer size.

Built-in Function: `number` getbufsize(number `fd)` ¶

  Returns the buffer size for the descriptor fd.

### 5.8 Filtering functions ¶

This section describes functions that transform data using Mailutils filter pipes. Filter pipe is a string defining data flow between several filters. Each filter takes input, transforms it according to certain rules and produces the transformed data on its output. As in shell, multiple filters are connected using pipe characters (‘`|`’). For example, the `crlf` filter inserts a carriage return character before each newline character. A filter doing that kind of transformation is defined as:

    "crlf"

Another filter, `base64`, converts its input to a BASE64 encoded string. To transform each newline into carriage return + newline pair and encode the resulting stream in BASE64, one would write:

    "crlf | base64"

Some filters take one or more arguments. These are specified as a comma-delimited list in parentheses after the filter name. For example, the `linelen` filter limits the length of each output line to the given number of octets. The following filter pipe will limit the length of base64 lines in the filter above to 62 octets:

    "crlf | base64 | linelen(62)"

Many filters operate in two modes: encode and decode. By default all MFL functions apply filters in encode mode. The desired mode can be stated explicitly in the filter string by using `encode()` and `decode()` functions. They take a filter pipe line as their argument. For example, the following will decode the stream produced by the example filter above:

    "decode(base64 | crlf)"

See Filters and Filter Pipes, for a discussion of available filters and their arguments.

Built-in Function: `string` filter_string `(string input, string filter_pipe)` ¶

  Transforms the string input using filters in filter_pipe and returns the result. Example:

      set input "test\ninput\n"
      filter_string(input, "crlf|base64") ⇒ "dGVzdA0KaW5wdXQNCg=="

Built-in Function: `void` filter_fd `(number src_fd, number dst_fd, string filter_pipe)` ¶

  Given two I/O descriptors, reads data from src_fd, transforms it using filter_pipe and writes the result to descriptor dst_fd.

  Both descriptors must be obtained using functions described in I/O functions.

#### 5.8.1 Filters and Filter Pipes ¶

A filter pipe is a string consisting of filter invocations delimited by pipe characters (‘`|`’). Each invocation is a filter name optionally followed by a comma-separated list of parameters. Most filters can operate in two modes: encode and decode. Unless specified otherwise, filters are invoked in encode mode. To change the mode, the `encode` and `decode` meta-filters are provided. Argments to these filters are filter pipes that will be executed in the corresponding mode.

The following Mailutils filters are available:

Filter: 7bit ¶

  In encode mode, converts its input into 7-bit ASCII, by clearing the 8th bit on each processed byte.

  In decode mode, it operates exactly as the 8bit filter, i.e. copies its input to the output verbatim.

  The filter takes no arguments.

Filter: 8bit ¶
Filter: binary ¶

  Copies its input to output verbatim.

Filter: base64 ¶
Filter: B ¶

  Encodes or decodes the input using the `base64` encoding.

  The only difference between `BASE64` and `B` is that, in encode mode, the former limits each ouput line length to 76 octets, whereas the latter produces a contiguous stream of base64 data.

  In decode mode, both filters operate exactly the same way.

Filter: charset (cset) ¶
Filter: charset (cset, fallback) ¶

  A convenience interface to the `iconv` filter, available for use only in the `message_body_to_stream` function. It decodes the part of a MIME message from its original character set, which is determined from the value of the `Content-Type` header, to the destination character set cset. Optional fallback parameter specifies the representation fallback to be used for octets that cannot be converted between the charater sets. Its use is described in See iconv.

  This filter is normally takes its input from the `mimedecode` filter, as in:

      message_body_to_stream(fd, msg, 'mimedecode|charset(utf-8)')

  See mimedecode, for a detailed discussion.

Filter: crlf ¶
Filter: rfc822 ¶

  Converts line separators from LF (ASCII 10) to CRLF (ASCII 13 10) and vice-versa.

  In decode mode, translates each CRLF to LF. Takes no arguments.

  In encode mode, translates each LF to CRLF. If an optional argument ‘`-n`’ is given, produces a normalized output, by preserving each input CRLF sequence untouched (otherwise such sequences will be are translated to CR CR LF).

Filter: crlfdot ¶

  In encode mode, replaces each LF (‘`\n`’ or ASCII 10) character with CRLF (‘`\r\n`’, ASCII 13 10), and byte-stuffs the output by producing an additional ‘`.`’ in front of any ‘`.`’ appearing at the beginning of a line in input. Upon end of input, it outputs additional ‘`.\r\n`’, if the last output character was ‘`\n`’, or ‘`\r\n.\r\n`’ otherwise.

  If supplied the ‘`-n`’ argument, it preserves each CRLF input sequence untranslated (see the `CRLF` above).

  In decode mode, the reverse is performed: each CRLF is replaced with a single LF byte, and additional dots are removed from beginning of lines. A single dot on a line by itself marks the end of the stream and causes the filter to return EOF.

Filter: dot ¶

  In encode mode, byte-stuffs the input by outputting an additional dot (‘`.`’) in front of any dot appearing at the beginning of a line. Upon encountering end of input, it outputs additional ‘`.\n`’.

  In decode mode, the reverse is performed: additional dots are removed from beginning of lines. A single dot on a line by itself (i.e. the sequence ‘`\n.\n`’) marks the end of the stream and causes the filter to return EOF.

  This filter doesn’t take arguments.

Filter: from ¶

  Performs a traditional UNIX processing of lines starting with a ‘`From`’ followed by a space character.

  In encode mode, each ‘`From` ’ at the beginning of a line is replaced by ‘`>From` ’.

  In decode mode, the reverse operation is performed: initial greater-then sign (‘`>`’) is removed from any line starting with ‘`>From` ’.

  The filter takes no arguments.

Filter: fromrd ¶

  MBOXRD-compatible processing of envelope lines.

  In encode mode, each ‘`From` ’ optionally preceded by any number of contiguous ‘`>`’ characters and appearing at the beginning of a line is prefixed by another ‘`>`’ character on output.

  In decode mode, the reverse operation is performed: initial greater-then sign (‘`>`’) is removed from any line starting with one or more ‘`>`’ characters followed by ‘`From` ’.

Filter: header ¶

  This filter treats its input as a RFC-2822 email message. It extracts its header part (i.e. everything up to the first empty line) and copies it to the output. The body of the message is ignored.

  The filter operates only in decode mode and takes no arguments.

Filter: iconv (src, dst [, fallback]) ¶

  Converts input from character set src to dst. The filter works the same way in both decode and encode modes.

  It takes two mandatory arguments: the names of the input (src) and output (dst) charset. Optional third argument specifies what to do when an illegal character sequence is encountered in the input stream. Its possible values are:

  `none` ¶

    Raise a `e_ilseq` exception.

  `copy-pass` ¶

    Copy the offending octet to the output verbatim and continue conversion from the next octet.

  `copy-octal` ¶

    Print the offending octet to the output using the C octal conversion and continue conversion from the next octet.

  The default is `copy-octal`.

  The following example creates a `iconv` filter for converting from `iso-8859-2` to `utf-8`, raising the `e_ilseq` exception on the first conversion error:

      iconv(iso-8859-2, utf-8, none)

Filter: inline-comment ¶
Filter: inline-comment (str, [options]) ¶

  In decode mode, the filter removes from the input all lines beginning with a given inline comment sequence str. The default comment sequence is ‘`;`’ (a semicolon).

  The following options modify the default behavior:

  `-i, str`

    Emit line number information after each contiguous sequence of removed lines. The argument str supplies an information starter – a sequence of characters which is output before the actual line number.

  `-r`

    Remove empty lines, i.e. the lines that contain only whitespace characters.

  `-s`

    Squeeze whitespace. Each sequence of two or more whitespace characters encountered on input is replaced by a single space character on output.

  `-S`

    A whitespace-must-follow mode. A comment sequence is recognized only if followed by a whitespace character. The character itself is retained on output.

  In encode mode the `inline-comment` filter adds a comment-starter sequence at the beginning of each line. The default comment-starter is ‘`;`’ and can be changed by specifying the desired comment starter as the first argument.

  The only option supported in this mode is `-S`, which enables the whitespace-must-follow mode, in which a single space character (ASCII 20) is output after each comment sequence.

Filter: linecon ¶
Filter: linecon (-i, str) ¶

  Implements a familiar UNIX line-continuation facility. The filter removes from itsinput stream any newline character immediately preceded by a backslash. This filter operates only in decode mode.

  If given the arguments (‘`-i`’, str), enables the line number information facility. This facility emits current input line number (prefixed with str) after each contiguous sequence of one or more removed newline characters. It is useful for implementing parsers which are normally supposed to identify eventual erroneous lines with their input line numbers.

Filter: linelen (n) ¶

  Limits the length of each output line to a certain number of octets. It operates in encode mode only and requires a single parameter: the desired output length in octets. This filter makes no attempt to analyze the lexical structure of the input: the newline caracters are inserted when the length of the output line reaches a predefined maximum. Any newline characters present in the input are taken into account when computing the input line length.

Filter: mimedecode ¶

  This is a domain-specific filter available for use only with the `message_body_to_stream` function. It decodes the part of a MIME message from whatever encoding that was used to store it in the message to a stream of bytes. See mimedecode.

Filter: quoted-printable ¶
Filter: Q ¶

  Encodes or decodes the input using the quoted-printable encoding.

Filter: XML ¶

  In encode mode, the `xml` filter converts input stream (which must contain valid UTF-8 characters) into a form suitable for inclusion into a XML or HTML document, i.e. it replaces ‘`<`’, ‘`>`’, and ‘`&`’ with ‘`&lt;`’, ‘`&gt;`’, and ‘`&amp;`’, correspondingly, and replaces invalid characters with their numeric character reference representation.

  In decode mode, a reverse operation is performed.

  The filter does not take arguments.

### 5.9 Email processing functions ¶

Built-in Function: `number` email_map `(string email)` ¶

  Parses email and returns a bitmap, consisting of zero or more of the following flags:

  ‘`EMAIL_MULTIPLE`’ ¶

    email has more than one email address.

  ‘`EMAIL_COMMENTS`’ ¶

    email has comment parts.

  ‘`EMAIL_PERSONAL`’ ¶

    email has personal part.

  ‘`EMAIL_LOCAL`’ ¶

    email has local part.

  ‘`EMAIL_DOMAIN`’ ¶

    email has domain part.

  ‘`EMAIL_ROUTE`’ ¶

    email has route part.

  These constants are declared in the `email.mfl` module. The function `email_map` returns 0 if its argument is not a valid email address.

Library Function: `boolean` email_valid `(string email)` ¶

  Returns ‘`True`’ (1) if email is a valid email address, consisting of local and domain parts only. E.g.:

      email_valid("gray@gnu.org") ⇒ 1
      email_valid("gray") ⇒ 0
      email_valid('"Sergey Poznyakoff <gray@gnu.org>') ⇒ 0

  This function is defined in `email.mfl` (see Modules).

### 5.10 Envelope Modification Functions ¶

Envelope modification functions set sender and add or delete recipient addresses from the message envelope. This allows MFL scripts to redirect messages to another addresses.

Built-in Function: `void` set_from `(string email [, string args])` ¶

  Sets envelope sender address to email, which must be a valid email address. Optional args supply arguments to ESMTP ‘`MAIL FROM`’ command.

Built-in Function: `void` rcpt_add `(string address)` ¶

  Add the e-mail address to the envelope.

Built-in Function: `void` rcpt_delete `(string address)` ¶

  Remove address from the envelope.

The following example code uses these functions to implement a simple alias-like capability:

    prog envrcpt
    do
       string alias dbget(aliasdb, $1, "NULL", 1)
       if alias != "NULL"
         rcpt_delete($1)
         rcpt_add(alias)
       fi
    done

### 5.11 Header Modification Functions ¶

There are two ways to modify message headers in a MFL script. First is to use header actions, described in Action Statements, and the second way is to use message modification functions. Compared with the actions, the functions offer a series of advantages. For example, using functions you can construct the name of the header to operate upon (e.g. by concatenating several arguments), something which is impossible when using actions. Moreover, apart from three basic operations (add, modify and remove), as supported by header actions, header functions allow to insert a new header into a particular place.

Built-in Function: `void` header_add `(string name, string value)` ¶

  Adds a header ‘`name: value`’ to the message.

  In contrast to the `add` action, this function allows to construct the header name using arbitrary MFL expressions.

Built-in Function: `void` header_add `(string name, string value, number idx)` ¶

  This syntax is preserved for backward compatibility. It is equivalent to `header_insert`, which see.

Built-in Function: `void` header_insert `(string name, string value, number idx)` ¶

  This function inserts a header ‘`name: ‘`value`’`’ at idxth header position in the internal list of headers maintained by the MTA. That list contains headers added to the message either by the filter or by the MTA itself, but not the headers included in the message itself. Some of the headers in this list are conditional, e.g. the ones added by the ‘`H?cond?`’ directive in `sendmail.cf`. MTA evaluates them after all header modifications have been done and removes those of headers for which they yield false. This means that the position at which the header added by `header_insert` will appear in the final message will differ from idx.

Built-in Function: `void` header_delete `(string name [, number index])` ¶

  Delete header name from the envelope. If index is given, delete indexth instance of the header name.

  Notice the differences between this function and the `delete` action:

  1. It allows to construct the header name, whereas `delete` requires it to be a literal string.
  2. Optional index argument allows to select a particular header instance to delete.

Built-in Function: `void` header_replace `(string name, string value [, number index])` ¶

  Replace the value of the header name with value. If index is given, replace indexth instance of header name.

  Notice the differences between this function and the `replace` action:

  1. It allows to construct the header name, whereas `replace` requires it to be a literal string.
  2. Optional index argument allows to select a particular header instance to replace.

Library Function: `void` header_rename `(string name, string newname[, number idx])` ¶

  Defined in the module `header_rename.mfl`.
  Available only in the ‘`eom`’ handler.

  Renames the idxth instance of header name to newname. If idx is not given, assumes 1.

  If the specified header or the idx instance of it is not present in the current message, the function silently returns. All other errors cause run-time exception.

  The position of the renamed header in the header list is not preserved.

  The example below renames ‘`Subject`’ header to ‘`X-Old-Subject`’:

      require 'header_rename'

      prog eom
      do
        header_rename("Subject", "X-Old-Subject")
      done

Library Function: `void` header_prefix_all `(string name [, string prefix])` ¶

  Defined in the module `header_rename.mfl`.
  Available only in the ‘`eom`’ handler.

  Renames all headers named name by prefixing them with prefix. If prefix is not supplied, removes all such headers.

  All renamed headers will be placed in a continuous block in the header list. The absolute position in the header list will change. Relative ordering of renamed headers will be preserved.

Library Function: `void` header_prefix_pattern `(string pattern, string prefix)` ¶

  Defined in the module `header_rename.mfl`.
  Available only in the ‘`eom`’ handler.

  Renames all headers with names matching pattern (in the sense of `fnmatch`, see fnmatches) by prefixing them with prefix.

  All renamed headers will be placed in a continuous block in the header list. The absolute position in the header list will change. Relative ordering of renamed headers will be preserved.

  If called with one argument, removes all headers matching pattern.

  For example, to prefix all headers beginning with ‘`X-Spamd-`’ with an additional ‘`X-`’:

      require 'header_rename'

      prog eom
      do
        header_prefix_pattern("X-Spamd-*", "X-")
      done

### 5.12 Body Modification Functions ¶

Body modification is an experimental feature of MFL. The version 9.1 provides only one function for that purpose.

Built-in Function: `void` replbody `(string text)` ¶

  Replace the body of the message with text. Notice, that text must not contain RFC 822 headers. See the previous section if you want to manipulate message headers.

  Example:

        replbody("Body of this message has been removed by the mail filter.")

  No restrictions are imposed on the format of text.

Built-in Function: `void` replbody_fd `(number fd)` ¶

  Replaces the body of the message with the content of the stream fd. Use this function if the body is very big, or if it is returned by an external program.

  Notice that this function starts reading from the current position in fd. Use `rewind` if you wish to read from the beginning of the stream.

  The example below shows how to preprocess the body of the message using external program `/usr/bin/mailproc`, which is supposed to read the body from its standard input and write the processed text to its standard output:

      number fd   # Temporary file descriptor

      prog data
      do
        # Open the temporary file
        set fd tempfile()
      done

      prog body
      do
        # Write the body to it.
        write_body(fd, $1, $2)
      done

      prog eom
      do
        # Use the resulting stream as the stdin to the mailproc
        # command and read the new body from its standard output.
        rewind(fd)
        replbody_fd(spawn("</usr/bin/mailproc", fd))
      done

### 5.13 Message Modification Queue ¶

Message modification functions described in the previous subsections do not take effect immediately, in the moment they are called. Instead they store the requested changes in the internal message modification queue. These changes are applied at the end of processing, before ‘`eom`’ stage finishes (see Figure 3.1).

One important consequence of this way of operation is that calling any MTA action (see Action Statements), causes all prior modifications to the message to be ignored. That is because after receiving the action command, MTA will not call filter for that message any more. In particular, the ‘`eom`’ handler will not be called, and the message modification queue will not be flushed. While it is logical for such actions as `reject` or `tempfail`, it may be quite confusing for `accept`. Consider, for example, the following code:

    prog envfrom
    do
      if $1 == ""
        header_add("X-Filter", "foo")
        accept
      fi
    done

Obviously, the intention was to add a ‘`X-Filter`’ header and accept the message if it was sent from the null address. What happens in reality, however, is a bit different: the message is accepted, but no header is added to it. If you need to accept the message and retain any modifications you have done to it, you need to use an auxiliary variable, e.g.:

    number accepted 0
    prog envfrom
    do
      if $1 == ""
        header_add("X-Filter", "foo")
        set accepted 1
      fi
    done

Then, test this variable for non-zero value at the beginning of each subsequent handler, e.g.:

    prog data
    do
      if accepted
        continue
      fi
      ...
    done

To help you trace such problematic usages of `accept`, `mailfromd` emits the following warning:

    RUNTIME WARNING near /etc/mailfromd.mfl:36: `accept' causes previous
    message modification commands to be ignored; call mmq_purge() prior
    to `accept', to suppress this warning

If it is OK to lose all modifications, call `mmq_purge`, as suggested in this message.

Built-in Function: `void` mmq_purge `()` ¶

  Remove all modification requests from the queue. This function undoes the effect of any of the following functions, if they had been called previously: `rcpt_add`, `rcpt_delete`, `header_add`, `header_insert`, `header_delete`, `header_replace`, `replbody`, `quarantine`.

### 5.14 Mail Header Functions ¶

Built-in Function: `string` message_header_encode `(string text, [string enc, string charset])` ¶

  Encode text in accordance with RFC 2047. Optional arguments:

  enc

    Encoding to use. Valid values are ‘`quoted-printable`’, or ‘`Q`’ (the default) and ‘`base64`’, or ‘`B`’.

  charset

    Character set. By default ‘`UTF-8`’.

  If the function is unable to encode the string, it raises the exception `e_failure`.

  For example:

      set string "Keld Jørn Simonsen <keld@dkuug.dk>"
      message_header_encode(string, "ISO-8859-1")
        ⇒ "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>"

Built-in Function: `string` message_header_decode `(string text, [string charset])` ¶

  text must be a header value encoded in accordance with RFC 2047. The function returns the decoded string. If the decoding fails, it raises `e_failure` exception. The optional argument charset specifies the character set to use (default – ‘`UTF-8`’).

      set string "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>"
      message_header_decode(string)
       ⇒ "Keld Jørn Simonsen <keld@dkuug.dk>"

Built-in Function: `string` unfold `(string text)` ¶

  If text is a “folded” multi-line RFC 2822 header value, unfold it. If text is a single-line string, return its unchanged copy.

  For example, suppose that the message being processed contained the following header:

      List-Id: Sent bugreports to
        <some-address@some.net>

  Then, applying `unfold` to its value24 will produce:

      Sent bugreports to <some-address@some.net>

### 5.15 Mail Body Functions ¶

Built-in Function: `string` body_string `(pointer text, number count)` ¶

  Converts first count bytes from the memory location pointed to by text into a regular string.

  This function is intended to convert the `$1` argument passed to a `body` handler to a regular MFL string. For more information about its use, see body handler.

Built-in Function: `bool` body_has_nulls `(pointer text, number count)` ¶

  Returns ‘`True`’ if first count bytes of the string pointed to by text contain ASCII NUL characters.

  Example:

      prog body
      do
        if body_has_nulls($1, $2)
          reject
        fi
      done

### 5.16 EOM Functions ¶

The following function is available only in the ‘`eom`’ handler:

Built-in Function: `void` progress `()` ¶

  Notify the MTA that the filter is still processing the message. This causes MTA to restart its timeouts and allows additional amount of time for execution of ‘`eom`’.

  Use this function if your ‘`eom`’ handler needs additional time for processing the message (e.g. for scanning a very big MIME message). You may call it several times, if the need be, although such usage is not recommended.

### 5.17 Current Message Functions ¶

Built-in Function: `number` current_message `()` ¶

  This function can be used in `eom` handlers only. It returns a message descriptor referring to the current message. See Message Functions, for a description of functions for accessing messages.

The functions below access the headers from the current message. They are available in the following handlers: `eoh`, `body`, `eom`.

Built-in Function: `number` current_header_count `([string name])` ¶

  Return number of headers in the current message. If name is specified, return number of headers that have this name.

        current_header_count() ⇒ 6
        current_header_count("Subject") ⇒ 1

Built-in Function: `string` current_header_nth_name `(number n)` ¶

  Return the name of the nth header. The index n is 1-based.

Built-in Function: `string` current_header_nth_value `(number n)` ¶

  Return the value of the nth header. The index n is 1-based.

Built-in Function: `string` current_header `(string name [, number n])` ¶

  Return the value of the named header, e.g.:

        set s current_header("Subject")

  Optional second argument specifies the header instance, if there are more than 1 header of the same name, e.g.:

        set s current_header("Received", 2)

  Header indices are 1-based.

All current_header function raise the `e_not_found` exception if the requested header was not found.

### 5.18 Mailbox Functions ¶

A set of functions is provided for accessing mailboxes and messages within them. In this subsection we describe the functions for accessing mailboxes.

A mailbox is opened using `mailbox_open` function:

Built-in Function: `number` mailbox_open `(string url [, string mode, string perms])` ¶

  Open a mailbox identified by url. Return a mailbox descriptor: a unique numeric identifier that can subsequently be used to access this mailbox.

  The optional mode argument specifies the access mode for the mailbox. Its valid values are:

  Value | Meaning
  --- | ---
  r | Open mailbox for reading. This is the default.
  w | Open mailbox for writing. If the mailbox does not exist, it is created.
  rw | Open mailbox for reading and writing. If the mailbox does not exist, it is created.
  wr | Same as ‘`rw`’.
  w+ | Open mailbox for reading and writing. If the mailbox does not exist, it is created.
  a | Open mailbox for appending messages to it. If the mailbox does not exist, an exception is signalled.
  a+ | Open mailbox for appending messages to it. If the mailbox does not exist, it is created.

  The optional perms argument specifies the permissions to use in case a new file (or files) is created. It is a comma-separated list of:

      [go](+|=)[wr]+

  The initial letter controls which users’ access is to be set: users in the file’s group (‘`g`’) or other users not in the file’s group (‘`o`’). The following character controls whether the permissions are added to the default ones (‘`+`’) or applied instead of them (‘`=`’). The remaining letters specify the permissions: ‘`r`’ for read access and ‘`w`’ for write access. For example:

      g=rw,o+r

The number of mailbox descriptors available for simultaneous opening is 64. This value can be changed using the `max-open-mailboxes` runtime configuration statement (see max-open-mailboxes).

Built-in Function: `number` mailbox_messages_count `(number nmbx)` ¶

  Return the number of messages in mailbox. The argument nmbx is a valid mailbox descriptor as returned by a previous call to `mailbox_open`.

Built-in Function: `number` mailbox_get_message `(number mbx, number n)` ¶

  Retrieve nth message from the mailbox identified by descriptor mbx. On success, the function returns a message descriptor, an integer number that can subsequently be used to access that message (see Message Functions). On error, an exception is raised.

  Messages in a mailbox are numbered starting from 1.

Built-in Function: `void` mailbox_close `(number nmbx)` ¶

  Close a mailbox previously opened by `mailbox_open`.

Built-in Function: `void` mailbox_append_message `(number nmbx, number nmsg)` ¶

  Append message nmsg to mailbox nmbx. The message descriptor nsmg must be obtained from a previous call to `mailbox_get_message` or `current_message` (see current_message).

### 5.19 Message Functions ¶

The functions described below retrieve information from RFC822 messages. The message to operate upon is identified by its descriptor, an integer number returned by the previous call to `mailbox_get_message` (see mailbox_get_message) or `current_message` (see current_message) function. The maximum number of message descriptors is limited by 1024. You can change this limit using the `max-open-messages` runtime configuration statement (see max-open-messages).

Built-in Function: `number` message_size `(number nmsg)` ¶

  Return the size of the message nmsg, in bytes. Notice, that if nmsg refers to current message (see current_message), the returned value is less than the size seen by the MTA, because `mailfromd` recodes CR-LF sequences to LF, i.e. removes carriage returns (ASCII 13) occurring before line feeds (ASCII 10. To obtain actual message length as seen by the MTA, add the number of lines in the message:

        set actual_length message_size(nmsg) + message_lines(nmsg)

Built-in Function: `boolean` message_body_is_empty `(number nmsg)` ¶

  Returns `true` if the body of message nmsg has zero size or contains only whitespace characters. If the ‘`Content-Transfer-Encoding`’ header is present, it is used to decode body before processing.

Built-in Function: `void` message_close `(number nmsg)` ¶

  Close the message identified by descriptor nmsg.

Built-in Function: `number` message_lines `(number nmsg)` ¶

  Return total number of lines in message nmsg. The following relation holds true:

      message_lines(x) = message_body_lines(x)
                               + message_header_lines(x) + 1

Built-in Function: `string` message_read_line `(number nmsg)` ¶

  Read and return next line from the message nmsg. If there are no more lines to read, raise the `eof` exception.

  Use `message_rewind` to rewind the message stream and read its contents again.

Built-in Function: `void` message_rewind `(number nmsg)` ¶

  Rewind the stream associated with message referred to by descriptor nmsg.

Built-in Function: `number` message_from_stream `(number fd; string filter_chain)` ¶

  Converts contents of the stream identified by fd to a mail message. Returns identifier of the created message.

  Optional filter_chain supplies the name of a Mailutils filter chain, through which the data will be passed before converting. See http://mailutils.org/wiki/Filter_chain, for a description of filter chains.

Built-in Function: `void` message_to_stream `(number fd, number nmsg; string filter_chain)` ¶

  Copies message nsmg to stream descriptor fd. The descriptor must be obtained by a previous call to `open`.

  Optional filter_chain supplies the name of a Mailutils filter chain, through which the data will be passed before writing them to fd. See http://mailutils.org/wiki/Filter_chain, for a description of filter chains.

#### 5.19.1 Header functions ¶

Built-in Function: `number` message_header_size `(number nmsg)` ¶

  Return the size, in bytes of the headers of message nmsg. See the note to the `message_size`, above.

Built-in Function: `number` message_header_lines `(number nmsg)` ¶

  Return number of lines occupied by headers in message nmsg.

Built-in Function: `number` message_header_count `(number nmsg, [string name])` ¶

  Return number of headers in message nmsg.

  If name is supplied, count only headers with that name.

Built-in Function: `string` message_find_header `(number nmsg, string name [, number idx])` ¶

  Return value of header name from the message nmsg. If the message contains several headers with the same name, optional parameter idx may be used to select one of them. Headers are numbered from ‘`1`’.

  If no matching header is not found, the `not_found` exception is raised. If another error occurs, the `failure` exception is raised.

  The returned string is a verbatim copy of the message contents (except for eventual CR-LF -> LF translation, see above). You might need to apply the `unfold` function to it (see unfold).

Built-in Function: `string` message_nth_header_name `(number nmsg, number n)` ¶

  Returns the name of the nth header in message nmsg. If there is no such header, `e_range` exception is raised.

Built-in Function: `string` message_nth_header_value `(number msg, number n)` ¶

  Returns the value of the nth header in message nmsg. If there is no such header, `e_range` exception is raised.

Built-in Function: `boolean` message_has_header `(number nmsg, string name [, number idx])` ¶

  Return `true` if message nmsg contains header with the given name. If there are several headers with the same name, optional parameter idx may be used to select one of them.

#### 5.19.2 Message body functions ¶

Built-in Function: `number` message_body_size `(number nmsg)` ¶

  Return the size, in bytes, of the body of message nmsg. See the note to the `message_size`, above.

Built-in Function: `number` message_body_lines `(number nmsg)` ¶

  Return number of lines in the body of message referred to by descriptor nmsg.

Built-in Function: `void` message_body_rewind `(number nmsg)` ¶

  Rewind the stream associated with the body of message referred to by descriptor nmsg.

  A call to `message_body_read_line` (see below) after calling this function will return the first line from the message body.

Built-in Function: `string` message_read_body_line `(number nmsg)` ¶

  Read and return next line from the body of the message nmsg. If there are no more lines to read, raise the `eof` exception.

  Use `message_body_rewind` (see above) to rewind the body stream and read its contents again.

Built-in Function: `void` message_body_to_stream `(number fd, number nmsg; string filter_pipe)` ¶

  Copies the body of the message nsmg to stream descriptor fd. The descriptor must be obtained by a previous call to `open`.

  Optional filter_pipe supplies a sequence of Mailutils filters, through which the data will be passed before writing them to fd. See Filtering functions, for a discussion of filter pipe syntax.

  In addition to filters described in See Filters and Filter Pipes, two special filters are provided for use with this function: `mimedecode` and `charset`. The `mimedecode` filter instructs the function to decode the message body by reverting the encoding specified by its `Content-Transfer-Encoding` header. It is normally used as the very first filter in chain. The `charset` filter recodes the message body from it original character set to the character set specified as its argument.

  See mimedecode, for a detailed discussion of this feature.

#### 5.19.3 MIME functions ¶

Built-in Function: `boolean` message_is_multipart `(number nmsg)` ¶

  Return `true` if message nmsg is a multipart (MIME) message.

Built-in Function: `number` message_count_parts `(number nmsg)` ¶

  Return number of parts in message nmsg, if it is a multipart (MIME) message. If it is not, return ‘`1`’.

  Use `message_is_multipart` to check whether the message is a multipart one.

Built-in Function: `number` message_get_part `(number nmsg, number n)` ¶

  Extract nth part from the multipart message nmsg. Numeration of parts begins from ‘`1`’. Return message descriptor referring to the extracted part. Message parts are regarded as messages, so any message functions can be applied to them.

Built-in Function: `string` message_content_type `(number nmsg)` ¶

  Returns content type for the message nmsg. The returned string is composed of content type and subtype, delimited by slash.

  If nmsg is not a multipart message, the function returns ‘`text/plain`’.

Several functions are provided for decoding multi-part messages. Such decoding is governed by `Content-Transfer-Encoding` and `Content-Type` headers of the message. The `Content-Transfer-Encoding` header defines the method used to encode the message. The value of `Content-Type` header is used to determine the character set the body is written in.

Basic MIME decoding facilities are provided by the built-in function `message_body_to_stream`, described in the previous subsection. To instruct it to decode the content, pass it the filter_chain argument beginning with the word `mimedecode`. The usual sequence is:

    set fd open("> outfile")
    message_body_to_stream(fd, msg, "mimedecode")

To ensure that the produced stream is represented in a specific character set, use the `charset` special filter. Its argument is the name of the character set to recode the text to:

    set fd open("> outfile")
    message_body_to_stream(fd, msg, "mimedecode|charset(utf-8)")

The `charset` filter takes also an optional second argument – a fallback method, specifying what to do when an octet sequence is encountered that cannot be represented in the requested character set. Possible values for this argument are:

‘`none`’

  Stop further conversion and signal the `e_ilseq` exception.

‘`copy-pass`’

  Copy the offending character to the output verbatim.

‘`copy-octal`’

  Represent the offending character as a C octal sequence (‘`\nnn`’, where n is an octal digit). This is the default.

To decode a particular part of the message, first extract it using the `message_get_part` function. Recall that message parts are messages as well, and as such can be passed to `message_body_to_stream`. For example, the following code fragment extracts all top-level parts of a multi-part message to files named ‘`part.N`’:

    if message_is_multipart(msg)
      set n message_count_parts(msg)
      loop for set i 1, while i <= n, set i i + 1
      do
        set fd open("> part.%i")
        message_body_to_stream(fd, message_get_part(msg, i), "mimedecode")
        close(fd)
      done
    fi

The `mime.mfl` module provides additional functions for decoding multi-part messages:

Library Function: `number` message_body_decode `(number nmsg; string charset, string fallback)` ¶

  Decodes the body of the message (or message part) nmsg, optionally converting it to the given charset. The fallback argument specifies what to do if a byte sequence cannot be converted to the specified character set. See iconv fallback, for a detailed discussion.

  The function returns a descriptor of the I/O stream that contains the decoded material. See I/O functions for a discussion of functions available for reading from it.

Library Function: `number` message_part_decode(number `nmsg, number part; string charset, string fallback)` ¶

  Decodes the body of the given part of a MIME message nmsg. The argument part is a 1-based index of the part in the message. Optional arguments charset and fallback have the same meaning as in `message_body_decode` (see above).

  Returns a descriptor of the I/O stream that contains the decoded material.

  This function is equivalent to:

      message_body_decode(message_get_part(nmsg, part, charset,
                          fallback))

#### 5.19.4 Message digest functions ¶

Message digests are specially formatted messages that contain certain number of mail messages, encapsulated using the method described in RFC 934. Such digests are often used in mailing lists to reduce the frequency of sending mails. Messages of this format are also produced by the forward function in most MUA’s.

The usual way to handle a message digest in MFL is to convert it first to a MIME message, and then to use functions for accessing its parts (see MIME functions).

Built-in Function: `number` message_burst `(number nmsg ; number flags)` ¶

  Converts the message identified by the descriptor nmsg to a multi-part message. Returns a descriptor of the created message.

  Optional argument flags controls the behavior of the bursting agent. It is a bitwise OR of error action and bursting flags.

  Error action defines what to do if a part of the digest is not in RFC822 message format. If it is ‘`BURST_ERR_FAIL`’ (the default), the function will raise the ‘`e_format`’ exception. If onerr is ‘`BURST_ERR_IGNORE`’, the improperly formatted part will be ignored. Finally, the value ‘`BURST_ERR_BODY`’ instructs `message_burst` to create a replacement part with empty headers and the text of the offending part as its body.

  Bursting flags control various aspects of the agent behavior. Currently only one flag is defined, ‘`BURST_DECODE`’, which instructs the agent to decode any MIME parts (according to the ‘`Content-Transfer-Encoding`’ header) it encounters while bursting the message.

Parts of a message digest are separated by so-called encapsulation boundaries, which are in essence lines beginning with at least one dash followed by a non-whitespace character. A dash followed by a whitespace serves as a byte-stuffing character, a sort of escape for lines which begin with a dash themselves. Unfortunately, there are mail agents which do not follow byte-stuffing rules and pass lines beginning with dashes unmodified into resulting digests. To help handle such cases a global variable is provided which controls how much dashes should the line begin with for it to be recognized as an encapsulation boundary.

Built-in variable: `number` burst_eb_min_length ¶

  Minimal number of consecutive dashes an encapsulation boundary must begin with.

  The default is 2.

The following example shows a function which saves all parts of a digest message to separate disk files. The argument orig is a message descriptor. The resulting files are named by concatenating the string supplied by the stem argument and the ordinal number (1-based) of the message part.

    func burst_digest(number orig, string stem)
    do
      number msg message_burst(orig)
      number nparts message_count_parts(msg)

      loop for number i 1,
           while i <= nparts,
           set i i + 1
      do
        number part message_get_part(msg, i)
        number out open(sprintf('>%s%02d', stem, i))
        message_to_stream(out, part)
      done
      message_close(msg)
    done

### 5.20 Quarantine Functions ¶

Built-in Function: `void` quarantine `(string text)` ¶

  Place the message to the quarantine queue, using text as explanatory reason.

### 5.21 SMTP Callout Functions ¶

Library Function: `number` callout_open `(string url)` ¶

  Opens connection to the callout server listening at url. Returns the descriptor of the connection.

Library Function: `void` callout_close `(number fd)` ¶

  Closes the connection. fd is the file descriptor returned by the previous call to `callout_open`.

Library Function: `number` callout_do `(number fd, string email [, string rest])` ¶

  Instructs the callout server identified by fd (a file descriptor returned by a previous call to `callout_open`) to verify the validity of the email. Optional rest argument supplies additional parameters for the server. It is formatted as a sequence of parameter settings delimited by whitespaces. Each setting is a parameter name and value separated by a ‘`=`’ sign. See callout parameters, for a discussion of available callout parameters.

  Possible return values:

  `0`

    Success. The email is found to be valid.

  `e_not_found`

    email does not exist.

  `e_temp_failure`

    The email validity cannot be determined right now, e.g. because remote SMTP server returned temporary failure. The caller should retry verification later.

  `e_failure`

    Some error occurred.

  The function will throw the `e_callout_proto` exception if the remote host doesn’t speak the correct callout protocol.

  Upon return, `callout_do` modifies the following variables:

  `last_poll_host`

    Host name or IP address of the last polled SMTP server.

  `last_poll_greeting`

    Initial SMTP reply from the last polled host.

  `last_poll_helo`

    The reply to the `HELO` (`EHLO`) command, received from the last polled host.

  `last_poll_sent`

    Last SMTP command sent to the polled host. If nothing was sent, `last_poll_sent` contains the string ‘`nothing`’.

  `last_poll_recv`

    Last SMTP reply received from the remote host. In case of multi-line replies, only the first line is stored. If nothing was received the variable contains the string ‘`nothing`’.

The default callout server is defined by the `callout-url` statement in the configuration file, or by the `callout` statement in the `server milter` section (see configuring default callout server. The following functions operate on that server.

Built-in Function: `string` default_callout_server_url `()` ¶

  Returns URL of the default callout server.

Library Function: `number` callout `(string email)` ¶

  Verifies the validity of the email using the default callout server.

### 5.22 Compatibility Callout Functions ¶

The following functions are wrappers over the callout functions described in the previous section. They are provided for backward compatibility.

These functions are defined in the module `poll.mfl`, which you must require prior to using any of them.

Library Function: `boolean` _pollhost `(string ip, string email, string domain, string mailfrom)` ¶

  Poll SMTP host ip for email address email, using domain as `EHLO` domain and mailfrom as `MAIL FROM`. Returns 0 or 1 depending on the result of the test. In contrast to the `strictpoll` function, this function does not use cache database and does not fall back to polling MX servers if the main poll tempfails. The function can throw one of the following exceptions: `e_failure`, `e_temp_failure`.

Library Function: `boolean` _pollmx `(string ip, string email, string domain, string mailfrom)` ¶

  Poll MXs of the domain for email address email, using domain as `EHLO` domain and mailfrom as `MAIL FROM` address. Returns 0 or 1 depending on the result of the test. In contrast to the `stdpoll` function, `_pollmx` does not use cache database and does not fall back to polling the ip if the poll fails. The function can throw one of the following exceptions: `e_failure`, `e_temp_failure`.

Library Function: `boolean` stdpoll `(string email, string domain, string mailfrom)` ¶

  Performs standard poll for email, using domain as `EHLO` domain and mailfrom as `MAIL FROM` address. Returns 0 or 1 depending on the result of the test. Can raise one of the following exceptions: `e_failure`, `e_temp_failure`.

  In `on` statement context, it is synonymous to `poll` without explicit host.

Library Function: `boolean` strictpoll `(string host, string email, string domain, string mailfrom)` ¶

  Performs strict poll for email on host host. See the description of `stdpoll` for the detailed information.

  In `on` context, it is synonymous to `poll host host`.

The mailfrom argument can be a comma-separated list of email addresses, which can be useful for servers that are unusually picky about sender addresses. It is advised, however, that this list always contain the ‘`<>`’ address. For example:

    _pollhost($client_addr, $f, "domain", "postmaster@my.net,<>")

See also mail-from-address.

Before returning, all described functions set the following built-in variables:

Variable | Contains
--- | ---
last_poll_host | Host name or IP address of the last polled host.
last_poll_sent | Last SMTP command, sent to this host. If nothing was sent, it contains literal string ‘`nothing`’.
last_poll_recv | Last SMTP reply received from this host. In case of multi-line replies, only the first line is stored. If nothing was received the variable contains the string ‘`nothing`’.
cache_used | `1` if cached data were used instead of polling, `0` otherwise. This variable is set by `stdpoll` and `strictpoll`. If it equals `1`, none of the above variables are modified. See cache_used example, for an example.

Table 5.1: Variables set by polling functions

### 5.23 Internet address manipulation functions ¶

Following functions operate on IPv4 addresses in numeric form.

Built-in Function: `number` ntohl `(number n)` ¶

  Converts the number n, from host to network byte order. The argument n is treated as an unsigned 32-bit number.

Built-in Function: `number` htonl `(number n)` ¶

  Converts the number n, from network to host byte order. The argument n is treated as an unsigned 32-bit number.

Built-in Function: `number` ntohs `(number n)` ¶

  The argument n is treated as an unsigned 16-bit number. The function converts this number from network to host order.

Built-in Function: `number` htons `(number n)` ¶

  The argument n is treated as an unsigned 16-bit number. The function converts this number from host to network order.

Built-in Function: `number` inet_aton `(string s)` ¶

  Converts the Internet host address s from the standard numbers-and-dots notation into the equivalent integer in host byte order.

      inet_aton("127.0.0.1") ⇒ 2130706433

  The numeric data type in MFL is signed, therefore on machines with 32 bit integers, this conversion can result in a negative number:

      inet_aton("255.255.255.255") ⇒ -1

  However, this does not affect arithmetical operations on IP addresses.

Built-in Function: `string` inet_ntoa `(number n)` ¶

  Converts the Internet host address n, given in host byte order to string in standard numbers-and-dots notation:

      inet_ntoa(2130706433) ⇒ "127.0.0.1"

Built-in Function: `number` len_to_netmask `(number n)` ¶

  Convert number of masked bits n to IPv4 netmask:

      inet_ntoa(len_to_netmask(24)) ⇒ 255.255.255.0
      inet_ntoa(len_to_netmask(7)) ⇒ 254.0.0.0

  If n is greater than 32 the function raises `e_range` exception.

Built-in Function: `number` netmask_to_len `(number mask)` ¶

  Convert IPv4 netmask mask into netmask length (number of bits preserved by the mask):

      netmask_to_len(inet_aton("255.255.255.0")) ⇒ 24
      netmask_to_len(inet_aton("254.0.0.0")) ⇒ 7

The following functions operate on string representation of IPv4 and IPv6 addresses.

Built-in Function: `string` reverse_ipstr(string `ip)` ¶

  Converts the IP address ip to reverse format suitable for use in DNS labels. That is, if ip is an IPv4 address, the return value is obtained by reversing the order of octets in the input:

      reverse_ipstr("192.0.2.10") ⇒ "10.2.0.192"

  If IP is an IPv6 address, the return string is formed as a sequence of nibbles separated by dots, encoded in reverse order, i.e. the low-order nibble is encoded first, followed by the next low-order nibble and so on. Each nibble is represented by a hexadecimal digit appending each byte from the IP represented in hex from the last to first, delimited by dots, e.g.:

      reverse_ipstr("2001:db8:0:0:1::2")
      ⇒
      "2.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2"

Built-in Function: `number` is_ipstr(string `s)` ¶

  Returns 1 if s is a string representation of an IP address (IPv4 or IPv6) and 0 otherwise.

Built-in Function: `number` is_ip4str(string `s)` ¶

  Returns 1 if s is a string representation of an IPv4 address, and 0 otherwise.

Built-in Function: `number` is_ip6str(string `s)` ¶

  Returns 1 if s is a string representation of an IPv6 address, and 0 otherwise.

The following functions operate on IP addresses or CIDRs represented as strings:

Built-in Function: `boolean` match_cidr `(string ip, string cidr)` ¶

  It returns `true` if the IP address ip pertains to the IP range cidr. The first argument, ip, is a string representation of an IP address (IPv4 or IPv6). The second argument, cidr, is a string representation of a IP range in CIDR notation, i.e. ‘`addr/N`’, where addr is IP address and N specifies address prefix length – the number of meaningful initial bits, counting from the left side of the address.

  The following example will reject the mail if the IP address of the sending machine does not belong to the block `192.0.2.0/24`:

      if not match_cidr(${client_addr}, "192.0.2.0/24")
        reject
      fi

  The following example does the same for a IPv6 CIDR `2001:DB8::/56`:

      if not match_cidr(${client_addr}, "2001:DB8::/56")
        reject
      fi

  Notice, that in previous versions of `mailfromd` this function was implemented as MFL function and required the use of `match_cidr.mfl` module (see Upgrading from 8.17 to 9.00. This is no longer the case. The module itself is retained in the distribution for compatibility reasons. If your code uses it, you will see the following warning during compilation phase (split in two lines for typesetting reasons):

      mailfromd: match_cidr.mfl:18.1-78: warning: This module is
      deprecated. The match_cidr function is now built-in.

  Just remove the `require 'match_cidr'` from your code to make this warning disappear.

### 5.24 DNS Functions ¶

MFL offers two sets of functions for querying the Domain Name System. The `dns_query` function and associated `dns_reply_` functions provide a generalized DNS API.

Other functions provide a simplified API.

#### 5.24.1 dns_query ¶

Built-in Function: `number` dns_query `(number type, string domain; number sort, number resolve)` ¶

  This function looks up the domain name name. The type argument specifies type of the query to perform. On success, the function returns DNS reply descriptor, a non-negative integer number identifying the reply. It can then be passed to any of the ‘`dns_reply_`’ functions discussed below in order to retrieve the information from it.

  If no matching records were found, the function returns ‘`-1`’.

  On error, it throws a corresponding exception.

  The type argument is one of the following constants (defined in the module ‘`dns`’):

  `DNS_TYPE_A` ¶

    Query the ‘`A`’ record. The domain should be the hostname to look up.

  `DNS_TYPE_NS` ¶

    Query the ‘`NS`’ records.

  `DNS_TYPE_PTR` ¶

    Query the ‘`PTR`’ record. The domain address should be the IP address in dotted-quad form.

  `DNS_TYPE_MX` ¶

    Query the ‘`MX`’ records.

  `DNS_TYPE_TXT` ¶

    Query the ‘`TXT`’ records.

  If the query returns multiple RR sets, the optional argument sort controls whether they should be returned in the same order as obtained from the DNS (0, the default), or should be sorted (1).

  Optional argument resolve is consulted if type is `DNS_TYPE_MX` or `DNS_TYPE_NS`. By default, queries for these types return hostnames. The resolve argument controls whether to return IP addresses instead. Its possible values (defined in module `status.mfl` are:

  `RESOLVE_NONE` ¶

    Don’t resolve hostnames to IP addresses. This is the default.

  `RESOLVE_DFL` ¶

    Resolve hostnames to IP addresses according to the address family of the SMTP session. That is, use ‘`A`’ records if the client connected using the INET family (i.e. connected to the IPv4 address), and use ‘`AAAA`’ records if the client connected to the IPv6 address.

  `RESOLVE_IP4` ¶

    Resolve hostnames to IPv4 addresses (‘`A`’ records).

  `RESOLVE_IP6` ¶

    Resolve hostnames to IPv6 addresses (‘`AAAA`’ records).

  To extract actual data from the `dns_query` return value, use the functions `dns_reply_count` and `dns_reply_string`. The usual processing sequence is:

        require dns

        # Send the query and save the reply descriptor
        set n dns_query(DNS_TYPE_NS, domain_name)

        if n >= 0
          # If non-empty set is returned, iterate over each value in it:
          loop for set i 0,
               while i < dns_reply_count(n),
               set i i + 1
          do
            # Get the actual data:
            echo dns_reply_string(n, i)
          done
          # Release the memory associated with the reply.
          dns_reply_release(n)
        fi

Built-in Function: `void` dns_reply_release `(number rd)` ¶

  Release the memory associated with the reply rd. If rd is -1, the function does nothing.

Built-in Function: `number` dns_reply_count `(number rd)` ¶

  Return the number of records in the reply rd. For convenience, if rd is -1, the function returns 0. If rd is negative (excepting -1), a ‘`e_failure`’ exception is thrown.

Built-in Function: `string` dns_reply_string `(number rd, number n)` ¶

  Returns nth record from the DNS reply rd.

Built-in Function: `number` dns_reply_ip `(number rd, number n)` ¶

  Returns nth record from the DNS reply rd, if the reply contains IPv4 addresses.

#### 5.24.2 Simplified DNS functions ¶

These functions are implemented in two layers: primitive built-in functions which raise exceptions if the lookup fails, and library calls that are warranted to always return meaningful value without throwing exceptions.

The built-in layer is always available. The library calls become available after requesting the `dns` module (see Modules):

    require dns

Library Function: `string` dns_getaddr `(string domain)` ¶

  Returns a whitespace-separated list of IP addresses (`A` records) for domain.

Library Function: `string` dns_getname `(string ipstr)` ¶

  Returns a whitespace-separated list of domain names (`PTR` records) for the IPv4 address ipstr.

Library Function: `string` getmx `(string domain [, boolean ip])` ¶

  Returns a whitespace-separated list of ‘`MX`’ names (if ip is not given or if it is `0`) or ‘`MX`’ IP addresses (if `ip!=0`)) for domain. Within the returned string, items are sorted in order of increasing ‘`MX`’ priority. If domain has no ‘`MX`’ records, an empty string is returned. If the DNS query fails, `getmx` raises an appropriate exception.

  Examples:

      getmx("mafra.cz") ⇒ "smtp1.mafra.cz smtp2.mafra.cz relay.iol.cz"
      getmx("idnes.cz") ⇒ "smtp1.mafra.cz smtp2.mafra.cz relay.iol.cz"
      getmx("gnu.org")  ⇒ "mx10.gnu.org mx20.gnu.org"
      getmx("org.pl") ⇒ ""

  Notes:

  1. Number of items returned by `getmx(domain)` can differ from that obtained from `getmx(domain, 1)`, e.g.:

         getmx("aol.com")
           ⇒ mailin-01.mx.aol.com mailin-02.mx.aol.com
                     mailin-03.mx.aol.com mailin-04.mx.aol.com
         getmx("aol.com", 1)
           ⇒ 64.12.137.89 64.12.137.168 64.12.137.184
                     64.12.137.249 64.12.138.57 64.12.138.88
                     64.12.138.120 64.12.138.185 205.188.155.89
                     205.188.156.185 205.188.156.249 205.188.157.25
                     205.188.157.217 205.188.158.121 205.188.159.57
                     205.188.159.217

  2. This function is a wrapper over `dns_query`.

     If you intend to iterate over returned values, better use `dns_query` directly, e.g. instead of doing

         string_list_iterate(getmx(domain), ` ', MX, `do_something(MX)')

     use

           set n dns_query(DNS_TYPE_MX, domain)
           if n >= 0
             loop for set i 0,
                  while i < dns_reply_count(n),
                  set i i + 1
             do
               do_something(dns_reply_string(n, i))
             done
             dns_reply_release(n)
           fi

     See dns_query, for details about the `dns_query` function and associated `dns_reply_*` calls.

  3. This interface is semi-deprecated.

     It will most probably be removed in future releases, when array data types are implemented.

Built-in Function: `boolean` primitive_hasmx `(string domain)` ¶

  Returns `true` if the domain name given by its argument has any ‘`MX`’ records.

  If the DNS query fails, this function throws `failure` or `temp_failure`.

Library Function: `boolean` hasmx `(string domain)` ¶

  Returns `true` if the domain name given by its argument has any ‘`MX`’ records.

  Otherwise, if domain has no ‘`MX`’s or if the DNS query fails, `hasmx` returns `false`.

Built-in Function: `string` primitive_hostname `(string ip)` ¶

  The ip argument should be a string representing an IP address in dotted-quad notation. The function returns the canonical name of the host with this IP address obtained from DNS lookup. For example

      primitive_hostname (${client_addr})

  returns the fully qualified domain name of the host represented by Sendmail variable ‘`client_addr`’.

  If there is no ‘`PTR`’ record for ip, `primitive_hostname` raises the exception `e_not_found`.

  If DNS query fails, the function raises `failure` or `temp_failure`, depending on the character of the failure.

Library Function: `string` hostname `(string ip)` ¶

  The ip argument should be a string representing an IP address in dotted-quad notation. The function returns the canonical name of the host with this IP address obtained from DNS lookup.

  If there is no ‘`PTR`’ record for ip, or if the lookup fails, the function returns ip unchanged.

  The previous `mailfromd` versions used the following paradigm to check if an IP address resolves:

        if hostname(ip) != ip
          ...

Built-in Function: `boolean` primitive_ismx `(string domain, string host)` ¶

  The domain argument is any valid domain name, the host is a host name or IP address.

  The function returns `true` if host is one of the ‘`MX`’ records for the domain.

  If domain has no ‘`MX`’ records, `primitive_ismx` raises exception `e_not_found`.

  If DNS query fails, the function raises `failure` or `temp_failure`, depending on the character of the failure.

Library Function: `boolean` ismx `(string domain, string host)` ¶

  The domain argument is any valid domain name, the host is a host name or IP address.

  The function returns `true` if host is one of the ‘`MX`’ records for the domain. Otherwise it returns `false`.

  If domain has no ‘`MX`’ records, or if the DNS query fails, the function returns `false`.

Built-in Function: `string` primitive_resolve `(string host, [string domain, number family])` ¶

  Reverse of `primitive_hostname`. The `primitive_resolve` function returns the IP address for the host name specified by its host argument. If the SMTP session uses IPv4 protocol, ‘`A`’ record is queried. It it uses IPv6, ‘`AAAA`’ record is queried. A particular record type can be requested via optional family, which can have one of the following values (defined in `status.mfl`):

  `RESOLVE_DFL` ¶

    Look for ‘`A`’ or ‘`AAAA`’, depending on the connection type. This is the default.

  `RESOLVE_IP4` ¶

    Resolve to IPv4 addresses (‘`A`’ records).

  `RESOLVE_IP6` ¶

    Resolve to IPv6 addresses (‘`AAAA`’ records).

  If host has no records of the requested type, the function raises the exception `e_not_found`.

  If DNS lookup fails, the function raises `failure` or `temp_failure`, depending on the character of the failure.

  Optional domain argument is deprecated. If a non-empty string is given as domain, the function works as follows:

  1. If domain is ‘`in-addr.arpa`’ (case-insensitive)

     The host must be a string representation of an IPv4 address in dotted-quad form. If it is not, a `e_inval` exception is thrown. The octets in the IPv4 are reversed, a dot and ‘`domain`’ are appended to it, and a ‘`PTR`’ record is queried for the resulting name.

     Thus, the call

         primitive_resolve("192.0.2.1", "in-addr.arpa")

     is equivalent to

         primitive_hostname("192.0.2.1")

  2. domain is ‘`ip6.arpa`’ (case-insensitive)

     The host must be a string representation of an IPv6. If it is not, a `e_inval` exception is thrown. The octets of this IPv6 address are reversed, a dot and ‘`domain`’ are appended to it, and a ‘`PTR`’ record is queried for the resulting name.

     Thus, the call

         primitive_resolve("2001:DB8::1", "ip6.arpa")

     is equivalent to

         primitive_hostname("2001:DB8::1")

  3. host is a string representation of an IPv4 address

     The address is reversed as in (1), then a dot and ‘`domain`’ are appended to it. Finally, the DNS is queried for an ‘`A`’ record of the resulting name.

     Thus,

         primitive_resolve("192.0.2.1", "rev.example.com")

     is equivalent to

         primitive_resolve("1.2.0.192.rev.example.com")

  4. host is a string representation of an IPv6 address

     The address is reversed as in (2), then a dot and ‘`domain`’ are appended to it. Finally, the DNS is queried for an ‘`AAAA`’ record of the resulting name.

  5. None of the above. Same as `primitive_hostname(‘`host.domain`’,'',resolve)`.

Library Function: `string` resolve `(string host, [string domain, number family])` ¶

  Reverse of `hostname`. The `resolve` function returns IP address for the host name specified by host argument. If the host name cannot be resolved, or a DNS failure occurs, the function returns ‘`"0"`’.

  This function is entirely equivalent to `primitive_resolve` (see above), except that it never raises exceptions.

Built-in Function: `string` ptr_validate `(string ip)` ¶

  Tests whether the DNS reverse-mapping for ip exists and correctly points to a domain name within a particular domain.

  First, it obtains all ‘`PTR`’ records for ip. Then, for each record returned, a look up for ‘`A`’ (or ‘`AAAA`’, if ip is an IPv6 address) records is performed and IP addresses of each record are compared against ip. The function returns true if a matching ‘`A`’ (or ‘`AAAA`’) record is found.

  This function can raise the following exceptions:

  `e_not_found`

    Unable to resolve IP address or hostname.

  `e_failure`

    Fatal error while resolving.

  `e_temp_failure`

    Temporary error in DNS resolution.

  `e_too_many`

    Too many CNAME records (see CNAME chains).

Built-in Function: `boolean` primitive_hasns `(string domain)` ¶

  Returns ‘`True`’ if the domain domain has at least one ‘`NS`’ record. Throws exception if DNS lookup fails.

Library Function: `boolean` hasns `(string domain)` ¶

  Returns ‘`True`’ if the domain domain has at least one ‘`NS`’ record. Returns ‘`False`’ if there are no ‘`NS`’ records or if the DNS lookup fails.

Library Function: `string` getns `(string domain ; boolean resolve, boolean sort)` ¶

  Returns a whitespace-separated list of all the ‘`NS`’ records for the domain domain. Optional parameters resolve and sort control the formatting. If resolve is 0 (the default), the resulting string will contain IP addresses of the NS servers. If resolve is not 0, hostnames will be returned instead. If sort is 1, the returned items will be sorted.

  If the DNS query fails, `getns` raises an appropriate exception.

  Notes:

  1. This function is a wrapper over `dns_query`.

     If you intend to iterate over returned values, better use `dns_query` directly, e.g. instead of doing

         string_list_iterate(getns(domain), ` ', NS, `do_something(NS)')

     use

           set n dns_query(DNS_TYPE_NS, domain)
           if n >= 0
             loop for set i 0,
                  while i < dns_reply_count(n),
                  set i i + 1
             do
               do_something(dns_reply_string(n, i))
             done
             dns_reply_release(n)
           fi

     See dns_query, for details about the `dns_query` function and associated `dns_reply_*` calls.

  2. This interface is semi-deprecated.

     It will most probably be removed in future releases, when array data types are implemented.

### 5.25 Geolocation functions ¶

The geolocation functions allow you to identify the country where the given IP address or host name is located. These functions are available only if the `libmaxminddb` library is installed and `mailfromd` is compiled with the ‘`GeoIP2`’ support.

The `libmaxminddb` library is distributed by ‘`MaxMind`’ under the terms of the Apache License Version 2.0. It is available from https://dev.maxmind.com/geoip/geoip2/downloadable/#MaxMind_APIs.

Built-in Function: `void` geoip2_open `(string filename)` ¶

  Opens the geolocation database file filename. The database must be in GeoIP2 format.

  If the database cannot be opened, `geoip2_open` throws the `e_failure` exception.

  If this function is not called, geolocation functions described below will try to open the database file ‘`/usr/share/GeoIP/GeoLite2-City.mmdb`’.

Built-in Function: `string` geoip2_dbname `(void)` ¶

  Returns the name of the geolocation database currently in use.

The geolocation database for each IP address, which serves as a look up key, stores a set of items describing this IP. This set is organized as a map of key-value pairs. Each key is a string value. A value can be a scalar, another map or array of values. Using JSON notation, the result of a look up in the database might look as:

    {
        "country":{
            "geoname_id":2921044,
            "iso_code":"DE",
            "names":{
                "en": "Germany",
                "de": "Deutschland",
                "fr":"Allemagne"
            },
        },
        "continent":{
            "code":"EU",
            "geoname_id":6255148,
            "names":{
                "en":"Europe",
                "de":"Europa",
                "fr":"Europe"
            }
        },
        "location":{
            "accuracy_radius":200,
            "latitude":49.4478,
            "longitude":11.0683,
            "time_zone":"Europe/Berlin"
        },
        "city":{
            "geoname_id":2861650,
            "names":{
                "en":"Nuremberg",
                "de":"Nürnberg",
                "fr":"Nuremberg"
            }
        },
        "subdivisions":[{
            "geoname_id":2951839,
            "iso_code":"BY",
            "names":{
                "en":"Bavaria",
                "de":"Bayern",
                "fr":"Bavière"
            }
        }
    }

Each particular data item in such structure is identified by its search path, which is a dot-delimited list of key names leading to that value. For example, using the above map, the name of the city in English can be retrieved using the key `city.names.en`.

Built-in Function: `string` geoip2_get `(string ip, string path)` ¶

  Looks up the IP address ip in the geolocation database. If found, returns data item identified by the search path path.

  The function can throw the following exceptions:

  e_not_found

    The ip was not found in the database.

  e_range

    The path does not exist the returned map.

  e_failure

    General error occurred. E.g. the database cannot be opened, ip is not a valid IP address, etc.

Built-in Function: `string` geoip2_get_json `(string ip [; number indent)` ¶

  Looks up the ip in the database and returns entire data set associated with it, formatted as a JSON object. If the optional parameter indent is supplied and is greater than zero, it gives the indentation for each nesting level in the JSON object.

Applications may test whether the GeoIP2 support is present and enable the corresponding code blocks conditionally, by testing if the ‘`WITH_GEOIP2`’ m4 macro is defined. For example, the following code adds to the message the ‘`X-Originator-Country`’ header, containing the 2 letter code of the country where the client machine is located. If `mailfromd` is compiled without the ‘`GeoIP2`’ support, it does nothing:

    m4_ifdef(`WITH_GEOIP2',`
      try
      do
        header_add("X-Originator-Country", geoip2_get($client_addr,
                                                      'country.iso_code'))
      done
      catch e_not_found or e_range
      do
        pass
      done
    ')

### 5.26 Database Functions ¶

The functions described below provide a user interface to DBM databases.

Each DBM database is a separate disk file that keeps key/value pairs. The interface allows to retrieve the value corresponding to a given key. Both ‘`key`’ and ‘`value`’ are null-terminated character strings. To lookup a key, it is important to know whether its length includes the terminating null byte. By default, it is assumed that it does not.

Another important database property is the file mode of the database file. The default file mode is ‘`640`’ (i.e. ‘`rw-r----`’, in symbolic notation).

These and other properties can be configured using the `dbprop` pragma:

    #pragma dbprop pattern prop [prop]

The pattern is the database name or shell-style globbing pattern. Properties defined by that pragma apply to each database whose name matches this pattern. If several `dbprop` pragmas match the database name, the one that matches exactly is preferred.

The rest of arguments define properties for that database. The valid values for prop are:

1. The word ‘`null`’, meaning that the terminating null byte is included in the key length.

   Setting ‘`null`’ property is necessary, for databases created with `makemap -N hash` command.

2. File mode for the disk file. It can be either an octal number, or a symbolic mode specification in ls-like format. E.g., the following two formats are equivalent:

       640
       rw-r----

3. DBM scheme, which specifies the type of the DBM (see Database Formats) to use for this database. The scheme consists of DBM type name followed by a colon and two slashes. E.g. ‘`gdbm://`’ means a GDBM database. See DBM scheme, for a detailed discussion.

For example, consider the following pragmas:

    #pragma dbprop /etc/mail/whitelist.db 640

It tells that the database file `whitelist.db` has privileges ‘`640`’ and do not include null in the key length.

Similarly, the following pragma:

    #pragma dbprop `/etc/mail/*.db' null 600 bdb://

declares that all database files in directory `/etc/mail` are Berkeley DB files, have privileges ‘`640`’, and include null terminator in the key length. Notice, the use of `m4` quoting characters in the example below. Without them, the sequence ‘`/*`’ would have been taken as the beginning of a comment.

Additionally, for compatibility with previous versions (up to 5.0), the terminating null property can be requested via an optional argument to the database functions (in description below, marked as null).

Built-in Function: `boolean` dbmap `(string db, string key, [boolean null])` ¶

  Looks up key in the DBM file db and returns `true` if it is found.

  See above for the meaning of null.

  See whitelisting, for an example of using this function.

Built-in Function: `string` dbget `(string db, string key [, string default, boolean null])` ¶

  Looks up key in the database db and returns the value associated with it. If the key is not found returns default, if specified, or empty string otherwise.

  See above for the meaning of null.

Built-in Function: `void` dbput `(string db, string key, string value [, boolean null, number mode ])` ¶

  Inserts in the database a record with the given key and value. If a record with the given key already exists, its value is replaced with the supplied one.

  See above for the meaning of null. Optional mode allows to explicitly specify the file mode for this database. See also `#pragma dbprop`, described above.

Built-in Function: `void` dbinsert `(string db, string key, string value [, boolean replace, boolean null, number mode ])` ¶

  This is an improved variant of `dbput`, which provides a better control on the actions to take if the key already exists in the database. Namely, if replace is ‘`True`’, the old value is replaced with the new one. Otherwise, the ‘`e_exists`’ exception is thrown.

Built-in Function: `void` dbdel `(string db, string key [, boolean null, number mode])` ¶

  Delete from the database the record with the given key. If there are no such record, return without signalling error.

  If the optional null argument is given and is not zero, the terminating null character will be included in key length.

  Optional mode allows to explicitly specify the file mode for this database. See also `#pragma dbprop`, described above.

The functions above have also the corresponding exception-safe interfaces, which return cleanly if the ‘`e_dbfailure`’ exception occurs. To use these interfaces, request the `safedb` module:

    require safedb

The exception-safe interfaces are:

Library Function: `number` safedbmap `(string db, string key [, number default, boolean null])` ¶

  This is an exception-safe interface to `dbmap`. If a database error occurs while attempting to retrieve the record, `safedbmap` returns default or ‘`0`’, if it is not defined.

Library Function: `string` safedbget `(string db, string key [, string default, boolean null])` ¶

  This is an exception-safe interface to `dbget`. If a database error occurs while attempting to retrieve the record, `safedbget` returns default or empty string, if it is not defined.

Library Function: `void` safedbput `(string db, string key, string value [, boolean null])` ¶

  This is an exception-safe interface to `dbput`. If a database error occurs while attempting to retrieve the record, the function returns without raising exception.

Library Function: `void` safedbdel `(string db, string key [, boolean null])` ¶

  This is an exception-safe interface to `dbdel`. If a database error occurs while attempting to delete the record, the function returns without raising exception.

The verbosity of ‘`safedb`’ interfaces in case of database error is controlled by the value of `safedb_verbose` variable. If it is ‘`0`’, these functions return silently. This is the default behavior. Otherwise, if `safedb_verbose` is not ‘`0`’, these functions log the detailed diagnostics about the database error and return.

The following functions provide a sequential access to the contents of a DBM database:

Built-in Function: `number` dbfirst `(string name)` ¶

  Start sequential access to the database name. The return value is an opaque identifier, which is used by the remaining sequential access functions. This number is ‘`0`’ if the database is empty.

Built-in Function: `number` dbnext `(number dn)` ¶

  Select next record form the database. The argument dn is the access identifier, returned by a previous call to `dbfirst` or `dbnext`.

  Returns new access identifier. This number is ‘`0`’ if all records in the database have been visited.

The usual approach for iterating over all records in a database dbname is:

      loop for number dbn dbfirst(dbname)
      do
        ...
      done while dbnext(dbn)

The following two functions can be used to access values of the currently selected database record. Their argument, dn, is the access identifier, returned by a previous call to `dbfirst` or `dbnext`.

Built-in Function: `string` dbkey `(number dn)` ¶

  Return the key from the selected database record.

Built-in Function: `string` dbvalue `(number dn)` ¶

  Return the value from the selected database record.

Built-in Function: `void` dbbreak `(number dn)` ¶

  Stop sequential access to the database and deallocate all associated resources. Use this function if you need to break from the sequential access loop, as in the example below:

        loop for number dbn dbfirst(dbname)
        do
          if some_condition
            dbbreak(dbn)
            break
          fi
        done while dbnext(dbn)

Built-in Function: `number` db_expire_interval `(string fmt)` ¶

  The fmt argument is a database format identifier (see Database Formats). If it is valid, the function returns the expiration interval for that format. Otherwise, `db_expire_interval` raises the `e_not_found` exception.

Built-in Function: `string` db_name `(string fmtid)` ¶

  The fmtid argument is a database format identifier (see Database Formats). The function returns the file name for that format. If fmtid does not match any known format, `db_name` raises the `e_not_found` exception.

Built-in Function: `number` db_get_active `(string fmtid)` ¶

  Returns the flag indicating whether the cache database fmtid is currently enabled. If fmtid does not match any known format, `db_name` raises the `e_not_found` exception.

Built-in Function: `void` db_set_active `(string fmtid, boolean enable)` ¶

  Enables the cache database fmtid if enable is ‘`True`’, or disables it otherwise. For example, to disable DNS caching, do:

      db_set_active("dns", 0)

Built-in Function: `boolean` relayed `(string domain)` ¶

  Returns `true` if the string domain is found in one of relayed domain files (see relayed-domain-file). The usual construct is:

      if relayed(hostname(${client_addr}))
        ...

  which yields `true` if the IP address from `Sendmail` variable ‘`client_addr`’ is relayed by the local machine.

### 5.27 Control Database ¶

Control database is a DBM file whose records define actions to be taken for mails coming from particular IP or email addresses. Functions and variables for using the control database are defined in module `cdb.mfl`.

cdb variable: `string` cdb_name ¶

  Name of the database file to use as control database. By default it is `/etc/mail/mfctl.db`.

cdb variable: `number` cdb_greylist_interval ¶

  Greylisting interval, for database records that prescribe greylisting (see below). Defaults to 900 seconds.

Library Function: `void` cdb_check `(string prefix, string addr)` ¶

  Perform a look up in the database. If the value is found, take the action it indicates.

  The key to use for the look up depends on the value of prefix:

  ip

    The addr argument must be an IP address. The look up key is ‘`ip:addr`’.

  email

    The addr argument is an email address. The key is ‘`email:cemail`’, where cemail is addr in canonical form, obtained by converting its domain part to lower case.

  domain

    The addr argument is an email address. The key is formed as ‘`domain:dom`’, where dom is domain part of addr converted to lower case.

  subdomain

    Initial key value is obtained as for ‘`domain`’. If the key is found, the requested action is performed. Otherwise, the shortest hostname prefix (sequence of characters up to and including the first dot) is stripped off the domain and the process is retried.

    Thus, the action is determined by the longest subdomain of addr, x, for which the key ‘`domain:x`’ exists in the database.

  mx

    If addr is an email address, its domain part is selected, otherwise it is used as is. The list of MX servers for this domain is obtained. For each host from that list the key ‘`mx:host`’ is looked up. First entry found is used.

  The function `cdb_check` returns if the key was not found in the database or if the value found was ‘`OK`’ (case-insensitive) or empty. Otherwise, the looked up value determines the action (see reply actions), as described in the following table. The action is returned and execution of the filter program stops.

  CONTINUE

    Continue to the next milter state. See continue.

  ACCEPT

    Accept the mail. See accept.

  REJECT

    Reject the mail. See reject.

  TEMPFAIL

    Return a temporary failure. See tempfail.

  GREYLIST

    Greylist the mail using the interval defined by the `cdb_greylist_interval` variable. If called in `envrcpt` handler, the action is taken immediately. Otherwise, if called in `connect`, `helo` or `envfrom`, the action is delayed until `envrcpt` is invoked. Otherwise, if called in any other handler an error is reported.

  code xcode text
  code text
  text

    Here, code is SMTP response code, xcode is extended STMP response code, and text is an arbitrary text. If code is given, it must begin with ‘`4`’ or ‘`5`’. Its first digit defines the action to be taken: temporary failure (if ‘`4`’) or reject (if ‘`5`’). If xcode is given, its first digit must match that of code.

    If only text is supplied, it is equivalent to

        reject(550, 5.1.0, text)

### 5.28 System functions ¶

Built-in Function: `boolean` access `(string pathname, number mode)` ¶

  Checks whether the calling process can access the file pathname. If pathname is a symbolic link, it is dereferenced. The function returns ‘`True`’ if the file can be accessed and ‘`False`’ otherwise25.

  Symbolic values for mode are provided in module `status`:

  F_OK ¶

    Tests for the existence of the file.

  R_OK ¶

    Tests whether the file exists and grants read permission.

  W_OK ¶

    Tests whether the file exists and grants write permission.

  X_OK ¶

    Tests whether the file exists and grants execute permission.

Built-in Function: `string` getenv `(string name)` ¶

  Searches the environment list for the variable name and returns its value. If the variable is not defined, the function raises the exception ‘`e_not_found`’.

Built-in Function: `string` gethostname `([bool fqn])` ¶

  Return the host name of this machine.

  If the optional fqn is given and is ‘`true`’, the function will attempt to return fully-qualified host name, by attempting to resolve it using DNS.

Built-in Function: `string` getdomainname `()` ¶

  Return the domain name of this machine. Note, that it does not necessarily coincide with the actual machine name in DNS.

  Depending on the underlying ‘`libc`’ implementation, this call may return empty string or the string ‘`(none)`’. Do not rely on it to get the real domain name of the box `mailfromd` runs on, use `localdomain` (see below) instead.

Library Function: `string` localdomain `()` ¶

  Return the local domain name of this machine.

  This function first uses `getdomainname` to make a first guess. If it does not return a meaningful value, `localdomain` calls `gethostname(1)` to determine the fully qualified host name of the machine, and returns its domain part.

  To use this function, require the `localdomain` module (see Modules), e.g.: `require localdomain`.

Built-in Function: `number` time `()` ¶

  Return the time since the Epoch (00:00:00 UTC, January 1, 1970), measured in seconds.

Built-in Function: `string` strftime `(string fmt, number timestamp)` ¶
Built-in Function: `string` strftime `(string fmt, number timestamp, boolean gmt)` ¶

  Formats the time timestamp (seconds since the Epoch) according to the format specification format. Ordinary characters placed in the format string are copied to the output without conversion. Conversion specifiers are introduced by a ‘`%`’ character. See Time and Date Formats, for a detailed description of the conversion specifiers. We recommend using single quotes around fmt to prevent ‘`%`’ specifiers from being interpreted as `Mailfromd` variables (See Literals, for a discussion of quoted literals and variable interpretation within them).

  The timestamp argument can be a return value of `time` function (see above).

  For example:

      strftime('%Y-%m-%d %H:%M:%S %Z', 1164477564)
       ⇒ 2006-11-25 19:59:24 EET
      strftime('%Y-%m-%d %H:%M:%S %Z', 1164477564, 1)
       ⇒ 2006-11-25 17:59:24 GMT

Built-in Function: `string` uname `(string format)` ¶

  This function returns system information formatted according to the format specification format. Ordinary characters placed in the format string are copied to the output without conversion. Conversion specifiers are introduced by a ‘`%`’ character. The following conversions are defined:

  %s

    Name of this system.

  %n

    Name of this node within the communications network to which this node is attached. Note, that it does not necessarily coincide with the actual machine name in DNS.

  %r

    Kernel release.

  %v

    Kernel version.

  %m

    Name of the hardware type on which the system is running.

  For example:

        uname('%n runs %s, release %r on %m')
          ⇒ "Trurl runs Linux, release 2.6.26 on i686"

  Notice the use of single quotes.

Built-in Function: `void` unlink `(string name)` ¶

  Unlinks (deletes) the file name. On error, throws the `e_failure` exception.

Built-in Function: `number` system `(string str)` ¶

  The function `system` executes a command specified in str by calling `/bin/sh -c string`, and returns -1 on error or the return status of the command otherwise.

Built-in Function: `void` sleep `(number secs[, usec])` ¶

  Sleep for secs seconds. If optional usec argument is given, it specifies additional number of microseconds to wait for. For example, to suspend execution of the filter for 1.5 seconds:

        sleep(1,500000)

  This function is intended mostly for debugging and experimental purposes.

Built-in Function: `number` umask `(number mask)` ¶

  Set the umask to mask & 0777. Return the previous value of the mask.

### 5.29 System User Database ¶

Built-in Function: `string` getpwnam `(string name)` ¶
Built-in Function: `string` getpwuid `(number uid)` ¶

  Look for the user name (`getpwnam`) or user ID uid (`getpwuid`) in the system password database and return the corresponding record, if found. If not found, raise the ‘`e_not_found`’ exception.

  The returned record consists of six fields, separated by colon sign:

      uname:passwd:uid:gid:gecos:dir:shell

  Field | Meaning
  --- | ---
  uname | user name
  passwd | user password
  uid | user ID
  gid | group ID
  gecos | real name
  dir | home directory
  shell | shell program

  For example:

      getpwnam("gray")
      ⇒ "gray:x:1000:1000:Sergey Poznyakoff:/home/gray:/bin/bash"

Following two functions can be used to test for existence of a key in the user database:

Built-in Function: `boolean` mappwnam `(string name)` ¶
Built-in Function: `boolean` mappwuid `(number uid)` ¶

  Return ‘`true`’ if name (or uid) is found in the system user database.

### 5.30 Sieve Interface ¶

‘`Sieve`’ is a powerful mail filtering language, defined in RFC 3028. `Mailfromd` supports an extended form of this language. For a description of the language and available extensions, see Sieve Language in GNU Mailutils Manual.

Built-in Function: `boolean` sieve `(number msg, string script [, number flags, string file, number line])` ¶

  Compile the Sieve program script and execute it over the message identified by the descriptor nmsg.

  Optional flags modify the behavior of the function. It is a bit-mask field, consisting of a bitwise `or` of one or more of the following flags, defined in `sieve.mfl`:

  `MF_SIEVE_FILE` ¶

    The script argument specifies the name of a Sieve program file. This is the default.

  `MF_SIEVE_TEXT` ¶

    The script argument is a string containing entire Sieve program. Optional arguments file and line can be used to fix source locations in Sieve diagnostic messages (see below).

  `MF_SIEVE_LOG` ¶

    Log every executed ‘`Sieve`’ action.

  `MF_SIEVE_DEBUG_TRACE` ¶

    Trace execution of ‘`Sieve`’ tests.

  `MF_SIEVE_DEBUG_INSTR` ¶

    Log every instruction, executed in the compiled ‘`Sieve`’ code. This produces huge amounts of output and is rarely useful, unless you suspect some bug in ‘`Sieve`’ implementation and wish to trace it.

  For example, `MF_SIEVE_LOG|MF_SIEVE_DEBUG_TRACE` enables logging ‘`Sieve`’ actions and tests.

  The `sieve` function returns `true` if the message was accepted by the script program, and `false` otherwise. Here, the word accepted means that some form of ‘`KEEP`’ action (see Actions in GNU Mailutils Manual) was executed over the message.

  While executing the Sieve script, Sieve environment (RFC 5183) is initialized as follows:

  domain

    The domain name of the server Sieve is running on.

  host

    Host name of the server Sieve is running on.

  location

    The string ‘`MTA`’.

  name

    The string ‘`GNU Mailutils`’.

  phase

    The string ‘`pre`’.

  remote-host

    Defined to the value of ‘`client_ptr`’ macro, if it was required.

  remote-ip

    Defined to the value of ‘`client_addr`’ macro, if it was required.

  version

    The version of GNU Mailutils.

The following example discards each message not accepted by the ‘`Sieve`’ program `/etc/mail/filter.siv`:

    require 'sieve'

    group eom
    do
      if not sieve(current_message(), "/etc/mail/filter.siv", MF_SIEVE_LOG)
         discard
      fi
    done

The Sieve program can be embedded in the MFL filter, as shown in the example below:

    require 'sieve'

    prog eom
    do
      if not sieve(current_message(),
                   "require \"fileinto\";\n"
                   "fileinto \"/tmp/sieved.mbox\";",
                   MF_SIEVE_TEXT | MF_SIEVE_LOG)
         discard
      fi
    done

In such cases, any Sieve diagnostics (error messages, traces, etc.) will be marked with the locations relative to the line where the call to `sieve` appears. For example, the above program produces the following in the log:

    prog.mfl:7: FILEINTO; delivering into /tmp/sieved.mbox

Notice, that the line number correctly refers to the line where the `fileinto` action appears in the source. However, there are cases where the reported line number is incorrect. This happens, for instance, if script is a string variable defined elsewhere. To handle such cases, `sieve` accepts two optional parameters which are used to compute the location in the Sieve program. The file parameter specifies the file name where the definition of the program appears, and the line parameter gives the number of line in that file where the program begins. For example:

    require 'sieve'

    const sieve_prog_line __line__ + 2
    string sieve_prog <<EOT
    require "fileinto";
    fileinto "/tmp/sieved.mbox";
    EOT

    prog eom
    do
      if not sieve(current_message(),
                   sieve_prog, MF_SIEVE_TEXT | MF_SIEVE_LOG,
                   __file__, sieve_prog_line)
         discard
      fi
    done

The actual Sieve program begins two lines below the `sieve_prog_line` constant definition, which is reflected in its initialization.

### 5.31 Interfaces to Third-Party Programs ¶

A set of functions is defined for interfacing with other filters via TCP. Currently implemented are interfaces with `SpamAssassin` `spamd` daemon and with `ClamAV` anti-virus.

Both interfaces work much the same way: the remote filter is connected and the message is passed to it. If the remote filter confirms that the message matches its requirements, the function returns `true`. Notice that in practice that means that such a message should be rejected or deferred.

The address of the remote filter is supplied as the second argument in the form of a standard URL:

    proto://path[:port]

The proto part specifies the connection protocol. It should be ‘`tcp`’ for the TCP connection and ‘`file`’ or ‘`socket`’ for the connection via UNIX socket. In the latter case the proto part can be omitted. When using TCP connection, the path part gives the remote host name or IP address and the optional port specifies the port number or service name to use. For example:

    # connect to ‘remote.filter.net’ on port 3314:
    tcp://remote.filter.net:3314

    # the same, using symbolic service name (must be defined in
    # /etc/services):
    tcp://remote.filter.net:spamd

    # Connect via a local UNIX socket (equivalent forms):
    /var/run/filter.sock
    file:///var/run/filter.sock
    socket:///var/run/filter.sock

The description of the interface functions follows.

#### 5.31.1 SpamAssassin ¶

Built-in Function: `boolean` spamc `(number msg, string url, number prec, number command)` ¶

  Send the message msgt to the SpamAssassin daemon (`spamd`) listening on the given url. The command argument identifies what kind of processing is needed for the message. Allowed values are:

  SA_SYMBOLS ¶

    Process the message and return 1 or 0 depending on whether it is diagnosed as spam or not. Store SpamAssassin keywords in the global variable `sa_keywords` (see below).

  SA_REPORT ¶

    Process the message and return 1 or 0 depending on whether it is diagnosed as spam or not. Store entire SpamAssassin report in the global variable `sa_keywords`.

  SA_LEARN_SPAM ¶

    Learn the supplied message as spam.

  SA_LEARN_HAM ¶

    Learn the supplied message as ham.

  SA_FORGET ¶

    Forget any prior classification of the message.

  The second argument, prec, gives the precision, in decimal digits, to be used when converting SpamAssassin diagnostic data and storing them into `mailfromd` variables.

  The floating point SpamAssassin data are converted to the integer `mailfromd` variables using the following relation:

      var = int(sa-var * 10**prec)

  where sa-var stands for the SpamAssassin value and var stands for the corresponding `mailfromd` one. `int()` means taking the integer part and ‘`**`’ denotes the exponentiation operator.

  The function returns additional information via the following variables:

  `sa_score` ¶

    The spam score, converted to integer as described above. To convert it to a floating-point representation, use `sa_format_score` function (see sa_format_score). See also the example below.

  `sa_threshold` ¶

    The threshold, converted to integer form.

  `sa_keywords` ¶

    If command is ‘`SA_SYMBOLS`’, this variable contains a string of comma-separated SpamAssassin keywords identifying this message, e.g.:

        ADVANCE_FEE_1,AWL,BAYES_99

    If command is ‘`SA_REPORT`’, the value of this variable is a spam report message. It is a multi-line textual message, containing detailed description of spam scores in a tabular form. It consists of the following parts:

    1. A preamble.
    2. Content preview.

       The words ‘`Content preview`’, followed by a colon and an excerpt of the message body.

    3. Content analysis details.

       It has the following form:

           Content analysis details: (score points, max required)

       where score and max are spam score and threshold in floating point.

    4. Score table.

       The score table is formatted in three columns:

       pts

         The score, as a floating point number with one decimal digit.

       rule name

         SpamAssassin rule name that contributed this score.

       description

         Textual description of the rule

       The score table can be extracted from `sa_keywords` using `sa_format_report_header` function (see sa_format_report_header), as illustrated in the example below.

    The value of this variable is undefined if command is ‘`SA_LEARN_SPAM`’, ‘`SA_LEARN_HAM`’ or ‘`SA_FORGET`’.

  The `spamc` function can signal the following exceptions: `e_failure` if the connection fails, `e_url` if the supplied URL is invalid and `e_range` if the supplied port number is out of the range 1–65535.

  An example of using this function:

      prog eom
      do
        if spamc(current_message(), "tcp://192.168.10.1:3333", 3,
                 SA_SYMBOLS)
          reject 550 5.7.0
               "Spam detected, score %sa_score with threshold %sa_threshold"
        fi
      done

  Here is a more advanced example:

      prog eom
      do
        set prec 3
        if spamc(current_message(),
                 "tcp://192.168.10.1:3333", prec, SA_REPORT)
          add "X-Spamd-Status" "SPAM"
        else
          add "X-Spamd-Status" "OK"
        fi
        add "X-Spamd-Score" sa_format_score(sa_score, prec)
        add "X-Spamd-Threshold" sa_format_score(sa_threshold, prec)
        add "X-Spamd-Keywords" sa_format_report_header(sa_keywords)
      done

Library Function: `boolean` sa `(string url, number prec; number command)` ¶

  Additional interface to the `spamc` function, provided for backward compatibility. It is equivalent to

      spamc(current_message(), url, prec, command)

  If command is not supplied, ‘`SA_SYMBOLS`’ is used.

#### 5.31.2 ClamAV ¶

Built-in Function: `boolean` clamav `(number msg, string url)` ¶

  Pass the message msg to the ClamAV daemon at url. Return `true` if it detects a virus in it. Return virus name in `clamav_virus_name` global variable.

  The message is sent to `clamd` in chunks. The size of each chunk is defined by the variable `clamav_stream_max_length`. Its value should not be greater than that of the `StreamMaxLength` parameter in `clamd.conf`.

  To stay on the safe side, `clamav_stream_max_length` defaults to 4096 bytes. To improve performance, adjust it to the actual value of `StreamMaxLength`.

  The `clamav` function can signal the following exceptions:

  `e_failure`

    Unable to connect to the server, or failed to send the message to it. In particular, this exception will be raised if the value of `clamav_stream_max_length` is greater than the `StreamMaxLength` `clamd` configuration parameter. In this case, the `clamd` server will report:

        WARNING: INSTREAM: Size limit reached, (requested: n, max: m)

    where n and m are decimal numbers. Adjust your `clamav_stream_max_length` value so that

        clamav_stream_max_length < m

  `e_url`

    The supplied URL is invalid.

  An example usage:

      prog eom
      do
        if clamav(current_message(), "tcp://192.168.10.1:6300")
          reject 550 5.7.0 "Infected with %clamav_virus_name"
        fi
      done

### 5.32 Rate limiting functions ¶

Built-in Function: `number` rate `(string key, number sample-interval, [number min-samples, number threshold])` ¶

  Returns the mail sending rate for key per sample-interval. Optional min-samples, if supplied, specifies the minimal number of mails needed to obtain the statistics. The default is 2. Optional threshold controls rate database updates. If the observed rate (per sample-interval seconds) is higher than the threshold, the hit counters for that key are not incremented and the database is not updated. Although the threshold argument is optional26, its use is strongly encouraged. Normally, the value of threshold equals the value compared with the return from rate, as in:

        if rate("$f-$client_addr", rate_interval, 4, maxrate) > maxrate
          tempfail 450 4.7.0 "Mail sending rate exceeded.  Try again later"
        fi

  This function is a low-level interface. Instead of using it directly, we advise to use the `rateok` function, described below.

Library Function: `boolean` rateok `(string key, number sample-interval, number threshold,` ¶

  [number min-samples])

  To use this function, require the `rateok` module (see Modules), e.g.: `require rateok`.

  The `rateok` function returns ‘`True`’ if the mail sending rate for key, computed for the interval of sample-interval seconds is less than the threshold. Optional min-samples parameter supplies the minimal number of mails needed to obtain the statistics. It defaults to 4.

See Sending Rate, for a detailed description of the `rateok` and its use. The `interval` function (see interval) is often used in the second argument to `rateok` or `rate`.

Built-in Function: `boolean` tbf_rate `(string key, number cost, number sample-interval, number burst-size)` ¶

  This function implements a classical token bucket filter algorithm. Tokens are added to the bucket identified by the key at constant rate of 1 token per sample-interval microseconds, to a maximum of burst-size tokens. If no bucket is found for the specified key, a new bucket is created and initialized to contain burst-size tokens. If the bucket contains cost or more tokens, cost tokens are removed from it and `tbf_rate` returns ‘`True`’. Otherwise, the function returns ‘`False`’.

  For a detailed description of the Token Bucket Algorithm and its use to limit mail rates, see TBF.

### 5.33 Greylisting functions ¶

Built-in Function: `boolean` greylist `(string key, number interval)` ¶

  Returns ‘`True`’ if the key is found in the greylist database (controlled by `database greylist` configuration file statement, see Database Configuration). The argument interval gives the greylisting interval in seconds. The function stores the number of seconds left to the end of greylisting period in the global variable `greylist_seconds_left`. See Greylisting, for a detailed explanation.

  The function `greylist` can signal `e_dbfailure` exception.

Built-in Function: `boolean` is_greylisted `(string key` ¶

  Returns ‘`True`’ if the key is still greylisted. If ‘`true`’ is returned, the function also stores the number of seconds left to the end of greylisting period in the global variable `greylist_seconds_left`.

  This function is available only if Con Tassios implementation of greylisting is used. See greylisting types, for a discussion of available greylisting implementations. See Pragma greylist, for a way to switch to Con Tassios implementation.

### 5.34 Special Test Functions ¶

Library Function: `boolean` portprobe `(string host, [number port])` ¶
Library Function: `boolean` listens `(string host, [number port])` ¶

  Returns `true` if the IP address or host name given by host argument listens on the port number port (default 25).

  This function is defined in the module `portprobe`.

Built-in Function: `boolean` validuser `(string name)` ¶

  Returns `true` if authenticity of the user name is confirmed using mailutils authentication system. See Local Account Verification, for more details.

Library Function: `boolean` valid_domain `(string domain)` ¶

  Returns `true` if the domain name domain has a corresponding A record or if it has any ‘`MX`’ records, i.e. if it is possible to send mail to it.

  To use this function, require the `valid_domain` module (see Modules):

      require valid_domain

Library Function: `number` heloarg_test `(string arg, string remote_ip, string local_ip)` ¶

  Verify if an argument of ‘`HELO`’ (‘`EHLO`’) command is valid. To use this function, require the `heloarg_test` module (see Modules).

  Arguments:

  arg

    ‘`HELO`’ (‘`EHLO`’) argument. Typically, the value of `$s` Sendmail macro;

  remote_ip

    IP address of the remote client. Typically, the value of `$client_addr` Sendmail macro;

  local_ip

    IP address of this SMTP server;

  The function returns a number describing the result of the test, as described in the following table.

  Code | Meaning
  --- | ---
  HELO_SUCCESS | arg successfully passes all tests.
  HELO_MYIP | arg is our IP address.
  HELO_IPNOMATCH | arg is an IP, but it does not match the remote party IP address.
  HELO_ARGNORESOLVE | arg is an IP, but it does not resolve.
  HELO_ARGNOIP | arg is in square brackets, but it is not an IP address.
  HELO_ARGINVALID | arg is not an IP address and does not resolve to one.
  HELO_MYSERVERIP | arg resolves to our server IP.
  HELO_IPMISMATCH | arg does not resolve to the remote client IP address.

### 5.35 Mail Sending Functions ¶

The mail sending functions are new interfaces, introduced in version 3.1.

The underlying mechanism for sending mail, called mailer, is specified by `--mailer` command line option. This global setting can be overridden using the last optional argument to a particular function. In any case, the mailer is specified in the form of a URL.

Mailer URL begins with a protocol specification. Two protocol specifications are currently supported: ‘`sendmail`’ and ‘`smtp`’. The former means to use a `sendmail`-compatible program to send mails. Such a program must be able to read mail from its standard input and must support the following options:

`-oi`

  Do not treat ‘`.`’ as message terminator.

`-f addr`

  Use addr as the address of the sender.

`-t`

  Get recipient addresses from the message.

These conditions are met by most existing MTA programs, such as `exim` or `postfix` (to say nothing of `sendmail` itself).

Following the protocol specification is the mailer location, which is separated from it with a colon. For the ‘`sendmail`’ protocol, the mailer location sets the full file name of the Sendmail-compatible MTA binary, for example:

    sendmail:/usr/sbin/sendmail

A special form of a sendmail URL, consisting of protocol specification only (‘`sendmail:`’) is also allowed. It means “use the sendmail binary from the `_PATH_SENDMAIL` macro in your `/usr/include/paths.h` file”. This is the default mailer.

The ‘`smtp`’ protocol means to use an SMTP server directly. In this case the mailer location consists of two slashes, followed by the IP address or host name of the SMTP server, and, optionally, the port number. If the port number is present, it is separated from the rest of URL by a colon. For example:

    smtp://remote.server.net
    smtp://remote.server.net:24

Built-in Function: `void` send_mail `(string msg [, string to, string from, string mailer])` ¶

  Sends message msg to the email address to. The value of msg must be a valid RFC 2822 message, consisting of headers and body. Optional argument to can contain several email addresses. In this case the message will be sent to each recipient specified in to. If it is not specified, recipient addresses will be obtained from the message headers.

  Other optional arguments are:

  from

    Sets the sender address. By default ‘`<>`’ is used.

  mailer

    The URL of the mailer to use

  Sample usage:

          set message <<- EOT
                Subject: Test message
                To: Postmaster <postmaster@gnu.org.ua>
                From: Mailfromd <devnull@gnu.org.ua>
                X-Agent: %__package__ (%__version__)

                Dear postmaster,

                This is to notify you that our /etc/mailfromd.mfl
                needs a revision.
                --
                Mailfromd filter administrator
          EOT
          send_mail(message, "postmaster@gnu.org.ua")

Built-in Function: `void` send_text `(string text, string headers [, string to, string from, string mailer])` ¶

  A more complex interface to mail sending functions.

  Mandatory arguments:

  text

    Text of the message to be sent.

  headers

    Headers for the message.

  Optional arguments:

  to

    Recipient email addresses.

  from

    Sender email address.

  mailer

    URL of the mailer to use.

  The above example can be rewritten using `send_text` as follows:

          set headers << -EOT
                Subject: Test message
                To: Postmaster <postmaster@gnu.org.ua>
                From: Mailfromd <devnull@gnu.org.ua>
                X-Agent: %__package__ (%__version__)
          EOT
          set text <<- EOT
                Dear postmaster,

                This is to notify you that our /etc/mailfromd.mfl
                needs a revision.
                --
                Mailfromd filter administrator
          EOT
          send_text(text, headers, "postmaster@gnu.org.ua")

Built-in Function: `void` send_message `(number msg [string to, string from, string mailer])` ¶

  Send the message identified by descriptor msg (see Message Functions).

  Optional arguments are:

  to

    Recipient email addresses.

  from

    Sender email address.

  mailer

    URL of the mailer to use.

Built-in Function: `void` send_dsn `(string to, string sender, string rcpt, string text [, string headers, string from, string mailer])` ¶

  This is an experimental interface which will change in the future versions. It sends a message disposition notification (RFC 2298, RFC 1894), of type ‘`deleted`’ to the email address to. Arguments are:

  to

    Recipient email address.

  sender

    Original sender email address.

  rcpt

    Original recipient email address.

  text

    Notification text.

  Optional arguments:

  headers

    Message headers

  from

    Sender address.

  mailer

    URL of the mailer to use.

Built-in Function: `void` create_dsn `(string sender, string rcpt, string text [, string headers, string from])` ¶

  Creates DSN message and returns its descriptor. Arguments are:

  sender

    Original sender email address.

  rcpt

    Original recipient email address.

  text

    Notification text.

  headers

    Message headers

  from

    Sender address.

### 5.36 Blacklisting Functions ¶

The functions described in this subsection allow to check whether the given IP address is listed in certain black list DNS zone.

Library Function: `boolean` match_dnsbl `(string address, string zone, string range)` ¶

  This function looks up address in the DNS blacklist zone zone and checks if the return falls into the given range of IP addresses.

  It is intended as a replacement for the Sendmail macros ‘`dnsbl`’ and ‘`enhdnsbl`’.

  To use `match_dnsbl`, require the `match_dnsbl` module (see Modules).

  Arguments:

  address

    IP address of the SMTP server to be tested.

  zone

    FQDN of the DNSbl zone to test against.

  range

    The range of IP addresses in CIDR notation or the word ‘`ANY`’, which stands for ‘`127.0.0.0/8`’.

  The function returns `true` if dns lookup for address in the zone dnsbl yields an IP that falls within the range, specified by cidr. Otherwise, it returns `false`.

  This function raises the following exceptions: `e_invip` if address is invalid and `e_invcidr` if cidr is invalid.

Library Function: `boolean` match_rhsbl `(string email, string zone, string range)` ¶

  This function checks if the IP address, corresponding to the domain part of email is listed in the RHS DNS blacklist zone zone, and if so, whether its record falls into the given range of IP addresses range.

  It is intended as a replacement for the Sendmail macro ‘`rhsbl`’ by Derek J. Balling.

  To use this function, require the `match_rhsbl` module (see Modules).

  Arguments:

  email

    E-mail address, whose domain name should be tested (usually, it is `$f`)

  zone

    Domain name of the RHS DNS blacklist zone.

  range

    The range of IP addresses in CIDR notation.

### 5.37 SPF Functions ¶

Sender Policy Framework, or SPF for short, is an extension to SMTP protocol that allows to identify forged identities supplied with the `MAIL FROM` and `HELO` commands. The framework is explained in detail in RFC 4408 (http://tools.ietf.org/html/rfc4408) and on the SPF Project Site. The following description is a short introduction only, and the users are encouraged to refer to the original specification for the detailed description of the framework.

The domain holder publishes an SPF record – a special DNS resource record that contains a set of rules declaring which hosts are, and are not, authorized to use a domain name for `HELO` and `MAIL FROM` identities. This resource record is usually of type `TXT`.27

The MFL script can verify if the identity matches the published SPF record by calling `check_host` function and analyzing its return code. The function can be called either in `helo` or in `envfrom` handler. Its arguments are:

ip

  The IP address of the SMTP client that is emitting the mail. Usually it is `$client_addr`.

domain

  The domain that provides the sought-after authorization information; Normally it is the domain portion of the `MAIL FROM` or `HELO` identity.

sender

  The `MAIL FROM` identity.

helo_domain

  The `HELO` identity.

my_domain

  The SMTP domain served by the local server.

The function returns a numeric result code. For convenience, all possible return values are defined as macros in module `spf.mfl`. The table below describes each value along with the recommended actions for it:

`None` ¶

  A result of `None` means that no records were published by the domain or that no checkable sender domain could be determined from the given identity. The checking software cannot ascertain whether or not the client host is authorized. Such a message can be subject to further checks that will decide about its fate.

`Neutral` ¶

  The domain owner has explicitly stated that he cannot or does not want to assert whether or not the IP address is authorized. This result must be treated exactly like `None`; the distinction between them exists only for informational purposes

`Pass` ¶

  The client is authorized to send mail with the given identity. The message can be subject to further policy checks with confidence in the legitimate use of the identity or it can be accepted in the absence of such checks.

`Fail` ¶

  The client is not authorized to use the domain in the given identity. The proper action in this case can be to mark the message with a header explicitly stating it is spam, or to reject it outright.

  If you choose to reject such mails, we suggest to use `reject 550 5.7.1`, as recommended by RFC 4408. The reject can return either a default explanation string, or the one supplied by the domain that published the SPF records, as in the example below:

        reject 550 5.7.1 "SPF check failed:\n%spf_explanation"

  (for the description of `spf_explanation`, see spf_explanation)

`SoftFail` ¶

  The domain believes the host is not authorized but is not willing to make that strong of a statement. This result code should be treated as somewhere in between a `Fail` and a `Neutral`. It is not recommended to reject the message based solely on this result.

`TempError`

  A transient error occurred while performing SPF check. The proper action in this case is to accept or temporarily reject the message. If you choose the latter, we suggest to use SMTP reply code of ‘`451`’ and DSN code ‘`4.4.3`’, for example:

        tempfail 451 4.4.3
                 "Transient error while performing SPF verification"

`PermError`

  This result means that the domain’s published records could not be correctly interpreted. This signals an error condition that requires manual intervention to be resolved, as opposed to the `TempError` result.

The following example illustrates the use of SPF verification in `envfrom` handler:

    require 'status'
    require 'spf'

    prog envfrom
    do
      switch check_host($client_addr, domainpart($f), $f, $s)
      do
      case Fail:
        string text ""
        if spf_explanation != ""
          set text "%text\n%spf_explanation"
        fi
        reject 550 5.7.1 "SPF MAIL FROM check failed: %text"

      case Pass:
        accept

      case TempError:
        tempfail 451 4.4.3
                 "Transient error while performing SPF verification"

      default:
        on poll $f do
        when success:
          accept
        when not_found or failure:
          reject 550 5.1.0 "Sender validity not confirmed"
        when temp_failure:
          tempfail 450 4.7.0 "Temporary failure during sender verification"
        done
      done
    done

The SPF support is implemented in MFL in two layers: a built-in layer that provides basic support, and a library layer that provides a convenience wrapper over the library function.

The library layer is implemented in the module `spf.mfl` (see Modules).

The rest of this node describes available SPF functions and variables.

Built-in Function: `number` spf_check_host `(string ip, string domain, string sender, string helo_domain, string my_domain)` ¶

  This function is the basic implementation of the `check_host` function, defined in RFC 4408, chapter 4. It fetches SPF records, parses them, and evaluates them to determine whether a particular host (ip) is or is not permitted to send mail from a given email address (sender). The function returns an SPF result code.

  Arguments are:

  ip

    The IP address of the SMTP client that is emitting the mail. Usually it is `$client_addr`.

  domain

    The domain that provides the sought-after authorization information; Normally it is the domain portion of the `MAIL FROM` or `HELO` identity.

  sender

    The `MAIL FROM` identity.

  helo_domain

    The `HELO` identity.

  my_domain

    The SMTP domain served by the local server.

  Before returning the `spf_check_host` function stores additional information in global variables:

  `spf_explanation`

    If the result code is `Fail`, this variable contains the explanation string as returned by the publishing domain, prefixed with the value of the global variable `spf_explanation_prefix`.

    For example, if `spf_explanation_prefix` contains ‘`The domain %{o} explains:` ’, and the publishing domain ‘`example.com`’ returns the explanation string ‘`Please see http://www.example.com/mailpolicy.html`’, than the value of `spf_explanation` will be:

        The domain example.com explains:
        Please see http://www.example.com/mailpolicy.html

    (see RFC 4408, chapter 8, for the description of SPF macro facility).

  `spf_mechanism`

    Name of the SPF mechanism that decided about the result code of the SPF record. If one or more ‘`include`’ or ‘`redirect`’ mechanisms were traversed before arriving at that mechanism, their values are appended in the reverse order.

Built-in Function: `number` spf_test_record `(string record, string ip, string domain, string sender, string helo_domain, string my_domain)` ¶

  Evaluate SPF record record as if it were published by domain. The rest of arguments are the same as for `spf_check_host` above.

  This function is designed primarily for testing and debugging purposes. You would hardly need to use it.

  The `spf_test_record` function sets the same global variables as `spf_check_host`.

Library Function: `number` check_host `(string ip, string domain, string sender, string helo)` ¶

  This function implements the `check_host` function, defined in RFC 4408, chapter 4. It fetches SPF records, parses them, and evaluates them to determine whether a particular host (ip) is or is not permitted to send mail from a given email address (sender). The function returns an SPF result code.

  This function is a wrapper over the built-in `spf_check_host`.

  The arguments are:

  ip

    The IP address of the SMTP client that is emitting the mail. Usually it is the same as the value of `$client_addr`.

  domain

    The domain that provides the sought-after authorization information; Normally it is the domain portion of the `MAIL FROM` or `HELO` identity.

  sender

    The `MAIL FROM` identity.

  helo

    The `HELO` identity.

Library Function: `string` spf_status_string `(number code)` ¶

  Converts numeric SPF result code to its string representation.

Built-in variable: `string` spf_explanation ¶

  If `check_host` (or `spf_check_host` or `spf_test_record`) returned `Fail`, this variable contains the explanation string as returned by the publishing domain, prefixed with the value of the global variable `spf_explanation_prefix`.

  For example, if `spf_explanation_prefix` contains ‘`The domain %{o} explains:` ’, and the publishing domain ‘`example.com`’ returns the explanation string ‘`Please see http://www.example.com/mailpolicy.html`’, than the value of `spf_explanation` will be:

      The domain example.com explains:
      Please see http://www.example.com/mailpolicy.html

Built-in variable: `string` spf_mechanism ¶

  Set to the name of a SPF mechanism that decided about the result code of the SPF record.

Built-in variable: `string` spf_explanation_prefix ¶

  The prefix to be appended to the explanation string before storing it in the `spf_explanation` variable. This string can contain valid SPF macros (see RFC 4408, chapter 8), for example:

      set spf_explanation_prefix "%{o} explains: "

  The default value is ‘`""`’ (an empty string).

### 5.38 DKIM ¶

DKIM or DomainKeys Identified Mail is an email authentication method that allows recipients to verify if an email was authorized by the owner of the domain that email claims to originate from. It does so by adding a digital signature which is verified using a public key published as a DNS TXT record. For technical details about DKIM, please refer to RFC 6376 (http://tools.ietf.org/html/rfc6376).

MFL provides functions for DKIM signing and verification.

Built-in Function: `number` dkim_verify `(number msg)` ¶

  Verifies the message msg (a message descriptor, obtained from a call to `current_message`, `mailbox_get_message`, `message_from_stream` or a similar function).

  Return value (constants defined in the ‘`status`’ module):

  dkim_verify status: DKIM_VERIFY_OK ¶

    The message contains one or more ‘`DKIM-Signature`’ headers and one of them verified successfully.

  dkim_verify status: DKIM_VERIFY_PERMFAIL ¶

    The message contains one or more ‘`DKIM-Signature`’ headers, all of which failed to verify.

  dkim_verify status: DKIM_VERIFY_TEMPFAIL ¶

    The message was not signed using DKIM, or the DNS query to obtain the public key failed, or an internal software error occurred during verification.

  The following two global variables are always set upon return from this function: `dkim_explanation` and `dkim_explanation_code`. These can be used to clarify the verification result to the end user. The variable `dkim_signing_algorithm` is initialized with the name of the algorithm used to sign the message.

  Upon successful return, the variable `dkim_verified_signature` is set to the value of the successfully verified DKIM signature.

Built-in variable: `string` dkim_signing_algorithm ¶

  Name of the algorithm used to sign the message (either ‘`rsa-sha1`’ or ‘`rsa-sha256`’). If the algorithm was not specified (e.g. the signature is malformed), this variable is assigned an empty value.

Built-in variable: `string` dkim_explanation ¶

  An explanatory message clarifying the verification result.

Built-in variable: `number` dkim_explanation_code ¶

  A numeric code corresponding to the ‘`dkim_explanation`’ string. Its possible values are defined in ‘`status.mfl`’:

  DKIM explanation code: DKIM_EXPL_OK ¶

    ‘`DKIM verification passed`’

  DKIM explanation code: DKIM_EXPL_OK ¶

    ‘`DKIM verification passed`’

  DKIM explanation code: DKIM_EXPL_NO_SIG ¶

    ‘`No DKIM signature`’

  DKIM explanation code: DKIM_EXPL_INTERNAL_ERROR ¶

    ‘`internal error`’

  DKIM explanation code: DKIM_EXPL_SIG_SYNTAX ¶

    ‘`signature syntax error`’

  DKIM explanation code: DKIM_EXPL_SIG_MISS ¶

    ‘`signature is missing required tag`’

    According to the DKIM specification, required tags are: `a=`, `b=`, `bh=`, `d=`, `h=`, `s=`, `v=`.

  DKIM explanation code: DKIM_EXPL_DOMAIN_MISMATCH ¶

    ‘`domain mismatch`’

    The domain part of the `i=` tag does not match and is not a subdomain of the domain listed in the `d=` tag.

  DKIM explanation code: DKIM_EXPL_BAD_VERSION ¶

    ‘`incompatible version`’

    Incompatible DKIM version listed in the `v=` tag.

  DKIM explanation code: DKIM_EXPL_BAD_ALGORITHM ¶

    ‘`unsupported signing algorithm`’

    Either the `a=` tag of the DKIM signature contains an unsupported algorithm (currently supported algorithms are: ‘`rsa-sha1`’ and ‘`rsa-sha256`’) or this algorithm, while being supported by `mailfromd`, is not listed in the `h=` tag of the public DKIM key.

  DKIM explanation code: DKIM_EXPL_BAD_QUERY ¶

    ‘`unsupported query method`’

    The `q=` tag of the public DKIM key contains something other than ‘`dns/txt`’.

  DKIM explanation code: DKIM_EXPL_FROM ¶

    ‘`From field not signed`’

  DKIM explanation code: DKIM_EXPL_EXPIRED ¶

    ‘`signature expired`’

  DKIM explanation code: DKIM_EXPL_DNS_UNAVAIL ¶

    ‘`public key unavailable`’

  DKIM explanation code: DKIM_EXPL_DNS_NOTFOUND ¶

    ‘`public key not found`’

  DKIM explanation code: DKIM_EXPL_KEY_SYNTAX ¶

    ‘`key syntax error`’

  DKIM explanation code: DKIM_EXPL_KEY_REVOKED ¶

    ‘`key revoked`’

  DKIM explanation code: DKIM_EXPL_BAD_BODY ¶

    ‘`body hash did not verify`’

  DKIM explanation code: DKIM_EXPL_BAD_BASE64 ¶

    ‘`can't decode b= tag`’

    Base64 decoding of the `b=` tag failed.

  DKIM explanation code: DKIM_EXPL_BAD_SIG ¶

    ‘`signature did not verify`’

  DKIM explanation code: DKIM_EXPL_BAD_KEY_TYPE ¶

    ‘`unsupported public key type`’

    The `k=` tag of the public DKIM signature contains a value, other than ‘`rsa`’.

Built-in variable: `string` dkim_verified_signature ¶

  Upon successful return from the `dkim_verify` function, this variable holds the value of the successfully verified DKIM header. This value is unfolded and all whitespace is removed from it.

An example of using the ‘`dkim_verify`’ function:

    require status
    require dkim

    prog eom
    do
      string result
      switch dkim_verify(current_message())
      do
         case DKIM_VERIFY_OK:
             set result "pass; verified for " .
                        dkim_verified_signature_tag('i')
         case DKIM_VERIFY_PERMFAIL:
             set result "fail (%dkim_explanation)"
         case DKIM_VERIFY_TEMPFAIL:
             set result "neutral"
      done
      header_add("X-Verification-Result", "dkim=%result")
    done

The ‘`dkim`’ module defines convenience functions for manipulating with DKIM signatures:

Library Function: `dkim_signature_tag` (string `sig, string tag)` ¶

  Extracts the value of the tag tag from the DKIM signature sig. Signature must be normalized by performing the header unwrapping and removing whitespace characters.

  If the tag was not found, returns empty string, unless tag is one of the tags listed in the table below. If any of these tags are absent, the following values are returned instead:

  Tag | Default value
  --- | ---
  c | ‘`simple/simple`’
  q | ‘`dns/txt`’
  i | ‘`@`’ + the value of the ‘`d`’ tag.

Library Function: `string` dkim_verified_signature_tag `(string tag)` ¶

  Returns the value of tag tag from the ‘`dkim_verified_signature`’ variable.

Built-in Function: `void` dkim_sign `(string d, string s, string keyfile, [ string ch, string cb, string headers, string algo ])` ¶

  This function is available only in the `eom` handler.

  Signs the current message. Notice, that no other modification should be attempted on the message after calling this function. Doing so would make the signature invalid.

  Mandatory arguments:

  d

    Name of the domain claiming responsibility for an introduction of a message into the mail stream. It is also known as the signing domain identifier (SDID).

  s

    The selector name. This value, along with d identifies the location of the DKIM public key necessary for verifying the message. The public key is stored in the DNS TXT record for

          s._domainkey.d

  keyfile

    Name of the disk file that keeps the private key for signing the message. The file must be in PKCS#1 or PKCS#8 format (PEM formatted).

  Optional arguments:

  ch

    Canonicalization algorithm for message headers. Valid values are: ‘`simple`’ and ‘`relaxed`’. ‘`simple`’ is the default.

  cb

    Canonicalization algorithm for message body. Valid and default values are the same as for ch.

  headers

    A colon-separated list of header field names that identify the header fields that must be signed. Optional whitespace is allowed at either side of each colon separator. Header names are case-insensitive. This list must contain at least the ‘`From`’ header.

    It may contain names of headers that are not present in the message being signed. This provides a way to explicitly assert the absence of a header field. For example, if headers contained ‘`X-Mailer`’ and that header is not present in the message being signed, but is added by a third party later, the signature verification will fail.

    Similarly, listing a header field name once more than the actual number of its occurrences in a message allows you to prevent any further additions. For example, if there is a single ‘`Comments`’ header field at the time of signing, putting ‘`Comments:Comments:`’ in the headers parameter is sufficient to prevent any surplus ‘`Comments`’ headers from being added later on.

    Multiple instances of the same header name are allowed. They mean that multiple occurrences of the corresponding header field will be included in the header hash. When such multiple header occurrences are referenced, they will be presented to the hashing algorithm in the reverse order. E.g. if the header list contained ‘`Received:Received`’) and the current message contained three ‘`Received`’ headers:

        Received: A
        Received: B
        Received: C

    then these headers will be signed in the following order:

        Received: C
        Received: B

    The default value for this parameter, split over multiple lines for readability, is as follows:

    - "From:From:"
    - "Reply-To:Reply-To:"
    - "Subject:Subject:"
    - "Date:Date:"
    - "To:"
    - "Cc:"
    - "Resent-Date:"
    - "Resent-From:"
    - "Resent-To:"
    - "Resent-Cc:"
    - "In-Reply-To:"
    - "References:"
    - "List-Id:"
    - "List-Help:"
    - "List-Unsubscribe:"
    - "List-Subscribe:"
    - "List-Post:"
    - "List-Owner:"
    - "List-Archive"
  algo

    Signing algorithm: either ‘`rsa-sha256`’ or ‘`rsa-sha1`’. Default is ‘`rsa-sha256`’.

An example of using this function:

    precious string domain "example.org"
    precious string selector "s2048"
    prog eom
    do
      dkim_sign("example.org", "s2048", "/etc/pem/my-private.pem",
                "relaxed", "relaxed", "from:to:subject")
    done

#### Note on interaction of dkim_sign with Sendmail ¶

When sending a signed message, it is critical that no other modifications be applied to the message after it has been signed. Unfortunately, it is not always the case when `mailfromd` is used with `Sendmail`. Before sending the message over SMTP, `Sendmail` reformats the headers that contain a list of email addresses, by applying to them a procedure called in its parlance commaization. The following headers are modified: `Apparently-To`, `Bcc`, `Cc`, `Disposition-Notification-To`, `Errors-To`, `From`, `Reply-To`, `Resent-Bcc`, `Resent-Cc`, `Resent-From`, `Resent-Reply-To`, `Resent-Sender`, `Resent-To`, `Sender`, `To`. Thus, if your `dkim_sign` includes any of these in the signature (which is the default) and some of them happen to be formatted other way than the one `Sendmail` prefers, the DKIM signature would not verify on the recipient side. To prevent this from happening, `dkim_sign` mimics the Sendmail behavior and reformats those headers before signing the message. This should ensure that the message signed and the message actually sent are the same. This default behavior is controlled by the following global variable:

Built-in variable: `number` dkim_sendmail_commaize ¶

  “Commaize” the address headers (see the list above) of the message the same way Sendmail does, and then sign the resulting message.

  The default value is 1 (`true`). You can set it to 0 (`false`) if this behavior is not what you want (e.g. if you are using `postfix` or some other MTA).

#### Note on interaction of dkim_sign with MMQ ¶

The functions `header_add` and `header_insert` (see Header Modification Functions) as well as the `add` action (see header manipulation) cannot interact properly with `dkim_sign` due to the shortcomings of the Milter API. If any of these was called, `dkim_sign` will throw the `e_badmmq` exception with the following diagnostics:

    MMQ incompatible with dkim_sign: op on h, value v

where op is the operation code (‘`ADD HEADER`’ or ‘`INSERT HEADER`’), h is the header name and v is its value.

The following example shows one graceful way of handling such exception:

    prog eom
    do
        try
        do
            dkim_sign("example.org", "s2048", "/etc/pem/my-private.pem")
        done
        catch e_badmmq
        do
            # Purge the message modification queue
            mmq_purge()
            # and retry
            dkim_sign("example.org", "s2048", "/etc/pem/my-private.pem")
        done
    done

See Message Modification Queue, for a discussion of the message modification queue.

#### 5.38.1 Setting up a DKIM record ¶

Follow these steps to set up your own DKIM record:

1. Generate a key pair:

   Use the `openssl genrsa` command. Run:

       openssl genrsa -out private.pem 2048

   The last argument is the size of the private key to generate in bits.

2. Extract the public key:

       openssl rsa -in private.pem -pubout -outform PEM -out public.pem

3. Set up a DKIM record in your domain:

   A DKIM record is a TXT type DNS record that holds the public key part for verifying messages. Its format is defined in RFC 487128. The label for this record is composed as follows:

         s._domainkey.d

   where d is your domain name, and s is the selector you chose to use. You will use these two values as parameters to the `dkim_sign` function in your `eom` handler. E.g. if your domain in ‘`example.com`’ and selector is ‘`s2048`’, then the DKIM TXT record label is ‘`s2048._domainkey.example.com`’.

   The public key file generated in step 2 will have the following contents:

       -----BEGIN PUBLIC KEY-----
       base64
       -----END PUBLIC KEY-----

   where base64 is the key itself in base64 encoding. The minimal DKIM TXT record will be:

       "v=DKIM1; p=base64"

   The only mandatory tag is in fact ‘`p=`’. The use of ‘`v=`’ is recommended. More tags can be added as needed. In particular, while testing the DKIM support, it is advisable to add the ‘`t=y`’ tag.

### 5.39 Sockmap Functions ¶

Socket map (sockmap for short) is a special type of database used in Sendmail and MeTA1. It uses a simple server/client protocol over INET or UNIX stream sockets. The server listens on a socket for queries. The client connects to the server and sends it a query, consisting of a map name and a key separated by a single space. Both map name and key are sequences of non-whitespace characters. The map name serves to identify the type of the query. The server replies with a response consisting of a status indicator and result, separated by a single space. The result part is optional.

For example, following is the query for key ‘`smith`’ in map ‘`aliases`’:

    11:aliases news,

A possible reply is:

    18:OK root@domain.net,

This reply means that the key ‘`news`’ was found in the map, and the value corresponding to that key is ‘`root@domain.net`’.

The following reply means the key was not found:

    8:NOTFOUND,

For a detailed description of the sockmap protocol, see Protocol in Smap manual.

The MFL library provides two primitives for dealing with sockmaps. Both primitives become available after requiring the `sockmap` module.

Library Function: `string` sockmap_lookup `(number fd, string map, string key)` ¶

  This function look ups the key in the map. The fd refers to the sockmap to use. It must be obtained as a result of a previous call to `open` with the URL of the sockmap as its first argument (see open). For example:

        number fd open("@ unix:///var/spool/meta1/smap/socket")
        string ret sockmap_query(fd, "aliases", $rcpt_to)
        if ret matches "OK (.+)"
          set alias \1
        fi
        close(fd)

Library Function: `string` sockmap_single_lookup `(string url, string map, string key)` ¶

  This function connects to the sockmap identified by the url, queries for key in map and closes the connection. It is useful when you need to perform only a single lookup on the sockmap.

### 5.40 National Language Support Functions ¶

The National Language Support functions allow you to write your scripts in such a way, that any textual messages they display are automatically translated to your native language, or, more precisely, to the language required by your current locale.

This section assumes the reader is familiar with the concepts of program internationalization and localization. If not, please refer to The Purpose of GNU `gettext` in GNU gettext manual, before reading further.

In general, internationalization of any MFL script follows the same rules as described in the GNU gettext manual. First of all, you select the program message domain, i.e. the identifier of a set of translatable messages your script contain. This identifier is then used to select appropriate translation. The message domain is set using `textdomain` function. For the purposes of this section, let’s suppose the domain name is ‘`myfilter`’. All NLS functions are provided in the `nls` module, which you need to require prior to using any of them.

To find translations of textual message to the current locale, the underlying `gettext` mechanism will look for file `dirname/locale/LC_MESSAGES/domainname.mo`, where dirname is the message catalog hierarchy name, locale is the locale name, and domainname is the name of the message domain. By default dirname is `/usr/local/share/locale`, but you may change it using `bindtextdomain` function. The right place for this initial NLS setup is in the ‘`begin`’ block (see The ‘`begin`’ and ‘`end`’ special handlers). To summarize all the above, the usual NLS setup will look like:

    require nls

    begin
    do
      textdomain("myfilter")
      bindtextdomain("myfilter", "/usr/share/locale");
    done

For example, given the settings above, and supposing the environment variable `LC_ALL` is set to ‘`pl`’, translations will be looked in file `/usr/share/locale/pl/LC_MESSAGES/myfilter.mo`.

Once this preparatory work is done, you can request each message to be translated by using `gettext` function, or `_` (underscore) macro. For example, the following statement will produce translated textual description for ‘`450`’ response:

    tempfail 450 4.1.0 _("Try again later")

Of course it assumes that the appropriate `myfile.mo` file already exists. If it does not, nothing bad happens: in this case the macro `_` (as well as `gettext` function) will simply return its argument unchanged, so that the remote party will get the textual message in English.

The ‘`mo`’ files are binary files created from ‘`po`’ source files using `msgfmt` utility, as described in Producing Binary MO Files in GNU gettext manual. In turn, the format of ‘`po`’ files is described in The Format of PO Files in GNU gettext manual.

Built-in Function: `string` bindtextdomain `(string domain, string dirname)` ¶

  This function sets the base directory of the hierarchy containing message catalogs for a given message domain.

  domain is a string identifying the textual domain. If it is not empty, the base directory for message catalogs belonging to domain domain is set to dirname. It is important that dirname be an absolute pathname; otherwise it cannot be guaranteed that the message catalogs will be found.

  If domain is ‘`""`’, `bindtextdomain` returns the previously set base directory for domain domain.

The rest of this section describes the NLS functions supplied in the `nls` module.

Built-in Function: `string` dgettext `(string domain, string msgid)` ¶

  `dgettext` attempts to translate the string msgid into the currently active locale, according to the settings of the textual domain domain. If there is no translation available, `dgettext` returns msgid unchanged.

Built-in Function: `string` dngettext `(string domain, string msgid, string msgid_plural, number n)` ¶

  The `dngettext` functions attempts to translate a text string into the language specified by the current locale, by looking up the appropriate singular or plural form of the translation in a message catalog, set for the textual domain domain.

  See Additional functions for plural forms in GNU gettext utilities, for a discussion of the plural form handling in different languages.

Library Function: `string` textdomain `(string domain)` ¶

  The `textdomain` function sets the current message domain to domain, if it is not empty. In any case the function returns the current message domain. The current domain is ‘`mailfromd`’ initially. For example, the following sequence of `textdomain` invocations will yield:

      textdomain("") ⇒ "mailfromd"
      textdomain("myfilter") ⇒ "myfilter"
      textdomain("") ⇒ "myfilter"

Library Function: `string` gettext `(string msgid)` ¶

  `gettext` attempts to translate the string msgid into the currently active locale, according to the settings of the current textual domain (set using `textdomain` function). If there is no translation available, `gettext` returns msgid unchanged.

Library Function: `string` ngettext `(string msgid, string msgid_plural, number n)` ¶

  The `ngettext` functions attempts to translate a text string into the language specified by the current locale, by looking up the appropriate singular or plural form of the translation in a message catalog, set for the current textual domain.

  See Additional functions for plural forms in GNU gettext utilities, for a discussion of the plural form handling in different languages.

### 5.41 Syslog Interface ¶

The basic means for outputting diagnostic messages is the ‘`echo`’ instruction (see The `echo` statement), which sends its arguments to the currently established logging channel. In daemon mode, the latter is normally connected to syslog, so any echoed messages are sent there with the facility selected in mailfromd configuration and priority ‘`info`’.

If you want to send a message to another facility and/or priority, use the ‘`syslog`’ function:

Built-in Function: `void` syslog `(number priority, string text)` ¶

  Sends text to syslog. The priority argument is formed by ORing the facility and the level values (explained below). The facility level is optional. If not supplied, the currently selected logging facility is used.

The facility specifies what type of program is logging the message, and the level indicates its relative severity. The following symbolic facility values are declared in the `syslog` module: ‘`LOG_KERN`’, ‘`LOG_USER`’, ‘`LOG_MAIL`’, ‘`LOG_DAEMON`’, ‘`LOG_AUTH`’, ‘`LOG_SYSLOG`’, ‘`LOG_LPR`’, ‘`LOG_NEWS`’, ‘`LOG_UUCP`’, ‘`LOG_CRON`’, ‘`LOG_AUTHPRIV`’, ‘`LOG_FTP`’ and ‘`LOG_LOCAL0`’ through ‘`LOG_LOCAL7`’

The declared severity levels are: ‘`LOG_EMERG`’, ‘`LOG_ALERT`’, ‘`LOG_CRIT`’, ‘`LOG_ERR`’, ‘`LOG_WARNING`’, ‘`LOG_NOTICE`’, ‘`LOG_INFO`’ and ‘`LOG_DEBUG`’.

### 5.42 Debugging Functions ¶

These functions are designed for debugging the MFL programs.

Built-in Function: `void` debug `(string spec)` ¶

  Enable debugging. The value of spec sets the debugging level. See debugging level specification, for a description of its format.

  For compatibility with previous versions, this function is also available under the name ‘`mailutils_set_debug_level`’.

Built-in Function: `number` debug_level `([string srcname])` ¶

  This function returns the debugging level currently in effect for the source module srcname, or the global debugging level, if called without arguments.

  For example, if the program was started with `--debug='all.trace5;engine.trace8'` option, then:

      debug_level() ⇒ 127
      debug_level("engine") ⇒ 1023
      debug_level("db") ⇒ 0

Built-in Function: `boolean` callout_transcript `([boolean value])` ¶

  Returns the current state of the callout SMTP transcript. The result is 1 if the transcript is enabled and 0 otherwise. The transcript is normally enabled either by the use of the `--transcript` command line option (see SMTP transcript) or via the ‘`transcript`’ configuration statement (see transcript).

  The optional value, supplies the new state for SMTP transcript. Thus, calling ‘`callout_transcript(0)`’ disables the transcript.

  This function can be used in bracket-like fashion to enable transcript for a certain part of MFL program, e.g.:

      number xstate callout_transcript(1)
      on poll $f do
        ...
      done
      set xstate callout_transcript(0)

  Note, that the use of this function (as well as the use of the `--transcript` option) makes sense only if callouts are performed by the `mailfromd` daemon itself. It will not work if a dedicated callout server is used for that purpose (see `calloutd`).

Built-in Function: `string` debug_spec `([string catnames, bool showunset])` ¶

  Returns the current debugging level specification, as given by `--debug` command line option or by the `debug` configuration statement (see Logging and Debugging configuration).

  If the argument srcnames is specified, it is treated as a semicolon-separated list of categories for which the debugging specification is to be returned.

  For example, if `mailfromd` was started with `--debug=all.trace5;spf.trace1;engine.trace8;db.trace0`, then:

      debug_spec() ⇒ "all.trace5,engine.trace8"
      debug_spec("all;engine") ⇒ "all.trace5,engine.trace8"
      debug_spec("engine;db") ⇒ "db.trace0;engine.trace8"
      debug_spec("prog") ⇒ ""

  When called without arguments, `debug_spec` returns only those categories which have been set, as shown in the first example above.

  Optional showunset parameters controls whether to return unset module specifications. To print all debugging specifications, whether set or not, use

      debug_spec("", 1)

These three functions are intended to complement each other. The calls to `debug` can be placed around some piece of code you wish to debug, to enable specific debugging information for this code fragment only. For example:

        /* Save debugging level for dns.c source */
        set dlev debug_spec("dns", 1)
        /* Set new debugging level */
        debug("dns.trace8")
        .
        .
        .
        /* Restore previous level */
        debug(dlev)

Built-in Function: `void` program_trace `(string module)` ¶

  Enable tracing for a set of modules given in module argument. See --trace-program, for a description of its format.

Built-in Function: `void` cancel_program_trace `(string module)` ¶

  Disable tracing for given modules.

This pair of functions is also designed to be used together in a bracket-like fashion. They are useful for debugging `mailfromd`, but are not advised to use otherwise, since tracing slows down the execution considerably.

Built-in Function: `void` stack_trace `()` ¶

  Generate a stack trace in this point. See tracing runtime errors, for the detailed description of stack traces.

Built-in Function: `string` stack_trace_format `(string fmt)` ¶

  Format each frame in stack trace to a string according to fmt, and return the concatenated strings. The format string fmt consists of regular characters, which are copied to the resulting string verbatim, and conversion specifiers, prefixed with percent sign, which expand to parts of stack trace. The following conversion specifiers are defined:

  `%d`

    Frame number, counting from the innermost (last) frame, 0-based.

  `%f`

    Name of the function.

  `%n`

    Line number in the source file.

  `%s`

    Name of the source file.

  `%p`

    Value of the PC register.

  `%%`

    Single percent sign.

  A ‘`%`’ followed by any other letter is reproduced verbatim.

Built-in Function: `string` stack_trace_top `(string fmt)` ¶

  Format the top-level stack frame according to fmt. See above for a discussion of format string.

The functions below are intended mainly for debugging MFL run-time engine and for use in `mailfromd` testsuite. You will hardly need to use them in your programs.

Built-in Function: `void` _expand_dataseg `(number n)` ¶

  Expands the run-time data segment by at least n words.

Built-in Function: `number` _reg `(number r)` ¶

  Returns the value of the register r at the moment of the call. Symbolic names for run-time registers are provided in the module `_register`:

  Name | Register
  --- | ---
  REG_PC | Program counter
  REG_TOS | Top of stack
  REG_TOH | Top of heap
  REG_BASE | Frame base
  REG_REG | General-purpose accumulator
  REG_MATCHSTR | Last matched string pointer

Built-in Function: `number` _stack_free `()` ¶

  Returns number of words available for use in stack. This is the same as

      _reg(REG_TOS) - _reg(REG_TOH)

Built-in Function: `number` _heap_reserve `(number n)` ¶

  Use up next n words in the heap. Return the address of the first word.

Built-in Function: `void` _wd `([number n])` ¶

  Enters a time-consuming loop and waits there for n seconds (by default – indefinitely). The intention is to facilitate attaching to `mailfromd` with the debugger. Before entering the loop, a diagnostic message is printed on the ‘`crit`’ facility, informing about the PID of the process and suggesting the command to be used to attach to it, e.g.:

      mailfromd: process 21831 is waiting for debug
      mailfromd: to attach: gdb -ex 'set variable mu_wd::_count_down=0'
       /usr/sbib/mailfromd 21831

### 5.43 Informative Functions ¶

These functions convert numeric identifiers of various MFL entities to strings and vice-versa.

Built-in Function: `string` milter_state_name `(number code)` ¶

  Convert the numeric identifier of a milter state to textual form. It is normally used to convert the `milter_state` variable (see milter state) to string, e.g.:

      milter_state_name(5) ⇒ "connect"

  If code does not refer to a valid milter state, the `e_inval` exception is raised.

Built-in Function: `number` milter_state_code `(string name)` ¶

  Returns numeric code of the milter state name. If name does not refer to a valid milter state, returns 0 (`milter_state_none` from the `milter.mfl` module).

Built-in Function: `string` milter_action_name `(number code)` ¶

  Convert the numeric identifier of a reply action (see reply actions) to textual name.

  If code does not refer to a valid reply action, the `e_inval` exception is raised.

      milter_state_code("connect") ⇒ 5

  This function is useful in action hooks. See Action Hook, for details.

Built-in Function: `number` milter_action_name `(string name)` ¶

  Returns numeric code of the reply action identified by name. If name does not refer to a valid action, returns -1.

### 5.44 Mfmod Interface Functions ¶

The calls described in this section provide interface for invoking functions defined in a dynamically loaded library and retrieving their return values. For a detailed description of this interface and a discussion of its use, see Dynamically Loaded Modules.

Built-in Function: `number` dlopen `(filename)` ¶

  Loads the dynamically loaded library filename and returns a numeric handle that can be used to call functions from that library.

  Unless filename is an absolute pathname, it will be looked up in mfmod search path, defined by the configuration variable `runtime.mfmod-path` (see mfmod-path).

  Maximum number of dynamic libraries that can be loaded simultaneously is limited by the configuration variable `runtime.max-mfmods`.

  Once open, the library remains loaded until `mailfromd` exits. There is no `dlclose` function, since it is not deemed necessary (at the time of this writing, at least). Therefore, the common practice is to call this function in a `begin` section (see The ‘`begin`’ and ‘`end`’ special handlers) and assign its return value to a global or static variable, which will then be used by further `dl*` calls in this module.

Built-in Function: `any` dlcall `(number dlh, string symbol, string types, ...)` ¶

  Calls a mfmod function symbol from the library identified by handle dlh (a value returned by a previous call to `dlopen`). The types parameter defines types of the remaining arguments. It is a list of type letters, one for each argument:

  ‘`s`’

    String value.

  ‘`n`’
  ‘`d`’

    Numeric value.

  ‘`m`’

    Message.

  An example usage:

      set dlh dlopen("mfmod_test.so")
      string x dlcall(dlh, "runtest", "sn", "input string", 3)

  This example calls the function ‘`runtest`’ from the `mfmod_test.so` library with two arguments: a string ‘`input string`’ and numeric value ‘`3`’ and assings the return value to the variable `x`.

  Type of the return value from `dlcall` is determined by the value of `retval.type` upon return from the underlying library function. See Loadable Library, for a detailed description.

  For more details about using this function, Interface Module.

#### Footnotes

##### (22)

That is, if it supports Milter protocol 6 and upper. Sendmail 8.14.0 and Postfix 2.6 and newer do. MeTA1 (via `pmult`) does as well. See Using `mailfromd` with Various MTAs, for more details.

##### (23)

Support for other locales is planned for future versions.

##### (24)

For example:

    prog header
    do
      echo unfold($2)
    done

##### (25)

Note, that the return code is inverted in respect to the system function ‘`access(2)`’.

##### (26)

It is made optional in order to provide backward compatibility with the releases of mailfromd prior to 5.0.93.

##### (27)

Although RFC 4408 introduces a special `SPF` record type for this purpose, it is not yet widely used. As of version 9.1, MFL does not support `SPF` DNS records.

##### (28)

https://tools.ietf.org/html/rfc4871