String Expansion

You can configure Exim in many different ways by using string expansions and lookups. String expansion is triggered by a dollar sign ($), the expander copies the string from left-to-right until it hits a dollar symbol at which point it reads to the end of the expansion item, does what ever processing is required and adds the resulting substring to its output before continuing to read the rest of the original string. Most of the expansions use curly brackets as delimiters but not all.

String expansion example

Before-${substr_4_2:$local_part}-After

Note:
${substr_4_2:$local_part} is the string exapnsion

Escaping Literal Substrings

Sometimes you may what to include a dollar symbol, braces, slashes, etc. Exim makes it possible by escaping a character which means to take the character literally. There are two ways to perform this

Using \ ${if match {$local_part} {dollar\$@datadisk.co.uk}}
Using \N when you have lots of escaping

${if match {$local_part} {\Ndollar$$$$$$$@datadisk.co.uk\N}}

Note: if you have lots of escaping, then use \N

Variable Substitution

Variables substitution can come in two favors, and you can use the def condition to see if a varaible has been defined or not.

Variable substitution $local_part
${local_part}

${if def:sender_host_address {remote}{local}} # yields the string remote if empty and local otherwise

Header Insertion

The contents of a specific message header line can be inserted into a string by an item of the form $header_from:, the abbrevation $h can be used instead of $header.

Operations on Strings

Exim can operation on some portions of string having expanded the string first, ther are in the form of  ${<operator-name>:<substring>}. In some cases the operator name is followed by one or more argument values separated by underscores. The string starts immediately after the colon, and may have leading and/or trailing whitespace.

Extracting the initial part of a substring

# Lets assume $local_part equals paul
${length_1:$local_part}                                 # produced p

# Extracting from a file
${length_1:${lookup{root}lsearch{/etc/passwd}}}         # produced x in my case
${length_2:${lookup{root}lsearch{/etc/passwd}}}         # produced x:

Note:
length_1 extract the first character of a string
length_2 extract the first two characters of a string

Extracting an arbitrary part of a substring

${substr_6_4:${lookup{root}lsearch{/etc/passwd}}}       # produced root

${substr_-4_4:${lookup{root}lsearch{/etc/passwd}}}      # produced bash

${substr_-10:${lookup{root}lsearch{/etc/passwd}}}        # produced x:0:0:root:/root (the remainder)

Note:
substr_6_4 means start at position 6 and extract 4 characters (the offset starts at 0 (zero))
substr_-4_4 means start from 4 positions from the end (-4) and extract 4 characters (end offset is -1)
substr_-10 means start from 10 positions from the end and obtain the reminder.

Hashing

${hash_5:${lookup{root}lsearch{/etc/passwd}}}       # produced megiq

${nhash_5:${lookup{root}lsearch{/etc/passwd}}}      # produced 0

Note:
hash_1 means produce a hash string of one character
hash_5 means produce a hash string of five characters
nhash_5 uses the newer hash function which can handle larger numbers

