Contents Contact

Hashcash milter

Introduction

This is a milter (mail filter) that can mint and verify Hashcash stamps for email messages passing through compatible MTAs. A Hashcash stamp proves that a certain difficult computation has been performed. Since putting such a stamp on every spam message would increase the cost of sending them dramatically, they may be used to indicate that a message is less likely to be spam. The stamps are described in more detail at the Hashcash website.

Project status

This is version 0.1.1 of Hashcash Milter.

Licensing

Hashcash Milter is free software, available under a BSD-style license. There is no warranty; not even for merchantability or fitness for a particular purpose. See the file LICENSE in the distribution for complete information.

Download

Source code:

hashcash-milter-0.1.1.tar.gz (27.19 kB, 2010-03-27)

Building and installing

This program requires the Sendmail Mail Filter API library (libmilter).

To build the Hashcash Milter run

    ./configure
    make

Optimization is important for speed of minting. GCC 4.2 seems to produce faster code here than later versions. Speed can be checked by running the test program

    make test
    ./test -p '' -f -a -i 192.0.2.0/24 -c 20 -m 24

To install the software, either copy the program hashcash-milter to an appropriate directory, or run

    make install

Next, configure the program to run at startup with appropriate command-line options. A Debian Linux init script is included with this distribution.

Finally, configure the MTA to use the milter and other programs to use its output. Some details on this are provided below.

Minting mode

The milter has two basic modes of operation. The first is minting mode, in which the milter will add Hashcash stamps to outgoing messages. Hashcash stamps are placed into X-Hashcash email headers which generally look like

    X-Hashcash: 1:25:100124:fox@forest.example::10ULm0awZLlz9Vbr:=CkW

The embedded email is the recipient, a distinct stamp must be attached for each one. The milter gets the list of recipients from To and CC headers. BCC headers and envelope recipients are ignored, because a stamp would reveal their existence.

There are two ways to specify that a message is outgoing:

  1. With the -a command-line flag, messages received after SMTP authentication are considered outgoing.

  2. With the -i command-line option, messages received from listed network addresses are considered outgoing. E.g.

        -i 127.0.0.1,192.0.2.1/24,2001:db8::1/32
    

    Messages received over a local-domain socket are treated as if received from 127.0.0.1.

Additionally, minting may be limited to particular sender domains with the -s command-line option. E.g.

    -s forest.example,valley.example

The value of minted stamps is specified in bits of partial preimage with the -m option. Without this option minting mode will be disabled. For each additional bit the minting time increases roughly twofold. The -r option may also be specified to reduce the number of bits for messages with multiple recipients. In that case, the number of bits is reduced by one for each doubling of the number of recipients until the given minimum is reached. E.g.

    -m 24 -r 20

Minting time may be limited with the -t command-line option. Even if this option is not given, the milter timeouts in the MTA may eventually cause it to accept or reject the message before minting is complete, so specifying the option is recommended. E.g.

    -t 120  # two minutes

If the message already contains Hashcash stamps, the milter will not add new ones.

Verification mode

The second mode of operation is verification mode, in which the milter will check Hashcash stamps on incoming messages. It will add a header indicating the outcome of this check which will generally look like this:

    Authentication-Results: forest.example; x-hashcash=x-pass (25 bits)

The minimum value of stamps to accept is specified in bits of partial preimage with the -c option. Without this option verification mode will be disabled. E.g.

    -c 20

In order to get an “pass” result, each of the envelope recipients that is also listed in the To or CC headers must have a corresponding valid Hashcash stamp with at least the value specified in the -c option. If only some of these recipients have stamps, the result will be “partial”. If some of the stamps don't have sufficient value, are expired, or have been previously spent, the result will be “policy” and if some of the stamps are invalid, the result will be “fail”.

Stamps which have been verified on previous messages are considered spent, and can be stored in a file specified by the -d option to prevent their reuse. E.g.

    -d /var/spool/postfix/hashcash-milter/spent.db

Using with Postfix

Postfix has support for Sendmail milters. In a simple configuration with no other milters, the following options in postfix/main.cf should be sufficient:

    smtpd_milters         = unix:/hashcash-milter/hashcash-milter.sock
    non_smtpd_milters     = unix:/hashcash-milter/hashcash-milter.sock
    milter_default_action = accept

The last line specifies that in case the milter fails messages will be accepted. Since this milter is not critical, this is the preferred option. Otherwise, if the milter failed, mail would be temporarily rejected until it was restarted. The milter also adopts this approach internally, accepting a message without modification in case of any errors.

The socket paths are relative to the Postfix chroot directory. The milter itself will have a socket path specified relative to the root,

    -p local:/var/spool/postfix/hashcash-milter/hashcash-milter.sock<

or relative to its own chroot directory,

    -p local:/hashcash-milter.sock

Note that there are some differences in the syntax for sockets between the milter and Postfix. See the Postfix documentation on milters for details.

Using with SpamAssassin

SpamAssassin has its own plugin for verifying Hashcash stamps, but since it doesn't have access to the envelope recipients, and the message recipient headers (To and CC) are easy to forge, it must be manually configured to accept stamps for specific addresses. On the other hand, it can be configured to accept stamps for arbitrary addresses, including, for example, mailing list addresses.

This milter does have access to the envelope recipients, which are validated by the MTA, and can thus reliably determine which stamps are required. This means that it can work for directly addressed mail without specific configuration. However, since it works at the host level (rather than the user level) it must verify all stamps for all recipients on the host together, and it can't be configured to accept stamps for arbitrary addresses.

SpamAssassin can be configured to use the results of verification by this milter to score messages similarly its own plugin by relying on the Authentication-Results header. E.g., the following ruleset accomplishes this (after replacing “example.com” with the MTA hostname):

    use_hashcash 0  # disable own plugin
    header HASHCASH_20 Authentication-Results =~
        /^example\.com; x-hashcash=x-pass \(20 bits\)/
    header HASHCASH_21 Authentication-Results =~
        /^example\.com; x-hashcash=x-pass \(21 bits\)/
    header HASHCASH_22 Authentication-Results =~
        /^example\.com; x-hashcash=x-pass \(22 bits\)/
    header HASHCASH_23 Authentication-Results =~
        /^example\.com; x-hashcash=x-pass \(23 bits\)/
    header HASHCASH_24 Authentication-Results =~
        /^example\.com; x-hashcash=x-pass \(24 bits\)/
    header HASHCASH_25 Authentication-Results =~
        /^example\.com; x-hashcash=x-pass \(25 bits\)/
    header HASHCASH_HIGH Authentication-Results =~
        /^example\.com; x-hashcash=x-pass \((2[6-9]|[3-9][0-9]|[0-9]{3}) bits\)/
    header HASHCASH_2SPEND Authentication-Results =~
        /^example\.com; x-hashcash=x-fail \(already spent\)/

If it finds Hashcash stamps, the milter will insert the Authentication-Results header with a single method (“x-hashcash”), and it will always remove any other headers which specify this method on this host. However it is possible to forge similar-looking headers which will not be removed. For example, in the following header the parenthesized part is a comment, so it will be ignored and the header will be left in place.

    Authentication-Results: example.com(; x-hashcash=x-pass (160 bits)); none

Thus, the regular expressions matching the header should be fairly strict, like the ones above.

Updates and feedback

This library is hosted at http://althenia.net/hashcash. It is programmed by Andrey Zholos. Comments, bug reports and testing results are welcome.