--------------------------------------------------------------
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.
 - 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)
- 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_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.
  

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.

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.
                    
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


  kavdaemon   Kapersky's kavdaemon  is a  daemon-type scanner.
              You    can    get    a    trial    version    at
              http://www.kapersky.com. 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
              
              
  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 = *
---------------------- */
     


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).

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.
  
  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.


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-Tracker: $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_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.
  
  
--------------------------------------------------------------
End of file
--------------------------------------------------------------
