--------------------------------------------------------------
The exiscan-acl patch for exim4 - Documentation
--------------------------------------------------------------
(c) Tom Kistner <tom@duncanthrax.net> 2003-????
License: GPL

The exiscan-acl patch adds  content scanning to the  exim4 ACL
system. It supports the following scanning features:

 - MIME ACL that is called for all MIME parts in
   incoming MIME messages.
 - Antivirus using 3rd party scanners.
 - Anti-spam using SpamAssassin.
 - Anti-spam using Brightmail Antispam.
 - SPF support via the libspf2 library
 - SRS support via the libsrs_alt library
 - Regular expression match against headers, bodies, raw
   MIME parts and decoded MIME parts.
 
These features are hooked  into exim by extending  exim's  ACL
system. The patch adds expansion variables and ACL conditions.
These conditions are designed to be used in the  acl_smtp_data
ACL. It is  run when the  sending host has  completed the DATA
phase and is waiting for our final response to his end-of-data
marker.  This  allows   us  to   reject  messages   containing
unwanted content at that stage.

Support for Brightmail AntiSpam requires special  compile-time
flags. Please refer to chapter 7 for details.

The   default   exim   configure   file   contains   commented
configuration examples for some features of exiscan-acl.


0. Overall concept / Overview
--------------------------------------------------------------

The   exiscan-acl   patch    extends Exims with  mechanisms to
deal with the  message body content.  Most of these  additions
affect the ACL system. The exiscan patch adds

- A new ACL, called 'acl_smtp_mime' (Please see detailed
  chapter on this one below).
- ACL conditions and modifiers
  o malware (attach 3rd party virus/malware scanner)
  o spam (attach SpamAssassin)
  o regex (match regex against message, linewise)
  o decode (decode MIME part to disk)
  o mime_regex (match regex against decoded MIME part)
  o control = fakereject (reject but really accept a message)
  o spf (do SPF checks)
- expansion variables
  (see chapters below for names and explanations)
- configuration options in section 1 of Exim's configure file.
  o av_scanner (type and options of the AV scanner)
  o spamd_address (network address / socket of spamd daemon).

All  facilites work  on a  MBOX copy  of the  message that  is
temporarily spooled up in a file called:

  <spool_directory>/scan/<message_id>/<message_id>.eml

The .eml extension is a  friendly hint to virus scanners  that
they can expect an  MBOX-like structure inside that  file. The
file is only spooled up once, when the first exiscan  facility
is  called. Subsequent  calls to  exiscan conditions will just
open  the file again.   The directory is  recursively  removed
when the  acl_smtp_data has  finished running.  When  the MIME
ACL decodes files, they will  be put into that same  folder by
default.


1. The acl_smtp_mime MIME ACL
--------------------------------------------------------------

Note: if you are not familiar with exims ACL system, please go
read the documentation on it, otherwise this chapter will  not
make much sense to you.

Here are the facts on acl_smtp_mime:

  - It  is  called  once  for each  MIME  part  of  a message,
    including  multipart  types,  in  the  sequence  of  their
    position in the message.
  
  - It is called just before the acl_smtp_data ACL. They share
    a result code  (the one assed  to the remote  system after
    DATA).  When  a  call  to  acl_smtp_mime  does  not  yield
    "accept",  ACL processing  is aborted  and the  respective
    result code is sent to the remote mailer. This means  that
    the acl_smtp_data is NOT called any more.
    
  - It is ONLY called if the message has a MIME-Version header.

  - MIME parts will NOT be dumped to disk by default, you have
    to call  the "decode"  condition to  do that  (see further
    below).

  - For  RFC822 attachments  (these are  messages attached  to
    messages,  with   a   content-type   of 'message/rfc822'),
    the ACL   is  called   again  in    the  same   manner  as
    for  the "primary" message, only  that the $mime_is_rfc822
    expansion variable  is  set  (see below).  These  messages
    are always  decoded  to  disk  before  being  checked, but
    the files  are unlinked once the check is done.
    
To activate acl_smtp_mime, you need to add assign it the  name
of an  ACL entry  in section  1 of  the config  file, and then
write that ACL in the ACL section, like:

  /* ---------------
  
  # -- section 1 ----
  [ ... ]
  acl_smtp_mime = my_mime_acl
  [ ... ]
  
  # -- acl section ----
  begin acl
  
  [ ... ]
  
  my_mime_acl:
  
    < ACL logic >
    
  [ ... ]
  
  ---------------- */

