LOOKUP MAPS (hash, SQL) AND ACCESS LISTS EXPLAINED
==================================================
   Updated: 2002-04, 2002-06, 2002-11, 2002-12, 2003-03, 2003-05, 2003-06
   Mark Martinec <Mark.Martinec@ijs.si>

New with amavisd-new-20020630:    SQL LOOKUPS
New with amavisd-new-20021116:    Regular expression lookups
New with amavisd-new-20021227-p3: nonexistent SQL field now returns 0 or 1
                                  as declared by field type, or logs a warning

(applies to the semantics of amavisd.conf variables like:
   %virus_lovers,     %bypass_checks,
   @virus_lovers_acl, @bypass_checks_acl,
   $virus_lovers_re,  $bypass_checks_re,
   %local_domains, @local_domains_acl, %mailto, ... )

NOTE:
  All lookups are performed with raw (rfc2821-unquoted
  and unbracketed) addresses as a key,
      i.e.:    Bob "Funny" Dude@example.com
      not:     "Bob \"Funny\" Dude"@example.com
      and not: <"Bob \"Funny\" Dude"@example.com>


Several configurable settings in amavisd are controlled through
the use of table (hash) lookups, access control lists (array),
Perl-regexp -based access control lists, or SQL lookups.
The subroutine that does all the lookups is:

  sub lookup ($@) {
      my($addr, lists...) = @_;

It perform a lookup for a key (usually a recipient e-mail address,
unless otherwise noted) against one or more lookup tables / maps.
The set of maps is hard-wired into program, and the order chosen
is: from specific to more general, and from faster to slower,
which is usually flexible enough. Thus the sequence of lookups is:
SQL, LDAP, hash, ACL, regexp, constant. The first that returns
a definitive answer (not undef/NULL) stops the search.

Available data type of results from lookup maps is boolean or other
data type, as indicated by this list:
- M   <- a Perl hash table
- B   <- a Perl array (list), functioning as access control list (ACL)
- B/M <- a list of Perl regular expressions
- B/M <- a lookup into SQL database
- B/M <- a lookup into LDAP database
- M   <- a constant value (hardly worth calling a lookup at all,
         but useful as a final resort default value)
where a return value is:
  B indicates boolean value (true/false, possibly undef),
  M indicates multivalued result, e.g. some string, usable as boolean as well;

If a match is found, the subroutine lookup() returns whatever the map
returns;  undef is returned if nothing matches (which for Perl is false
as well).


A CONSTANT

Specifying a Perl scalar as an argument to lookup() is a degenerate
case of a lookup table: it matches any key, and the value of the
scalar is returned as the match value, unless it is undef (not defined).
If the value is undef, the search continues with the next argument
(if any (usually not), otherwise the final result is undef).

Specifying a scalar argument in a call to lookup() is useful as a
last-resort (catchall, default) value.


HASH LOOKUPS

For arguments to subroutine lookup() of type hash-ref, the argument
is passed to subroutine lookup_hash(), which does a lookup into
a Perl hash.

Hash lookups (e.g. for user+foo@sub.example.com) are performed in the
following order:
 - lookup for user+foo@sub.example.com
 - lookup for user@sub.example.com (only if $recipient_delimiter nonempty)
 - lookup for user+foo@
 - lookup for user@  (only if $recipient_delimiter is nonempty)
 - lookup for sub.example.com
 - lookup for .sub.example.com
 - lookup for .example.com
 - lookup for .com
 - lookup for .

The search sequence stops as soon as a match is found, and the value
of the matched entry determines the result.

The domain part is always matched case-insensitively, the localpart
is matched case-sensitively when $localpart_is_case_sensitive
is true (not case-sensitive by default).

The field value undef implies that the next lookup table (if there are more)
is to be tried. In plain words, undef means "this table does not know
the answer, try the next one". Further searching in this table
(for possibly more general defaults) is terminated.

NOTE: a null reverse path e-mail address used by MTA for delivery status
notifications (DSN) has empty local part and empty domain. As far as the
lookup is concerned (which uses raw, i.e. non-quoted and non-bracketed
address form), this address is @, i.e. a single character "@".
The lookup_hash for null address goes through the following sequence
of keys: "@", "", "." (double quotes added for clarity, they are not part
of the key).


ACL LOOKUPS

For arguments to subroutine lookup() of type array-ref, the argument
is passed to subroutine lookup_acl(), which does an access list lookup:

  sub lookup_acl($$) {
      my($addr, $acl_ref) = @_;

Domain name of the supplied address is compared with each member of the
access list in turn, the first match wins (terminates the search),
and its value decides whether the result is true (yes, permit, pass)
or false (no, deny, drop). Falling through without a match
produces undef. Search is case-insensitive.

Acl lookup is not aware of address extensions and they are not
handled specially.

If a list member contains a '@', the full e-mail address is compared,
otherwise if a list member has a leading dot, the domain name part is
matched only, and the domain as well as its subdomains can match. If there
is no leading dot, the domain must match exactly (subdomains do not match).

The presence of character '!' prepended to the list member decides
whether the result will be true (without a '!') or false (with '!')
in case this list member matches.

Because search stops at the first match, it only makes sense
to place more specific patterns before the more general ones.

Although not a special case, it is good to remember that '.' always matches,
so a '.' would stop the search and return true, whereas '!.' would stop
the search and return false (0).

Examples:

given: @acl = qw( me.ac.uk !.ac.uk .uk )
  'u@me.ac.uk' matches me.ac.uk, returns true and search stops

given: @acl = qw( me.ac.uk !.ac.uk .uk )
  'u@you.ac.uk' matches .ac.uk, returns false (because of '!'), search stops

given: @acl = qw( me.ac.uk !.ac.uk .uk )
  'u@them.co.uk' matches .uk, returns true and search stops

given: @acl = qw( me.ac.uk !.ac.uk .uk )
  'u@some.com' does not match anything, falls through and returns false (undef)

given: @acl = qw( me.ac.uk !.ac.uk .uk !. )
  'u@some.com' similar to the previous, except it returns 0 instead of undef,
  which would only make a difference if this ACL is not the last argument
  in a call to lookup()

given: @acl = qw( me.ac.uk !.ac.uk .uk . )
  'u@some.com' matches catchall ".", and returns true

more complex example: @acl = qw(
  !The.Boss@dept1.xxx.com .dept1.xxx.com
  .dept2.xxx.com .dept3.xxx.com lab.dept4.xxx.com
  sub.xxx.com !.sub.xxx.com
  me.d.aaa.com him.d.aaa.com !.d.aaa.com .aaa.com
);


Comparing hash and ACL:

For smaller sets of keys and if only boolean results are needed,
both hash and ACL are appropriate.

- hash is still effective for lots of keys, ACL search is linear;
- hash can be read from a file;
- hash can return any value, not just true or false;
- hash can strip away address extension, ACL does not;

- ACL is simpler and looks more obvious for smaller sets;
- ACL can accomodate arbitrarily nested if-then-elseif-then-...-else cases
  whereas hash only follows a fixed order of stripping away addresses;


ACL FOR IP ADDRESSES

A special type of lookup is an IP-matching access list implemented by
lookup_ip_acl(). It performs a lookup for an IP address against
access control list of IPv4 networks. It is used by amavisd to check
if the SMTP client (normally your MTA) is allowed to connect - the variable
is @inet_acl .

IP address is compared with each member of the access list in turn,
the first match wins (terminates the search), and its value decides
whether the result is true (yes, permit) or false (no, deny).
Falling through without a match produces false (undef).

The presence of character '!' prepended to the list member decides
whether the result will be true (without a '!') or false (with '!')
in case this list member matches and terminates the search.

Because search stops at the first match, it only makes sense
to place more specific patterns before the more general ones.

Network can be specified in classless notation n.n.n.n/k, or using
a mask n.n.n.n/m.m.m.m . Missing mask implies /32, i.e. a host address.

Although not a special case, it is good to remember that '0/0' always matches.

NOTE: IPv4 syntax is assumed, IPv6 is not currently supported.

Example
  given: @inet_acl = qw( !192.168.1.12 172.16.3.3 !172.16.3/255.255.255.0
                         10/8 172.16/12 192.168/16 );
matches rfc1918 private address space except host 192.168.1.12
and net 172.16.3/24 (but host 172.16.3.3 within 172.16.3/24 still matches)

More examples at amavisd.conf.


REGULAR EXPRESSION LOOKUPS

For arguments to subroutine lookup() of type Amavis::Lookup::RE
(objects), the object is passed to method lookup_re, which does a
lookup into a list of Perl regular expressions (regexp or RE for short).

The full unmodified key (e.g. an e-mail address) is always used,
so splitting to localpart and domain, lowercasing or handling address
extensions is NOT performed. This means the routine is general enough
to be useful for other RE tests, such as looking for banned file names.

Each element of the list can be a ref to a pair, or directly a regexp
('Regexp' object created by qr operator, or just a (less efficient)
string containing a regular expression). If it is a pair, the first
element is treated as a regexp, and the second provides a return value
in case the regexp matches. If not a pair, the implied result value
of a match is 1.

The pattern allows for capturing of parenthesized substrings, which can
then be referenced from the result string using the $1, $2, ... notation,
as with the Perl m// operator. The number after the $ may be a multi-digit
number. To avoid possible ambiguity the ${n} or $(n) form may be used.
Substring numbering starts with 1. Nonexistent references evaluate to empty
strings. If any substitution is done, the result inherits the taintedness
of the key. Keep in mind that the $ character needs to be backslash-quoted
in qq() strings. Example:
  $virus_quarantine_to = new_RE(
    [ qr'^(.*)@example\.com$'i => 'virus-${1}@example.com' ],
    [ qr'^(.*)(@[^@]*)?$'i     => 'virus-${1}${2}' ] );

The regular expression is taken as-is, no extra anchoring or setting
case insensitivity is done, so do use qr'(?i)^user@example\.com$',
and not a sloppy qr'user@example.com', which is doubly wrong in semantics
but correct in syntax, and can easily backfire. Also, if qr operator
is used with a delimiter other than ' , make sure to quote the @ and $ .

Example (equivalent to the example in lookup_acl):
   $acl_re = new_re->new(
                 qr'@me\.ac\.uk$'i,
                 [ qr'[@.]ac\.uk$'i => 0 ],
                 qr'\.uk$'i,
             );
   ($r,$k) = $acl_re->lookup_re('user@me.ac.uk');
or $r = lookup('user@me.ac.uk', $acl_re);

'user@me.ac.uk'   matches me.ac.uk, returns true and search stops
'user@you.ac.uk'  matches .ac.uk, returns false (because of =>0) and search stops
'user@them.co.uk' matches .uk, returns true and search stops
'user@some.com'   does not match anything, falls through and returns false (undef)

NOTE: new_re is a synonym (shorthand) for the
      internal subroutine Amavis::Lookup::RE::new

See Perl documentation (or Google the Internet) for the description
of Perl regular expressions. They are just enhanced version of Posix regular
expressions, i.e. what your egrep, awk and sed thrive on. Here are the
most important constructs (simplified):

    .   Match any character                 inter..t
    |   Alternation                         alfa|beta|gamma
    ()  Grouping                            (pre|post)fix
    []  Character class                     [Aa]lfa[0-9]
    ^   Match the beginning of the string   ^MakeMoney
    $   Match the end of the string         com$
    \   Quote the next metacharacter        \.com$
    most other characters just match themselves

quantifiers may be placed after the pattern to modify its meaning
from 'match itself exactly once' into:
    *      Match 0 or more times            ^alfa.*omega$
    +      Match 1 or more times            alfa +beta
    ?      Match 1 or 0 times               (first)?aid


SQL LOOKUPS

For arguments to subroutine lookup() of type Amavis::Lookup::SQLfield
(objects), the object is passed to method lookup_sql_field, which does
a lookup into a SQL table by using Perl module DBI.

The amavisd.conf variable @lookup_sql_dsn controls access to the SQL
server (dsn = data source name). If the list @lookup_sql_dsn is empty,
no attempts to use SQL will be made, and no code to use DBI will be
loaded or compiled.

@lookup_sql_dsn variable is a list of triples: (dsn,user,passw).
More than one triple may be specified to list multiple (backup)
SQL servers - the first that responds will be used.

@lookup_sql_dsn =
   ( ['DBI:mysql:mail:host1', 'some-username1', 'some-password1'],
     ['DBI:mysql:mail:host2', 'some-username2', 'some-password2'] );

With PostgreSQL the dsn (first element of the triple) may look like:
      'DBI:Pg:host=host1;dbname=mail'

See man page for the Perl module DBI, and corresponding DBD module
man pages (DBD::mysql, DBD::Pg, ...) for syntax of the first argument,


SQL 'select' requests all available fields from the specified
tables, and the result is cached (just for this mail message processing).
Individual fields can be extracted one at a time from this cache
very quickly, so there is no penalty in using several calls to lookup
for different fields (for the same key) in different parts of the program.

lookup_sql() performs a lookup for an e-mail address against a SQL map.
If a match is found it returns whatever the map returns (a reference
to a hash containing values of requested fields), otherwise returns undef.
A match aborts further fetching sequence.

lookup_sql_field() also performs a lookup for an e-mail address against
a SQL map. It first calls lookup_sql() if it hasn't been called yet for
this key, but instead of returning all available fields, it returns
just a value of one particular table field. This is the subroutine
that gets called from lookup() for arguments (objects) of type
Amavis::Lookup::SQLfield.

The field value NULL is translated to Perl undef, which according
to lookup rules implies that the next lookup table (if there are more)
is to be tried. In plain words, NULL means "this table does not know
the answer, try the next one". Further searching in this table
(e.g. for more general defaults) is terminated.

Boolean fields are usually declared as a single character (instead of
integer) to minimize storage. The characters N,n,F,f,0,NUL and SPACE
represent false (0), any other character represents true. Trailing blanks
are ignored. It is customary to use Y for true and N for false.

SQL lookups (e.g. for user+foo@example.com) are performed in order
which is normally requested by 'ORDER BY...DESC' in the SELECT statement;
otherwise the order is unspecified, which is only useful if just specific
entries exist in a database (e.g. full address always, not only domain part
or mailbox part).

The following order (implemented by sorting on the 'priority' field
in DESCending order, zero is low priority) is recommended, to follow
the same specific-to-general principle as in other lookup tables:

 - lookup for user+foo@example.com
 - lookup for user@example.com (only if $recipient_delimiter nonempty)
 - lookup for user+foo (only if domain part is local)
 - lookup for user     (only local; only if $recipient_delimiter is nonempty)
 - lookup for @example.com
 - lookup for @.       (catchall)

NOTE:
 this is different from hash and ACL lookups in three important aspects:
   - naked key (without '@') implies mailbox name, not domain name;
   - subdomains are not looked at, only full domain names are matched;
   - the naked mailbox name lookups ('user') are only performed
     when the address matches local_domains lookups.

The domain part is always lowercased when constructing a key,
the localpart is not lowercased when $localpart_is_case_sensitive is true.

Table names and field names are hard-wired in the routine
prepare_sql_queries(). Please adjust it to will. Field names should
be unique even without the table prefix. If they are not, the last one
in the SELECT field list prevails.

Below is an example database that can be used with MySQL or PostgreSQL
to test the code.

-- PostgreSQL notes (by Phil Regnauld):
--   - remove the 'unsigned' throughout,
--   - in tables 'users', 'mailaddr' and 'policy':
--     use SERIAL instead of INT...AUTO_INCREMENT when declaring id, e.g.:
--       id serial NOT NULL,
--   - create an amavis user and the database (choose name, e.g. mail)
--       $ creatuser -U pgsql --no-adduser --createdb amavis
--       $ createdb -U amavis mail
--   - populate the database using the schema below:
--       $ psql -U amavis mail < amavisd-pg.sql


-- local users
CREATE TABLE users (
  id         int unsigned DEFAULT '0' NOT NULL auto_increment,
  priority   int          DEFAULT '7' NOT NULL,  -- 0 is low priority
  policy_id  int unsigned DEFAULT '1' NOT NULL,
  email      varchar(255) NOT NULL,
  fullname   varchar(255) DEFAULT NULL,
  PRIMARY KEY (id),
  KEY email (email)
);
CREATE UNIQUE INDEX users_idx_email ON users(email);

-- any e-mail address, external or local, used as senders in wblist
CREATE TABLE mailaddr (
  id         int unsigned DEFAULT '0' NOT NULL auto_increment,
  priority   int          DEFAULT '7' NOT NULL,  -- 0 is low priority
  email      varchar(255) NOT NULL,
  PRIMARY KEY (id),
  KEY email (email)
);
CREATE UNIQUE INDEX mailaddr_idx_email ON mailaddr(email);

-- per-recipient whitelist and/or blacklist,
-- puts sender and recipient in relation wb  (white or blacklisted sender)
CREATE TABLE wblist (
  rid        int unsigned NOT NULL,     -- recipient: users.id
  sid        int unsigned NOT NULL,     -- sender:    mailaddr.id
  wb         char(1) NOT NULL,              -- W or Y / B or N / space=neutral
  PRIMARY KEY (rid,sid)
);

CREATE TABLE policy (
  id              int unsigned DEFAULT '0' NOT NULL auto_increment,
  policy_name     varchar(32),

  virus_lover          char(1),     -- Y/N
  spam_lover           char(1),     -- Y/N  (optional field)
  banned_files_lover   char(1),     -- Y/N  (optional field)
  bad_header_lover     char(1),     -- Y/N  (optional field)

  bypass_virus_checks  char(1),     -- Y/N
  bypass_spam_checks   char(1),     -- Y/N
  bypass_banned_checks char(1),     -- Y/N  (optional field)
  bypass_header_checks char(1),     -- Y/N  (optional field)

  spam_modifies_subj   char(1),     -- Y/N  (optional field)
  spam_quarantine_to   varchar(64) DEFAULT NULL,   -- (optional field)
  spam_tag_level  float,  -- higher score inserts spam info headers
  spam_tag2_level float DEFAULT NULL,  -- higher score inserts
                          -- 'declared spam' info header fields
  spam_kill_level float,  -- higher score activates evasive actions, e.g.
                          -- reject/drop, quarantine, ...
                          --   (subject to final_spam_destiny setting)
  PRIMARY KEY (id)
);


INSERT INTO users VALUES ( 1, 9, 5, 'user1+foo@y.example.com','Name1 Surname1');
INSERT INTO users VALUES ( 2, 7, 5, 'user1@y.example.com', 'Name1 Surname1');
INSERT INTO users VALUES ( 3, 7, 2, 'user2@y.example.com', 'Name2 Surname2');
INSERT INTO users VALUES ( 4, 7, 7, 'user3@z.example.com', 'Name3 Surname3');
INSERT INTO users VALUES ( 5, 7, 7, 'user4@example.com',   'Name4 Surname4');
INSERT INTO users VALUES ( 6, 7, 1, 'user5@example.com',   'Name5 Surname5');
INSERT INTO users VALUES ( 7, 5, 0, '@sub1.example.com', NULL);
INSERT INTO users VALUES ( 8, 5, 7, '@sub2.example.com', NULL);
INSERT INTO users VALUES ( 9, 5, 5, '@example.com',      NULL);
INSERT INTO users VALUES (10, 3, 8, 'userA', 'NameA SurnameA anywhere');
INSERT INTO users VALUES (11, 3, 9, 'userB', 'NameB SurnameB');
INSERT INTO users VALUES (12, 3,10, 'userC', 'NameC SurnameC');
INSERT INTO users VALUES (13, 3,11, 'userD', 'NameD SurnameD');
INSERT INTO users VALUES (14, 3, 0, '@sub1.example.net',    NULL);
INSERT INTO users VALUES (15, 3, 7, '@sub2.example.net',    NULL);
INSERT INTO users VALUES (16, 3, 5, '@example.net',         NULL);
INSERT INTO users VALUES (17, 7, 5, 'u1@example.org', 'u1');
INSERT INTO users VALUES (18, 7, 6, 'u2@example.org', 'u2');
INSERT INTO users VALUES (19, 7, 3, 'u3@example.org', 'u3');

-- INSERT INTO users VALUES (20, 0, 5, '@.',              NULL);  -- catchall

INSERT INTO policy VALUES (1, 'Non-paying',    'N','N','N','N',  'Y','Y','Y','N',  'Y',NULL, 3.0,   7, 10);
INSERT INTO policy VALUES (2, 'Uncensored',    'Y','Y','Y','Y',  'N','N','N','N',  'Y',NULL, 3.0, 999, 999);
INSERT INTO policy VALUES (3, 'Wants all spam','N','Y','N','N',  'N','N','N','N',  'Y',NULL, 3.0, 999, 999);
INSERT INTO policy VALUES (4, 'Wants viruses', 'Y','N','Y','Y',  'N','N','N','N',  'Y',NULL, 3.0, 6.9, 6.9);
INSERT INTO policy VALUES (5, 'Normal',        'N','N','N','N',  'N','N','N','N',  'N',NULL, 3.0, 6.9, 6.9);
INSERT INTO policy VALUES (6, 'Trigger happy', 'N','N','N','N',  'N','N','N','N',  'N',NULL, 3.0,   5, 5);
INSERT INTO policy VALUES (7, 'Permissive',    'N','N','N','Y',  'N','N','N','N',  'N',NULL, 3.0,  10, 20);
INSERT INTO policy VALUES (8, '6.5/7.8',       'N','N','N','N',  'N','N','N','N',  'N',NULL, 3.0, 6.5, 7.8);
INSERT INTO policy VALUES (9, 'userB',         'N','N','N','Y',  'N','N','N','N',  'N',NULL, 3.0, 6.3, 6.3);
INSERT INTO policy VALUES (10,'userC',         'N','N','N','N',  'N','N','N','N',  'N',NULL, 3.0, 6.0, 6.0);
INSERT INTO policy VALUES (11,'userD',         'Y','N','Y','Y',  'N','N','N','N',  'N',NULL, 3.0,   7, 7);

-- sender envelope addresses needed for white/blacklisting
INSERT INTO mailaddr VALUES ( 1, 5, '@example.com');
INSERT INTO mailaddr VALUES ( 2, 9, 'owner-postfix-users@postfix.org');
INSERT INTO mailaddr VALUES ( 3, 9, 'amavis-user-admin@lists.sourceforge.net');
INSERT INTO mailaddr VALUES ( 4, 9, 'MakeMoney@example.com');
INSERT INTO mailaddr VALUES ( 5, 5, '@example.net');
INSERT INTO mailaddr VALUES ( 6, 9, 'spamassassin-talk-admin@lists.sourceforge.net');
INSERT INTO mailaddr VALUES ( 7, 9, 'spambayes-bounces@python.org');

-- whitelist for user 14, i.e. default for recipients in domain sub1.example.com
INSERT INTO wblist VALUES (14, 1, 'W');
INSERT INTO wblist VALUES (14, 3, 'W');

-- whitelist and blacklist for user 17, i.e. u1@example.org
INSERT INTO wblist VALUES (17, 2, 'W');
INSERT INTO wblist VALUES (17, 3, 'W');
INSERT INTO wblist VALUES (17, 6, 'W');
INSERT INTO wblist VALUES (17, 7, 'W');
INSERT INTO wblist VALUES (17, 5, 'B');
INSERT INTO wblist VALUES (17, 4, 'B');


-- $sql_select_policy setting in amavisd.conf tells amavisd
-- how to fetch per-recipient policy settings.
-- See comments there. Example:
--
-- SELECT * FROM users,policy
--   WHERE (users.policy_id=policy.id) AND (users.email IN (%k))
--   ORDER BY users.priority DESC;

-- $sql_select_white_black_listin amavisd.conf tells amavisd
-- how to check sender in per-recipient whitelist/blacklist.
-- See comments there. Example:
--
-- SELECT wb FROM wblist,mailaddr
--   WHERE (rid=%r) AND (sid=mailaddr.id) AND (mailaddr.email IN (%k))
--   ORDER BY mailaddr.priority DESC;


LDAP LOOKUPS

The decumentation in this section is a bit spartanic.
Here is the text from the RELEASE_NOTES and from the file 'amavisd'
pertaining to LDAP.

In the config file, you have to enable ldap first:

| $enable_ldap = 1;

Then you can define defaults for your ldap queries:

| $default_ldap = {
|   hostname => 'localhost', tls => 0,
|   base => 'ou=hosting,dc=example,dc=com', scope => 'sub',
|   query_filter => '(&(objectClass=amavisAccount)(mail=%m))'
| };

And then the lookups themselves:

| $virus_lovers_ldap        = {res_attr => 'amavisVirusLover'};
| $banned_files_lovers_ldap = {res_attr => 'amavisBannedFilesLover'};
| ...

With this method, you can define every parameter individually.
You could have a different ldap server for each lookup! Like that,
the configuration is closer to the one in postfix or courier.

The hashes are converted into lookups objects in the
Amavis::Lookup::LDAP class method.


Definitions of LDAP lookup queries.

hostname      : The hostname of the LDAP server we connect to.
                (Default = 'localhost')
port          : The port where LDAP sends queries. (Default = 389)
timeout       : Timeout (in sec) passed when connecting the remote
                server. (Default = 120)
tls           : Enable TLS/SSL if true. (Default = 0)
base          : The DN that is the base object entry relative to
                which the search is to be performed. (Default = undef)
scope         : Scope can be 'base', 'one' or 'sub'. (Default = 'sub')
query_filter  : The filter used to find the amavis account. The string
                must contain a '%m' token that will be replaced by the
                actual e-mail address.
                (Default = '(&(objectClass=amavisAccount)(mail=%m))')
res_attr      : (Default = undef)
res_filter    : (Default = %r)
bind_dn       : If binding is needed, this is where you specify the
                DN to bind as. (Default = undef)
bind_password : Binding password. (Default = undef)

$default_ldap = {
  hostname => 'ldap.example.com',
  tls => 1,
  base => 'dc=example,dc=com',
  scope => 'sub',
  query_filter => '(&(objectClass=amavisAccount)(mail=%m))'}
};

$virus_lovers_ldap        = {res_attr => 'amavisVirusLover'};
$banned_files_lovers_ldap = {res_attr => 'amavisBannedFilesLover'};
$bypass_virus_checks_ldap = {res_attr => 'amavisBypassVirusChecks'};
$bypass_spam_checks_ldap  = {res_attr => 'amavisBypassSpamChecks'};
$spam_tag_level_ldap      = {res_attr => 'amavisSpamTagLevel'};
$spam_kill_level_ldap     = {res_attr => 'amavisSpamKillLevel'};

$spam_whitelist_sender_ldap = {
  query_filter => '(&(objectClass=amavisAccount)(mail=%m)
                     (amavisWhitelistSender=%s))',
  res_filter => 'OK'};

$spam_blacklist_sender_ldap = {
  query_filter => '(&(objectClass=amavisAccount)(mail=%m)
                     (amavisBlacklistSender=%s))',
  res_filter => 'OK'};

$local_domains_ldap = {
  query_filter => '(&(objectClass=mailDomain)(dc=%m))
  res_filter => 'OK'};
