qpsmtpd Wiki


You are here: start » plugins » spam » hashcash


You are currently not logged in! Enter your authentication credentials below to log in. You need to have cookies enabled to log in.


You don't have an account yet? Just get one: Register

Forgotten your password? Get a new one: Set new password

Plug-in Summary

Plug-in name: hashcash
Info: This plugin will add hashcash headers to outgoing messages.
Author: tyskjohan
Email: Won't say
Compatibility: all
Download: here


Hashcash is a denial-of-service counter measure tool. Its main current use is to help hashcash users avoid losing email due to content based and blacklist based anti-spam systems.

A hashcash stamp constitutes a proof-of-work which takes a parameterizable amount of work to compute for the sender. The recipient can verify received hashcash stamps efficiently.

This plugin will add hashcash headers to outgoing messages. It's quite a crude hack at the moment.

# written by Johan Almqvist
sub hook_data_post {
  my ($self, $transaction) = @_;

my @hashcash_rcpt = map { $_->address } $transaction->recipients;

if (($#hashcash_rcpt == 0)  && ($self->connection->notes('authuser')))
# if exactly one recipient, and if there is an authenticated user
        my $hashcash_header =
                `/usr/local/bin/hashcash -Xmb26 $hashcash_rcpt[0]`;
        $self->log(LOGNOTICE, "generated hashcash: $hashcash_header");
        $transaction->header->add(undef, $hashcash_header);
else {
        $self->log(LOGNOTICE, "not generating hashcash for: ".$#hashcash_rcpt);
	return DECLINED;

Another version of this Module has been written in Dec.2007 by Marc Sebastian Pelzer. This version is using the CPAN Perl Module Digest::HashCash and calculates X-HashCash headers for all recipients of a outgoing mail.

=head1 NAME
HashCash Version 1.0 / Dec.2007 / Marc Sebastian Pelzer / http://search.cpan.org/~mpelzer/
HashCash adds a X-HashCash header for each recipient of an outgoing email. This header is supported
by SpamAssasin and other Spam-Filters. The generation of a HashCash is expensive - it takes some CPU
cycles to calculate the hash. Spam senders normally send millions of mails per day and it's not
possible (or at least quite expensive) for them to calculate this HashCash. On the other hand, for a
mailserver like yours, with normal daily business, it should be no problem to do the job for each
outgoing mail.
for more information about the HashCash mechanism
Copy this plug-in into your qpsmtpd's plugin directory and put a line like this into your config/plugins:
HashCash 10 5
This plug-in is being called with two (optional) parameters. The first one as a time in seconds that
the generation of a HashCah should take a maximum. The second parameter is a time in seconds that the
generation should take a minimum. So, for example if you specify "10 5" like in the example above, the
generation of one HashCah per recipient will take between 5 and 10 seconds. Please be aware, that it
can take a long time to create HashCah headers if you send an email to multiple recipients!
If you dont specify those two numbers, a default of "5 3" will be used.
Two Perl Modules are needed in order to run this plug-in:
Get them via "perl -MCPAN -e shell" if you dont have them.
This plug-in is being called for all emails - wether they're incoming mails or outgoing mails.
Normally your qpsmtpd is listening on an external interface, tcp port 25 and handles mails that
should be locally delivered and also mail that should be routed to the outside world through
your running MTA. A HashCash should only be added to mails that are being sent from one of your
realay-clients to the outside world! Otherwise we're goning to calulate the stuff also for all
incoming mails, which doesnt make too much sense.
At this time there is no standard flag or note in qpsmtpd that marks a mail as "incoming" or
"outgoing". So you need to do this by your-self, by adding a line like this:
$self->qp->connection->notes('is_outgoing_mail', 1);
to one of the other qpsmtpd modules, like "check_rcptto_exists" or "check_qmail_deliverable"
or any other module that checks if the remote peer is a valid RELAY client or not. Only if this
"note" is set, this hook will be activated when it's being called by qpsmtpd. So make sure,
that you set it somewhere where it makes sense.
Example "patch" for the "check_rcptto_exists" plug-in:
--- 8< ---------------------------------------------------------------------------------
  my @relayclients = $self->qp->config("relayclients");
  foreach $tmp (@relayclients) {
    if ($remote_host =~ m!$tmp! || $remote_ip =~ m!$tmp!) {
        $self->log(LOGNOTICE, "Ok - remote peer is a allowed RELAY client ($remote_host / $remote_ip)");
        $self->qp->connection->notes('is_outgoing_mail', 1);		# <========= add this line
        return DECLINED;
--- 8< ---------------------------------------------------------------------------------
$self->qp ->connection->relay_client()
is being check for a TRUE value. This is the case, when the user has been authenticated against
qpsmtpd via SMTP AUTH.
use Digest::Hashcash;
use Time::HiRes qw ( gettimeofday tv_interval );
sub register {
	my ($self, $qp, @args) = @_;
	unless ($args[0] && $args[1]) {
		$self->log(LOGWARN, "Called with no parameters. Using DEFAULT values instead.");
		$self->{_max} = 5;
		$self->{_min} = 3;
	} else {
		$self->log(LOGNOTICE, "Setting max/min values to $args[0]/$args[1]");
		$self->{_max} = $args[0];
		$self->{_min} = $args[1];
sub hook_data_post {
	my ($self, $transaction) = @_;
	my ($size, $cipher, $token, $recipient, @recipients, $start_time);
	if ($self->qp->connection->relay_client || $self->qp->connection->notes('is_outgoing_mail')) {
		$self->log(LOGNOTICE, "This is a outgoing mail - we create a HashCash for this mail!");
		Digest::Hashcash::estimate_time(1);		# work-around for bug in Digest::Hashcash
		$size = Digest::Hashcash::estimate_size($self->{_max}, $self->{_min});
		@recipients = map { $_->address } $transaction->recipients;
		foreach $recipient (@recipients) {
			$start_time = [gettimeofday];
			$cipher = new Digest::Hashcash(size => $size, uid => $recipient);
			$token = $cipher->hash();
			$self->log(LOGNOTICE, "Calculated a HashCash for recipient '$recipient' in " . tv_interval($start_time, [gettimeofday]) . " seconds.");
			# add one X-Hashcash header per recipient - as described in http://www.hashcash.org/faq/
			$transaction->header->add("X-Hashcash", $token);
	} else {
		$self->log(LOGDEBUG, "This mail is NOT marked as outgoing mail. Nothing to do for us.");
	return DECLINED;