The following list describes all expansion variables that are
available in the MIME ACL:

  $mime_content_type
  ------------------
  A very important variable. If  the MIME part has a  "Content
  -Type:"  header,  this  variable  will  contain  its  value,
  lowercased,  and  WITHOUT   any  options  (like   "name"  or
  "charset", see below for  these). Here are some  examples of
  popular MIME types, as they may appear in this variable:
  
  text/plain
  text/html
  application/octet-stream
  image/jpeg
  audio/midi

  If  the  MIME  part  has  no  "Content-Type:"  header,  this
  variable is the empty string.


  $mime_filename
  --------------
  Another important variable, possibly the most important one.
  It contains a  proposed filename for  an attachment, if  one
  was  found  in   either  the  "Content-Type:"   or  "Content
  -Disposition"   headers.   The  filename   will   be RFC2047
  decoded,  however NO  additional sanity checks are done. See
  instructions on "decode" further  below. If no filename  was
  found, this variable is the  empty string.
  
  
  $mime_charset
  -------------
  Contains the  charset identifier,  if one  was found  in the
  "Content-Type:" header. Examples for charset identifiers are
  
  us-ascii
  gb2312 (Chinese)
  iso-8859-1
  
  Please note that this value  will NOT be normalized, so  you
  should do matches case-insensitively.
  
  
  $mime_boundary
  --------------
  If the current part is a multipart (see  $mime_is_multipart)
  below, it  SHOULD have  a boundary  string. It  is stored in
  this variable. If the current part has no boundary parameter
  in the  "Content-Type:" header,  this variable  contains the
  empty string. 
  
  
  $mime_content_disposition
  -------------------------
  Contains   the   normalized   content   of   the    "Content
  -Disposition:"   header.   You  can   expect   strings  like
  "attachment" or "inline" here.
  
  
  $mime_content_transfer_encoding
  -------------------------------
  Contains   the   normalized   content   of   the    "Content
  -transfer-encoding:"   header. This  is a symbolic  name for
  an encoding  type. Typical  values are  "base64" and "quoted
  -printable".


  $mime_content_id
  ----------------
  Contains   the   normalized   content   of   the    "Content
  -ID:"   header.  This is  a unique  ID that  can be  used to
  reference a part from another part.
  
  
  $mime_content_description
  -------------------------
  Contains   the   normalized   content   of   the    "Content
  -Description:"    header.  It can  contain  a human-readable
  description of the parts content. Some implementations  will
  repeat  the  filename  for attachments  here,  but  they are
  usually only used for display purposes.
  
  
  $mime_part_count
  ----------------
  This is  a counter  that is  raised for  each processed MIME
  part. It starts  at zero for  the very first  part (which is
  usually a multipart). The  counter is per-message, so  it is
  reset    when    processing    RFC822    attachments    (see
  $mime_is_rfc822). The counter stays set after  acl_smtp_mime
  is complete, so you can use it in the DATA ACL to  determine
  the  number  of  MIME  parts  of  a  message.  For  non-MIME
  messages, this variable will contain the value -1.
  
  
  $mime_is_multipart
  ------------------
  A  "helper"  flag   that  is  true  (1)  when  the   current
  part   has   the   main   type  "multipart",    for  example
  "multipart/alternative"    or     "multipart/mixed".   Since
  multipart entities only serve as containers for other parts,
  you may not want to carry out specific actions on them.
 
 
  $mime_is_coverletter
  --------------------
  This flag attempts to differentiate the "cover letter" of an
  e-mail from attached data.  It can be used to clamp down  on
  "flashy"   or   unneccessarily   encoded   content   in  the
  coverletter, while not restricting attachments at all.

  It returns 1 (true) for a  MIME part believed to be part  of
  the  coverletter,  0   (false)  for   an  attachment.     At
  present,  the algorithm is as follows:

  1. The outermost MIME part of a message always coverletter.
  2. If a multipart/alternative or multipart/related MIME part
     is coverletter, so are all MIME subparts within that
     multipart.
  3. If any other multipart is coverletter, the first subpart
     is coverletter, the rest are attachments.
  4. All parts contained within an attachment multipart are
     attachments.

  As an example, the following will ban "HTML mail" (including
  that sent with alternative plain text), while allowing  HTML
  files to be attached:
  
    /* ----------------------
    deny message = HTML mail is not accepted here
         condition = $mime_is_coverletter
         condition = ${if eq{$mime_content_type}{text/html}{1}{0}}
    ----------------------- */
  
  
  $mime_is_rfc822
  ---------------
  This flag is true (1) if  the current part is NOT a  part of
  the checked message itself, but part of an attached message.
  Attached message decoding is fully recursive.


  $mime_decoded_filename
  ----------------------
  This variable is only set after the "decode" condition  (see
  below) has been successfully run. It contains the full  path
  and file name of the file containing the decoded data.
  
  
  $mime_content_size
  ------------------
  This variable is only set after the "decode" condition  (see
  below) has been  successfully run. It  contains the size  of
  the  decoded   part  in  kilobytes(!).  The  size  is always
  rounded up  to full  kilobytes, so  only a  completely empty
  part will have a mime_content_size of 0.


The expansion variables only  reflect the content of  the MIME
headers for each  part. To actually  decode the part  to disk,
you can use the "decode" condition. The general syntax is

decode = [/<PATH>/]<FILENAME>

The right hand side is expanded before use. After expansion,
the value can

  - be '0' or 'false', in which case no decoding is done.
  - be the string 'default'. In that case, the file will be
    put in the temporary "default" directory
    <spool_directory>/scan/<message_id>/
    with a sequential file name, consisting of the message  id
    and a sequence number. The full path and name is available
    in $mime_decoded_filename after decoding.
  - start  with  a slash.  If  the full  name  is an  existing
    directory,  it  will  be used  as  a  replacement for  the
    "default"  directory.  The  filename  will  then  also  be
    sequentially assigned. If the name does not exist, it will
    be used as the full path and file name.
  - not  start with  a slash.  It will  then be  used as  the
    filename, and the default path will be used.
    
You  can  easily decode  a  file with  its  original, proposed
filename using "decode = $mime_filename". However, you  should
keep in  mind that  $mime_filename might  contain anything. If
you place files outside of the default path, they will not  be
automatically unlinked.

The  MIME  ACL  also  supports  the  regex=  and   mime_regex=
conditions. You  can use  those to  match regular  expressions
against raw  and decoded  MIME parts,  respectively. Read  the
next section for more information on these conditions.