Forcing case letters $home/mail/${lc:$sender_address}                    # force to lower case
$home/mail/${uc:$sender_address}                    # force to upper case
IP address masking ${mask:$sender_host_address/26}                     # expand sender_host_address
Quoting local parts ${quote_local_part:$local_part}@datadisk.co.uk      # exim double quotes the local_part
Quoting data from regular expressions ${if match{$h_to:}{^.*${rxquote:$local_part@datadisk.co.uk}} {..

Note: rxquote inserts a backslash before any non-alphanumeric characters

You can perform some simple arithmetic in Exim

Simple Arithmetic

${eval:1+1}                   # produces 2
${eval:1+1*3}                 # produces 4
${eval:(1+2)*3}               # produces 9
${eval:2+42%5}                # produces 4

Note: you can also use /, 0x, |, ^, &, >>, <<, ~

Character Translation

The tr expansion item translates single characters in strings into different characters, according to its arguments.

tr example ${tr {a,b,c}{,}{:}}           # produces a:b:c

Note:
{,} pattern to match
{:} the replacement pattern

Text Substitution

In Perl you can use the s (substition) with the g (global) to replace test, Exim has a simular feature

Text substitution

${sg {abcdefabcdef}{abc}{***}}            # produces ***def***def

${sg {abcdef}{^(...)(...)\$}{\N$2$1\N}}   # produces defabc, (...)(...) is $1 and $2

Conditional Expansion

Exim has the capabilities to use condition expressions using the if statement, the if statement has a number of comparsion operators both string and numeric.

String Comparison
eq Equal
eqi Equal, case independent
ge Greater or equal
gei Greater or equal, case independent
gt Greater
gti Greater, case independent
le less or equal
lei less or equal, case independent
lt less
lti less, case independent
Numeric Comparison
= equal
== equal
> greater
>= great or equal
< less
<= less or equal

Now for some examples

if statement

${if <condition> {<string1>}{<string2>}}

Note: if the condition is meet then string1 is expanded, other string2 is expanded

if examples

## String

${if eq{$local_part}{paul} {/home/paul/inbox} {/var/mail/$local_part}}

${if ! eq{$local_part}{paul} {/var/mail/$local_part} {/home/paul/inbox}}  # using negation

${if match {$local_part}{\N^x(\d\d)\N}{$1}}                               # capture the match in $1

## Numerical

${if > {$message_size}{10M}{...                                           # true is message is greater 10M

Note: you can of course nest if statements, there are other conditions that you can use, i will you to investigate these

match_ip
match_domain
crypteq
pam
saslauthd
radius
ldapauth

You can check for empty variables and non-existent header lines and file existance

Empty variable ${if def:sender_host_address {remote}{local}} # yields the string remote if empty and local otherwise
File existance

${if exists{/var/oldmail/$local_part}{old}{new}mail/$local_part}

Note: expands to /var/oldmail/paul if it exists otherwise /var/newmail/paul

You can check the statement of the message delivery

Check delivery condition = ${first_delivery}

You can force the expansion failure, by using two substrings "true" and "false", by using "false" you force the string expansion to fail.

Force failure

header_add = ${if eq{$sender_host_address} {}{X-Postmaster: <postmaster@datadisk.co.uk>} fail}

Note: when $sender_host_address is not empty false forces the string expansion to fail which causes the header addition to be cancelled

Lookups in Expansion Strings

You can perform powerful lookups with Exim, it is in the form of a conditional expansion containing two substrings following the specification of the lookup, if the lookup succeeds the first substring is expanded and used, during its expansion the variable $value contains the data that was looked up. If the lookup fails the second substring is expanded and used, just in case of an if condition, the second substring may be absent or the word fail may used as described in the previous section.

single-key lookups ${lookup {$local_part} lsearch{/the/file} {$value}{/var/mail/$local_part}}

{$local_part}              - is the key to lookup
{/the/file}                - is the file that we lookup the key value from above
{$value}                   - is the value obtained from the file using the key
{/var/mail/$local_part}    - is used if the lookup fails , no key value found in the file

Inserting Whole Files

It is possible to insert a whole file into a expansion string by using readfile

Inserting whole files ${readfile{/etc/company_message}{<end-of-line string>}}

Extracting Fields from Substrings

You can extract data fields from substrings, having expanded them first

splitting up addresses local part is ${localpart:$sender_address}
domain is ${domain:$sender_address}
extracting name fields # The file may contain
pvalle: uid=100 gid=100 home=/export/home/pvalle

# To extract the uid using a lookup
${extract{uid}{ ${lookup{pvalle}lsearch{/the/file}} }}

# Using a text string
${extract{uid} {uid=100 gid=100 home=/export/home/pvalle}}
extract field location ${extract{4}{:} {0:1:2:3:4:5} }        # produces 3 (remember we start at 0 and use : as the delimiter)

Note:
{4} field number we want
{:} the delimiter we use

Calling External Code

You can communicate with other process using a socket, running an external program and using embedded Perl. I provide a basic syntax but I will leave you to investigate further.

Socket ${readsocket{/socket/name}{$auth1:$auth2}}
Running external program ${run {<command> <args>} {<string1>}{<string2>}}
Embedded Perl ${perl{func}{argument1}{argument2} ... }

Testing String Expansion

Exim provides a way to test your string expansions without actually sending a message or causing any problems with Exim, by usng the -be option

Testing string expansions $ exim -be
> ${lookup {root} lsearch{/etc/passwd}}
x:0:1:Super User:/:/bin/sh
>