DNS-based White/Black/Grey-list

Greylist is a milter plugin written in Python that implements the greylist filtering method proposed by Evan Harris. It works by assuming that, spam engines will not retry sending their junk mail on a temporary error. The idea is to reject mail on a first attempt and then accept it after some time has elapsed. According to the SMTP specification, properly configured mail server should attempt to retry a delivery if it receives an temporary failure code.

Features

  • Milter plugin for Postfix and Sendmail capable to reduce the amount of spam incoming and reduce the load of mail server
  • Greylist is combined with whitelisting and blacklisting, so mail coming from certain IP addresses or subnets never goes through greylist / blacklist filtering process or is blacklisted without further inspection.
  • If a message is rejected, plugin will tell the remote server how long have to wait (after second attempt).
  • Is is possible to utilize external DNS-BL services and customize the message and error codes that will be send users
  • If more than one external DNS-BL service is used, load balancing (round-robin) is employed
  • Since it harnesses Unbound caching resolver, the amount of outgoing DNS packets is reduced as much as possible
  • As it is written in Python, it can be easily enhanced and customized

Installation and testing

  1. Download the sources (here) and extract by typing:

    > mkdir dnsbl
    > cd dnsbl
    > tar -xzf pygreylist-1.0.0.tgz
    > ls -l
    milter
    unbound-src
  1. Configure and compile unbound:

    > cd unbound-src
    > ./configure --with-pyunbound
    > make
    > cd ../milter
  2. Install sendmail and necessary packages:

    > sudo apt-get install sendmail libmilter-dev python-milter
  3. Run sendmail:

    > /etc/init.d/sendmail start

    Check /var/log/mail.log, the log-file should not contain any errors. If you find error “unable to qualify my own ...”, you have to edit /etc/hosts and add line like this:

    127.0.0.1 test.local. test
  4. Now, you have to setup milter plugin. Edit configuration file /etc/mail/sendmail.mc and add these lines:

    INPUT_MAIL_FILTER(`pythonfilter',`S=local:/var/run/pythonfilter.sock,F=, T=S:4m;R:4m')dnl
    define(`confINPUT_MAIL_FILTERS', `pythonfilter')dnl
  5. Update sendmail.cf and restart sendmail service:

    > make -C /etc/mail
    > /etc/init.d/sendmail reload
  6. Run milter-filter by typing:

    > make testenv
    > sudo LD_LIBRARY_PATH=./unbound python milter-filter.py -vvv

    Make will create test environment (copies unbound library and python libraries to unbound subdirectory).

  7. Open new shell and test whether the filtr works:

    > telnet 127.0.0.1 25
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    220 test.local. ESMTP Sendmail 8.14.3/8.14.3/Debian-4; Wed, 14 Jan 2009 20:44:46 +0100
    > HELO test
    250 test.local. Hello localhost [127.0.0.1], pleased to meet you
    > MAIL FROM: test@test.cz
    250 2.1.0 test@test.cz... Sender ok
    > RCPT TO: test@test.cz
    451 4.7.1 Greylisting in action, please come back later
    > RCPT TO: test@test.cz
    451 4.7.1 Greylisting still in action, please come back later (273 secs)
    > RCPT TO: test1@test.cz
    451 4.7.1 Greylisting in action, please come back later
    quit
    221 2.0.0 test.local. closing connection
    Connection closed by foreign host.

    Milter-filter should print debug messages, if not, check /var/log/mail.log for errors.

Configuration and parameters

Milter-filter can be configured by editing milter-filter.py.

Usage

Usage: milter-filter.py [options]

Options:
  -h, --help                        show this help message and exit
  -n FILTERNAME, --name=FILTERNAME  specifies milter filter name
  -s SOCKET, --socket=SOCKET        specifies milter socket
  -v, --verbose                     increase verbosity
  -a DNSBLS, --addbl=DNSBLS         add DNSBL service (e.g. -a dnsbl.sorbs.net)

Interface

plugin.set_plugin_option(name, value)

Set plugin option

Parameters:
  • name – name of option
  • value – new value

Available options:

filtername - (string) identification of milter plugin (default value is “pyfilter”),

socket - (string) socket path which uses mail server to communication with plugin (default value is “/var/run/pythonfilter.sock”)

debugfile - (string) path to file for unbound debug messages. None switch the debug messages off.

verb - (integer) verbosity. Possible values: VERB_ERRORS_ONLY - print errors only, VERB_ERRORS_WARNINGS - print errors and warnings, VERB_INFOS- print everything (suitable for debuging)

cachedb - (string) path to sqlite database which keeps greylist informations. Default value is “:memory:”, this means, the sqlite allocates DB in memory. If you will use a persistent file, the content of database will be reused after restart.

policy - (int) determines the DNSBL policy. Possible values: DNSBL_POLICY_ANY - mail is rejected if any of DNSBL services have listed sender IP. DNSBL_POLICY_ALL - mail rejected only when all the services have listed sender IP, DNSBL_POLICY_MAJORITY - mail is rejected if majority of servers have IP listed

reject - (bool) determines the behaviour in case the sender address is blacklisted and should be rejected. If this option is True (default value), connection is rejected. If False, the mail is marked with “X-RBL-Warning” flag only.

greytimeout - how long should be connection greylisted - in seconds (default value is 300, i.e. 5 minutes)

custom-wl - (string) path to file containing list of white listed IP addresses (one IP address per line, wildcards are allowed)

custom-bl - (string) path to file containing list of blacklisted listed IP addresses (one IP address per line, wildcards are allowed)

plugin.add_dnsbl(domain, msg='Rejected. Address {ADDR} found in {DNSBL}', err=('550', '5.7.1'))

Register new DNSBL service

Parameters:
  • domain – domain of DNSBL service (e.g. sbl-xbl.spamhaus.org)
  • msg – custom message which will be send to the client. Note that, {ADDR} will be replaced by client address and {DNSBL} will be replaced by domain
  • err – 2-tuple contains error code and extended error code
ubinit.unbount_init(pluginmodule)

Initialize unbound context and WL/BL custom zone

Returns:unbound context
plugin.run()
Initialize cache and run Milter daemon