2. Match message or MIME parts against regular expressions
--------------------------------------------------------------

The "regex" condition takes one or more regular expressions as
arguments and  matches them   against the  full message  (when
called in the DATA ACL) or a raw MIME part (when called in the
MIME  ACL). The  "regex" condition  matches linewise,  with a
maximum line length  of 32k characters.  That means you  can't
have multiline matches with the "regex" condition.

The  "mime_regex"  can only  be  called in  the  MIME ACL.  It
matches up  to 32k  of decoded  content (the  whole content at
once, not linewise). If the part has not been decoded with the
"decode"  condition  earlier  in  the  ACL,  it   is   decoded
automatically  when "mime_regex"  is  executed (using  default
path and  filename values).   If the  decoded data   is larger
than   32k,   only   the   first   32k   characters   will  be
matched.

The regular expressions are passed as a colon-separated  list.
To include  a literal  colon, you  must double  it. Since  the
whole right-hand  side string  is expanded  before being used,
you must also escape dollar ($) signs with backslashes.

Here is a simple example:

/* ----------------------
deny message = contains blacklisted regex ($regex_match_string)
     regex = [Mm]ortgage : URGENT BUSINESS PROPOSAL
----------------------- */

The  conditions   returns  true    if  one   of  the   regular
expressions  has matched.  The  $regex_match_string  expansion
variable  is then  set up  and contains  the matching  regular
expression.

Warning: With large messages,  these conditions can be  fairly
CPU-intensive.



3. Antispam measures with SpamAssassin
--------------------------------------------------------------

The "spam" ACL  condition calls SpamAssassin's  "spamd" daemon
to get a spam-score  and a  report for  the message.  You must
first install     SpamAssassin.     You     can     get     it
at http://www.spamassassin.org, or,   if you  have  a  working
Perl installation, you can use CPAN by calling

perl -MCPAN -e 'install Mail::SpamAssassin'

SpamAssassin has its own  set of configuration files.  Please
review its  documentation to  see how  you can  tweak it.  The
default installation should work nicely, however.

After having installed and configured SpamAssassin, start  the
"spamd" daemon. By default, it  listens on 127.0.0.1, TCP port
783. If you use another host  or port for spamd, you must  set
the   spamd_address  option   in  Section   1  of   the  exim
configuration as follows (example):

spamd_address = 127.0.0.1 783

As of version 2.60, spamd also supports communication over UNIX
sockets. If you want to use these, supply spamd_address with
an absolute file name instead of a address/port pair, like:

spamd_address = /var/run/spamd_socket

If you use the above mentioned default, you do NOT need to set
this option.

You  can   also  have   multiple  spamd   servers  to  improve
scalability. These can also reside on other hardware reachable
over the network. To specify multiple spamd servers, just  put
multiple  address/port  pairs  in  the  spamd_address  option,
separated with colons:

spamd_address = 192.168.2.10 783 : 192.168.2.11 783 : 192.168.2.12 783

Up to  32 spamd  servers are  supported. The  servers will  be
queried in a random fashion.   When a server fails to  respond
to the  connection attempt,   all  other  servers  are   tried
until  one succeeds.   If  no  server   responds,  the  "spam"
condition will  return DEFER.  IMPORTANT:  It  is not possible
to    use  the    UNIX   socket    connection   method    with
multiple  spamd servers.

To use the  antispam facility, put  the "spam" condition  in a
DATA ACL block. Here is a very simple example:

/* ---------------
deny message = This message was classified as SPAM
        spam = joe
---------------- */

On the right-hand side of the spam condition, you can put  the
username that SpamAssassin should scan for. That allows you to
use per-domain or  per-user antispam profiles.  The right-hand
side is expanded before being used, so you can put lookups  or
conditions there. When the right-hand side evaluates to "0" or
"false", no scanning will be done and the condition will  fail
immediately.

If you do not want to  scan for a particular user, but  rather
use the SpamAssassin system-wide default profile, you can scan
for an unknown user, or simply use "nobody".

The  "spam"  condition  will  return  true  if  the  threshold
specified in the user's SpamAssassin profile has been  matched
or exceeded. If  you want to  use the spam  condition for its
side effects (see the variables below), you can make it always
return "true" by appending ":true" to the username. 

When the condition is run, it sets up the following  expansion
variables:

  $spam_score       The spam score of the message, for example
                    "3.4"  or  "30.5".  This  is  useful   for
                    inclusion in log or reject messages.
                  
  $spam_score_int   The spam score of the message,  multiplied
                    by ten, as  an integer value.  For example
                    "34" or "305". This is useful for  numeric
                    comparisons  in  conditions.  See  further
                    below for a more complicated example. This
                    variable is special,  since it is  written
                    to  the  spool  file, so  it  can  be used
                    during the  whole life  of the  message on
                    your exim system, even in routers
                    or transports.
                    
  $spam_bar         A string consisting of a number of '+'  or
                    '-'    characters,    representing     the
                    spam_score value.  A spam  score of  "4.4"
                    would have a  spam_bar of '++++'.  This is
                    useful for  inclusion in  warning headers,
                    since MUAs can match on such strings.
  
  $spam_report      A  multiline  text  table,  containing the
                    full SpamAssassin report for the  message.
                    Useful for inclusion in headers or  reject
                    messages.
            
The spam condition  caches its results.  If you call  it again
with the same  user name, it  will not really  scan again, but
rather return the same values as before.

The spam  condition will  return DEFER  if there  is any error
while running the message through SpamAssassin. If you want to
treat DEFER  as FAIL  (to pass  on to  the next  ACL statement
block), append '/defer_ok' to the right hand side of the  spam
condition, like this:

/* ---------------
deny message = This message was classified as SPAM
        spam = joe/defer_ok
---------------- */

This will  cause messages  to be  accepted even  if there is a
problem with spamd.

Finally, here is  a commented example  on how to  use the spam
condition:

/* ----------------
# put headers in all messages (no matter if spam or not)
warn  message = X-Spam-Score: $spam_score ($spam_bar)
      spam = nobody:true
warn  message = X-Spam-Report: $spam_report
      spam = nobody:true
      
# add second subject line with *SPAM* marker when message
# is over threshold
warn  message = Subject: *SPAM* $h_Subject
      spam = nobody

# reject spam at high scores (> 12)
deny   message = This message scored $spam_score spam points.
       spam = nobody:true
       condition = ${if >{$spam_score_int}{120}{1}{0}}
----------------- */



4. The "malware" facility
   Scan messages for viruses using an external virus scanner
--------------------------------------------------------------

This facility lets you connect virus scanner software to exim.
It supports a "generic"  interface to scanners called  via the
shell,  and  specialized interfaces  for  "daemon" type  virus
scanners, who are resident in memory and thus are much faster.

To use this facility, you MUST set the "av_scanner" option  in
section 1 of  the exim config  file. It specifies  the scanner
type to use, and any  additional options it needs to  run. The
basic syntax is as follows:

  av_scanner = <scanner-type>:<option1>:<option2>:[...]
  
The following scanner-types are supported in this release:

  sophie      Sophie  is a  daemon that  uses Sophos'  libsavi
              library to scan for viruses. You can get  Sophie
              at http://www.vanja.com/tools/sophie/. The  only
              option for this scanner type is the path to  the
              UNIX  socket   that  Sophie   uses  for   client
              communication.    The     default    path     is
              /var/run/sophie, so if  you are using  this, you
              can omit the option. Example:
              
              av_scanner = sophie:/tmp/sophie

  aveserver   This is the scanner daemon of Kaspersky  Version
              5.   You   can   get   a   trial   version    at
              http://www.kaspersky.com.   This   scanner  type
              takes  one  option,  which is  the  path  to the
              daemon's   UNIX    socket.   The    default   is
              "/var/run/aveserver". Example:
              
              av_scanner = aveserver:/var/run/aveserver


  kavdaemon   This is the scanner daemon of Kaspersky  Version
              4.  This  version of  the  Kaspersky scanner  is
              outdated.   Please   upgrade   (see  "aveserver"
              above).  This  scanner  type  takes  one option,
              which is the path  to the daemon's UNIX  socket.
              The default is "/var/run/AvpCtl". Example:
              
              av_scanner = kavdaemon:/opt/AVP/AvpCtl
              
              
  clamd       Another daemon type scanner, this one is GPL and
              free. Get  it at  http://clamav.elektrapro.com/.
              Clamd does not  seem to unpack  MIME containers,
              so it is recommended to use the demime  facility
              with it.  It takes  one option:  either the path
              and  name   of  a   UNIX  socket   file,  or   a
              hostname/port  pair,  separated  by  space.   If
              unset, the default is "/tmp/clamd". Example:
              
              av_scanner = clamd:192.168.2.100 1234
              or
              av_scanner = clamd:/opt/clamd/socket
  
  
  fsecure     This  is  for  the  F-Secure  (www.f-secure.com)
              scanner. It takes   one argument  which  is  the
              path   to  a   UNIX  socket.   If  omitted,   it
              defaults to
              
              /var/run/.fsav
              
              Example:
              
              av_scanner = fsecure:/path/to/.fsav
       
              Thanks  to   Johan  Thelmen   <jth@home.se>  for
              contributing the code for this scanner.     
                
                                       
  drweb       This one is for the DrWeb (http://www.sald.com/)
              daemon.  It takes  one argument,  either a  full
              path to a UNIX socket, or an IP address and port
              separated  by  whitespace.   If  you  omit   the
              argument, the default
              
              /usr/local/drweb/run/drwebd.sock
              
              is used. Example:
              
              av_scanner = drweb:192.168.2.20 31337
              or
              av_scanner = drweb:/var/run/drwebd.sock
              
              Thanks  to  Alex  Miller  <asm@abbyy.com.ua> for
              contributing the code for this scanner.              
              
              
  mksd        Yet another daemon type scanner, aimed mainly at
              Polish users, though some parts of documentation
              are now avaliable in English.  You can get it at
              http://linux.mks.com.pl/.  The only  option  for
              this  scanner  type  is the  maximum  number  of
              processes   used   simultaneously  to  scan  the
              attachments, provided  that the  demime facility
              is  employed  and also  mksd has been  run  with
              at least  the same  number of  child  processes.
              You can  safely  omit this  option,  the default
              value is 1. Example:
              
              av_scanner = mksd:2


  cmdline     This is the keyword for the generic command line
              scanner  interface.  It can  be  used to  attach
              virus scanners  that are  invoked on  the shell.
              This scanner type takes 3 mantadory options:
              
              - full path and name of the scanner binary, with
                all  command  line options  and  a placeholder
                (%s) for the directory to scan.
                
              - A  regular  expression  to  match  against the
                STDOUT and STDERR output of the virus scanner.
                If the expression matches, a virus was  found.
                You  must  make  absolutely  sure  that   this
                expression only matches on "virus found". This
                is called the "trigger" expression.
                
              - Another regular expression, containing exactly
                ONE pair of braces,  to match the name  of the
                virus found  in the  scanners output.  This is
                called the "name" expression.
                
              Example:
              
              Sophos  Sweep reports  a virus  on a  line  like
              this:

              Virus 'W32/Magistr-B' found in file ./those.bat

              For the  "trigger" expression,  we just  use the
              "found" word. For the "name" expression, we want
              to get the W32/Magistr-B string, so we can match
              for  the single  quotes left  and right  of it,
              resulting in the regex '(.*)' (WITH the quotes!)

              Altogether,   this   makes   the   configuration
              setting:
              
              av_scanner = cmdline:\
              /path/to/sweep -all -rec -archive %s:\
              found:'(.+)'

When av_scanner  is correcly  set, you  can use  the "malware"
condition in the  DATA ACL. The  condition takes a  right-hand
argument that is expanded before use. It can then be one of

  - "true", "*", or "1", in which case the message is  scanned
    for viruses.  The condition  will succeed  if a  virus was
    found, or fail otherwise. This is the recommended usage.
    
  - "false" or "0", in which case no scanning is done and  the
    condition will fail immediately.
    
  - a regular expression, in which case the message is scanned
    for viruses. The condition  will succeed if a  virus found
    found and  its name  matches the  regular expression. This
    allows you  to take  special actions  on certain  types of
    viruses.
    
When a  virus was  found, the  condition sets  up an expansion
variable called  $malware_name that  contains the  name of the
virus found. You  should use it  in a "message"  modifier that
contains the error returned to the sender.

The malware condition caches its  results, so when you use  it
multiple times,  the actual  scanning process  is only carried
out once.

If your virus scanner  cannot unpack MIME and  TNEF containers
itself,  you  should use  the  demime condition  prior  to the
malware condition.

Here is a simple example:

/* ----------------------
deny message = This message contains malware ($malware_name)
     demime = *
     malware = *
---------------------- */

Like with the "spam" condition, you can append '/defer_ok'  to
the malware condition  to accept messages  even if there  is a
problem with the virus scanner, like this:

/* ----------------------
deny message = This message contains malware ($malware_name)
     demime = *
     malware = */defer_ok
---------------------- */

ADVANCED TIP: When the av_scanner option starts with a  dollar
($) sign, it is expanded before being used. This is useful  if
you want to use multiple scanners. You can then set

/* ----------------------
av_scanner = $acl_m0
---------------------- */

and  use  these  ACL  blocks  to  scan  with  both  sophie and
aveserver scanners:

/* ----------------------
deny message = This message contains malware ($malware_name)
     set acl_m0 = sophie
     malware = *
     
deny message = This message contains malware ($malware_name)
     set acl_m0 = aveserver
     malware = *
---------------------- */

However, when  av_scanner is  expanded, the  result caching of
the malware condition is  not used, so each  malware condition
call results in a new scan of the message.



5. The "demime" facility
   MIME unpacking, sanity checking and file extension blocking
--------------------------------------------------------------

* This facility provides a simpler interface to MIME decoding
* than the MIME ACL functionality. It is kept in exiscan for
* backward compatability.

The demime facility unpacks MIME containers in the message. It
detects  errors  in  MIME   containers  and  can  match   file
extensions found  in the  message against  a list.  Using this
facility will produce additional  files in the temporary  scan
directory that contain the unpacked MIME parts of the message.
If you  do antivirus  scanning, it  is recommened  to use  the
"demime" condition before the antivirus ("malware") condition.

The condition name of this facility is "demime". On the  right
hand  side,  you  can  pass  a  colon-separated  list  of file
extensions that it  should match against.  If one of  the file
extensions  is  found,  the  condition  will  return  "OK" (or
"true"), otherwise it will return FAIL (or "false"). If  there
was any TEMPORARY error while demimeing (mostly "disk  full"),
the  condition  will return  DEFER,  and the  message  will be
temporarily rejected.

The right-hand side gets "expanded" before being treated as  a
list, so  you can  have conditions  and lookups  there. If  it
expands  to  an  empty  string,  "false",  or  zero  ("0"), no
demimeing is done and the conditions returns FALSE.

A short example:

/* ------------
deny message = Found blacklisted file attachment
     demime = vbs:com:bat:pif:prf:lnk
--------------- */

When the condition is run, it sets up the following  expansion
variables:

 $demime_errorlevel   When  an error  was detected  in a  MIME
                      container, this   variable contains  the
                      "severity"  of the error,  as an integer
                      number.  The   higher  the   value,  the
                      more  severe    the  error.    If   this
                      variable is unset or zero, no error  has
                      occured.

 $demime_reason       When $demime_errorlevel is greater  than
                      zero,  this  variable  contains  a human
                      -readable  text  string  describing  the
                      MIME error that occured.
                          
 $found_extension     When  the  "demime"  condition   returns
                      "true", this variable contains the  file
                      extension it has found.
                          
Both $demime_errorlevel  and $demime_reason  are set  with the
first call of the "demime"  condition, and are not changed  on
subsequent calls.

If do not  want to check  for any file  extensions, but rather
use  the  demime  facility  for  unpacking  or  error checking
purposes, just pass "*" as the right-hand side value.

Here is a more elaborate example on how to use this facility:

/* -----------------
# Reject messages with serious MIME container errors
deny  message = Found MIME error ($demime_reason).
      demime = *
      condition = ${if >{$demime_errorlevel}{2}{1}{0}}

# Reject known virus spreading file extensions.
# Accepting these is pretty much braindead.
deny  message = contains $found_extension file (blacklisted).
      demime = com:vbs:bat:pif:scr

# Freeze .exe and .doc files. Postmaster can
# examine them and eventually thaw them up.
deny  log_message = Another $found_extension file.
      demime = exe:doc
      control = freeze
--------------------- */



6. The "fakereject" control statement
   Reject a message while really accepting it.
--------------------------------------------------------------

When you put "control =  fakereject" in an ACL statement,  the
following  will  happen:  If  exim  would  have  accepted  the
message, it will tell the remote host that it did not, with  a
message of:

550-FAKE_REJECT id=xxxxxx-xxxxxx-xx
550-Your message has been rejected but is being kept for evaluation.
550 If it was a legit message, it may still be delivered to the target recipient(s).

But exim will go on to treat the message as if it had accepted
it. This should be used with extreme caution, please look into
the examples document for possible usage.



7. Brighmail AntiSpam (BMI) suppport
--------------------------------------------------------------

Brightmail  AntiSpam  is  a  commercial  package.  Please  see
http://www.brightmail.com    for    more    information     on
the product. For  the sake of  clarity, we'll refer  to it  as
"BMI" from now on.


0) BMI concept and implementation overview

In  contrast  to   how  spam-scanning  with   SpamAssassin  is
implemented  in  exiscan-acl,  BMI  is  more  suited  for  per
-recipient  scanning of  messages. However,  each messages  is
scanned  only  once,  but  multiple  "verdicts"  for  multiple
recipients can be  returned from the  BMI server. The  exiscan
implementation  passes  the  message to  the  BMI  server just
before accepting it.  It then adds  the retrieved verdicts  to
the messages header file in the spool. These verdicts can then
be  queried  in  routers,  where  operation  is  per-recipient
instead  of per-message.  To use  BMI, you  need to  take the
following steps:

  1) Compile Exim with BMI support
  2) Set up main BMI options (top section of exim config file)
  3) Set up ACL control statement (ACL section of the config
     file)
  4) Set up your routers to use BMI verdicts (routers section
     of the config file).
  5) (Optional) Set up per-recipient opt-in information.

These four steps are explained in more details below. 

1) Adding support for BMI at compile time

  To compile with BMI support,  you need to link Exim  against
  the   Brighmail  client   SDK,  consisting   of  a   library
  (libbmiclient_single.so)  and  a  header  file  (bmi_api.h).
  You'll also need to explicitly set a flag in the Makefile to
  include BMI support in the Exim binary. Both can be achieved
  with  these 2 lines in Local/Makefile:
  
  CFLAGS=-DBRIGHTMAIL -I/path/to/the/dir/with/the/includefile
  EXTRALIBS_EXIM=-L/path/to/the/dir/with/the/library -lbmiclient_single
  
  If  you use  other CFLAGS  or EXTRALIBS_EXIM  settings then
  merge the content of these lines with them.

  Note for BMI6.x users: You'll also have to add -lxml2_single
  to the EXTRALIBS_EXIM line. Users of 5.5x do not need to  do
  this.
  
  You    should     also    include     the    location     of
  libbmiclient_single.so in your dynamic linker  configuration
  file   (usually   /etc/ld.so.conf)   and   run    "ldconfig"
  afterwards, or  else the  produced Exim  binary will  not be
  able to find the library file.


2) Setting up BMI support in the exim main configuration

  To enable BMI  support in the  main exim configuration,  you
  should set the path to the main BMI configuration file  with
  the "bmi_config_file" option, like this:
  
  bmi_config_file = /opt/brightmail/etc/brightmail.cfg
  
  This must go into section 1 of exims configuration file (You
  can  put it  right on  top). If  you omit  this option,  it
  defaults to /opt/brightmail/etc/brightmail.cfg.

  Note for BMI6.x users: This  file is in XML format  in V6.xx
  and its  name is  /opt/brightmail/etc/bmiconfig.xml. So  BMI
  6.x users MUST set the bmi_config_file option.
  

3) Set up ACL control statement

  To  optimize performance,  it makes  sense only  to process
  messages coming from remote, untrusted sources with the  BMI
  server.  To set  up a  messages for  processing by  the BMI
  server, you MUST set the "bmi_run" control statement in  any
  ACL for an incoming message.  You will typically do this  in
  an "accept"  block in  the "acl_check_rcpt"  ACL. You should
  use the "accept" block(s)  that accept messages from  remote
  servers for your own domain(s). Here is an example that uses
  the "accept" blocks from exims default configuration file:
  

  accept  domains       = +local_domains
          endpass
          verify        = recipient
          control       = bmi_run

  accept  domains       = +relay_to_domains
          endpass
          verify        = recipient
          control       = bmi_run
  
  If bmi_run  is not  set in  any ACL  during reception of the
  message, it will NOT be passed to the BMI server.


4) Setting up routers to use BMI verdicts

  When a message has been  run through the BMI server,  one or
  more "verdicts" are  present. Different recipients  can have
  different verdicts. Each  recipient is treated  individually
  during routing, so you  can query the verdicts  by recipient
  at  that stage.  From Exims  view, a  verdict can  have the
  following outcomes:
  
  o deliver the message normally
  o deliver the message to an alternate location
  o do not deliver the message
  
  To query  the verdict  for a  recipient, the  implementation
  offers the following tools:
  
  
  - Boolean router  preconditions. These  can be  used in  any
    router. For a simple  implementation of BMI, these  may be
    all  that  you  need.  The  following  preconditions   are
    available:
    
    o bmi_deliver_default
    
      This  precondition  is  TRUE  if  the  verdict  for  the
      recipient is  to deliver  the message  normally. If  the
      message has not been  processed by the BMI  server, this
      variable defaults to TRUE.
      
    o bmi_deliver_alternate
    
      This  precondition  is  TRUE  if  the  verdict  for  the
      recipient  is to  deliver the  message to  an alternate
      location.  You  can  get the  location  string  from the
      $bmi_alt_location expansion variable if you need it. See
      further below. If the message has not been processed  by
      the BMI server, this variable defaults to FALSE.
      
    o bmi_dont_deliver
    
      This  precondition  is  TRUE  if  the  verdict  for  the
      recipient  is  NOT  to   deliver  the  message  to   the
      recipient. You will typically use this precondition in a
      top-level blackhole router, like this:
      
        # don't deliver messages handled by the BMI server
        bmi_blackhole:
          driver = redirect
          bmi_dont_deliver
          data = :blackhole:
      
      This router should be on top of all others, so  messages
      that should not be delivered do not reach other  routers
      at all. If   the  message  has  not  been  processed  by
      the  BMI server, this variable defaults to FALSE.
      
      
  - A list router  precondition to query  if rules "fired"  on
    the message for the recipient. Its name is "bmi_rule". You
    use  it  by  passing it  a  colon-separated  list of  rule
    numbers. You can use this condition to route messages that
    matched specific rules. Here is an example:
    
      # special router for BMI rule #5, #8 and #11
      bmi_rule_redirect:
        driver = redirect
        bmi_rule = 5:8:11
        data = postmaster@mydomain.com
      
  
  - Expansion variables. Several  expansion variables are  set
    during  routing.  You  can  use  them  in  custom   router
    conditions,  for  example.  The  following  variables  are
    available:
    
    o $bmi_base64_verdict
    
      This variable  will contain  the BASE64  encoded verdict
      for the recipient being routed. You can use it to add  a
      header to messages for tracking purposes, for example:
      
      localuser:
        driver = accept
        check_local_user
        headers_add = X-Brightmail-Verdict: $bmi_base64_verdict
        transport = local_delivery
      
      If there is no verdict available for the recipient being
      routed, this variable contains the empty string.
    
    o $bmi_base64_tracker_verdict
    
      This variable  will contain  a BASE64  encoded subset of
      the  verdict  information  concerning  the  "rules" that
      fired  on the  message. You  can add  this string  to a
      header, commonly named "X-Brightmail-Tracker". Example:
      
      localuser:
        driver = accept
        check_local_user
        headers_add = X-Brightmail-Tracker: $bmi_base64_tracker_verdict
        transport = local_delivery
        
      If there is no verdict available for the recipient being
      routed, this variable contains the empty string.
      
    o $bmi_alt_location
    
      If  the  verdict  is  to  redirect  the  message  to  an
      alternate  location,  this  variable  will  contain  the
      alternate location string returned by the BMI server. In
      its default configuration, this is a header-like  string
      that can be added to the message with "headers_add".  If
      there is  no verdict  available for  the recipient being
      routed, or if the  message is to be  delivered normally,
      this variable contains the empty string.
      
    o $bmi_deliver
    
      This is an additional integer variable that can be  used
      to query if the message should be delivered at all.  You
      should use router preconditions instead if possible.
      
      $bmi_deliver is '0': the message should NOT be delivered.
      $bmi_deliver is '1': the message should be delivered.
      
   
  IMPORTANT NOTE: Verdict inheritance.
  The  message  is passed  to  the BMI  server  during message
  reception,  using the  target addresses  from the  RCPT TO:
  commands in the SMTP transaction. If recipients get expanded
  or re-written (for example by aliasing), the new address(es)
  inherit the  verdict from  the original  address. This means
  that verdicts also apply to all "child" addresses  generated
  from top-level addresses that were sent to the BMI server.
  
  
5) Using per-recipient opt-in information (Optional)

  The  BMI server  features multiple  scanning "profiles"  for
  individual recipients.  These are  usually stored  in a LDAP
  server and are  queried by the  BMI server itself.  However,
  you can also  pass opt-in data  for each recipient  from the
  MTA to the  BMI server. This  is particularly useful  if you
  already look  up recipient  data in  exim anyway  (which can
  also be  stored in  a SQL  database or  other source).  This
  implementation enables you  to pass opt-in  data to the  BMI
  server  in  the  RCPT   ACL.  This  works  by   setting  the
  'bmi_optin' modifier in  a block of  that ACL. If  should be
  set to a list  of comma-separated strings that  identify the
  features which the BMI server should use for that particular
  recipient. Ideally, you  would use the  'bmi_optin' modifier
  in the same  ACL block where  you set the  'bmi_run' control
  flag. Here is an example that will pull opt-in data for each
  recipient      from       a      flat       file      called
  '/etc/exim/bmi_optin_data'.
  
  The file format:
  
    user1@mydomain.com: <OPTIN STRING1>:<OPTIN STRING2>
    user2@thatdomain.com: <OPTIN STRING3>
    
    
  The example:
  
    accept  domains       = +relay_to_domains
            endpass
            verify        = recipient
            bmi_optin     = ${lookup{$local_part@$domain}lsearch{/etc/exim/bmi_optin_data}}
            control       = bmi_run  
  
  Of course,  you can  also use  any other  lookup method that
  exim supports, including LDAP, Postgres, MySQL, Oracle etc.,
  as long as  the result is  a list of  colon-separated opt-in
  strings.
  
  For a list of available opt-in strings, please contact  your
  Brightmail representative.
  
  

  
8. Sender Policy Framework (SPF) support
--------------------------------------------------------------

To learn  more  about  SPF, visit   http://spf.pobox.com. This
document does   not explain  the SPF  fundamentals, you should
read and understand the implications of deploying SPF on  your
system before doing so.

SPF support is added via the libspf2 library. Visit 

  http://www.libspf2.org/
  
to obtain  a copy,  then compile  and install  it. By default,
this will  put headers  in /usr/local/include  and the  static
library in /usr/local/lib.

To compile exim with SPF support, set these additional flags in
Local/Makefile:

CFLAGS=-DSPF -I/usr/local/include
EXTRALIBS_EXIM=-L/usr/local/lib -lspf2

This assumes   that the   libspf2 files   are installed  in
their default locations.

You can now run SPF checks in incoming SMTP by using the "spf"
ACL condition  in either  the MAIL,  RCPT or  DATA ACLs.  When
using it in the RCPT ACL, you can make the checks dependend on
the RCPT  address (or  domain), so  you can  check SPF records
only  for   certain  target   domains.  This   gives  you  the
possibility  to opt-out  certain customers  that do  not want
their mail to be subject to SPF checking.

The spf condition  takes a list  of strings on  its right-hand
side. These strings describe the outcome of the SPF check  for
which the spf condition should succeed. Valid strings are:

  o pass      The SPF check passed, the sending host
              is positively verified by SPF.
  o fail      The SPF check failed, the sending host
              is NOT allowed to send mail for the domain
              in the envelope-from address.
  o softfail  The SPF check failed, but the queried
              domain can't absolutely confirm that this
              is a forgery.
  o none      The queried domain does not publish SPF
              records.
  o neutral   The SPF check returned a "neutral" state.
              This means the queried domain has published
              a SPF record, but wants to allow outside
              servers to send mail under its domain as well.
  o err_perm  This indicates a syntax error in the SPF
              record of the queried domain. This should be
              treated like "none".
  o err_temp  This indicates a temporary error during all
              processing, including exim's SPF processing.
              You may defer messages when this occurs.
              
You can prefix each string with an exclamation mark to  invert
is meaning,  for example  "!fail" will  match all  results but
"fail".  The  string  list is  evaluated  left-to-right,  in a
short-circuit fashion.  When a  string matches  the outcome of
the SPF check, the condition  succeeds. If none of the  listed
strings matches the  outcome of the  SPF check, the  condition
fails.

Here is a simple example to fail forgery attempts from domains
that publish SPF records:

/* -----------------
deny message = $sender_host_address is not allowed to send mail from $sender_address_domain
     spf = fail
--------------------- */

You can also give special treatment to specific domains:

/* -----------------
deny message = AOL sender, but not from AOL-approved relay.
     sender_domains = aol.com
     spf = fail:neutral
--------------------- */

Explanation: AOL  publishes SPF  records, but  is liberal  and
still allows  non-approved relays  to send  mail from aol.com.
This will result in a "neutral" state, while mail from genuine
AOL servers  will result  in "pass".  The example  above takes
this into account and  treats "neutral" like "fail",  but only
for aol.com. Please note that this violates the SPF draft.

When the spf condition has run, it sets up several expansion
variables.

  $spf_header_comment
  This contains a human-readable string describing the outcome
  of the SPF check. You can add it to a custom header or use
  it for logging purposes.
  
  $spf_received
  This contains a complete SPF-Received: header that can be
  added to the message. Please note that according to the SPF
  draft, this header must be added at the top of the header
  list. Please see section 10 on how you can do this.
  
  $spf_result
  This contains the outcome of the SPF check in string form,
  one of pass, fail, softfail, none, neutral, err_perm or
  err_temp.
  
  $spf_smtp_comment
  This contains a string that can be used in a SMTP response
  to the calling party. Useful for "fail".
  
  

9. SRS (Sender Rewriting Scheme) Support
--------------------------------------------------------------

Exiscan  currently  includes SRS  support  via Miles  Wilton's
libsrs_alt  library.  His patch  to  link to  that  library is
included  in exiscan.  The current  version of  the supported
library is 0.5.

In order to  use SRS, you  must get a  copy of libsrs_alt from

http://srs.mirtol.com/

Unpack the tarball, then refer to MTAs/README.EXIM
to proceed. 

The SRS support is subject to change and should be  considered
experimental. SRS itself is still subject to changes.



10. Selecting header positions when adding headers in ACLs
--------------------------------------------------------------\

Exim introduced selectable  header positions in  Version 4.43.
One option allows you to  add your header after all  Received:
headers. For optimal SPF draft/RFC2822 compliance, exiscan-acl
adds one  other option,  called "spf-received".  It adds  your
header  before  the first  header  which is  NOT  Received: or
Resent-*:, seen from the top of the header stack.

Example:

warn message = :spf_received:$spf_received

--------------------------------------------------------------
End of file
--------------------------------------------------------------
