Image of the glider from the Game of Life by John Conway
Skip to content

Verifying Hashcash Tokens With Mutt

Just five days ago, I blogged about minting Hashcash tokens in Mutt using a Python script (make sure you check that page for any updates to the source if you're using it). Well today, I finished writing my verification script. It takes some additional changes to your ~/.muttrc, which I'll outline here, and it requires the installation of a Python script. Of course, as previous, I'm assuming that you're running at least Python 2.5 and the latest version of Hashcash. With that, let's get busy.

First, the necessary changes to your "~/.muttrc" config. The script relies on the "X-Hashcash:" header (as well as the "Hashcash:" header- I guess there was some discrepancy or something about X-headers being deprecated, or something) not being weeded out. It must be displayed if present. This way, the script can actually see the token in the header, and process the logic of checking if it's valid. If you hid the hashcash headers, then the script won't execute, and as a result, won't add anything to the token database. We use the $display_filter variable to execute the Python script, and show the results to the pager. Here's the changes you will need to make:

# file: ~/.muttrc
ignore *                                    # draconian header weed - recommended
unignore from date subject to cc user-agent # standard headers unignored - recommended
unignore x-hashcash hashcash                # required

You will also need to set the $display_filter variable. I like having my theme consistent, so I've also added color to my theme to show the Hashcash verification at the top of the mail:

# file: ~/.muttrc
set display_filter="/path/to/verify_hashcash.py"    # required
color body brightyellow default "^\$$!--.*Hashcash*" # recommended

Now with that set, all we need to do is install the Python script, and we're ready to go. When looking over the code, you'll notice that it's creating a database of spent tokens. I've placed the database in ~/.mutt/, seeing as though I only have Mutt working with Hashcash at the moment (and it's really the only MUA I use these days). If you have something else that uses Hashcash tokens, in an already existing database, you may want to make the necessary modifications to the Python script, so it's pointing to the right file. Also, we're only interested in keeping track of tokens minted for us personally, not all tokens we can find in the headers. Lastly, this Python script is only working for one email address. If you have multiple emails, as I do, you'll have to either use a primary email to verify the tokens against, or modify the Python script to support checking tokens under multiple accounts.

With that said, here's the script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python
# Licensed under the public domain

import rfc822
import StringIO
import subprocess
import sys

# Change the DB path in COMMAND as needed, and change your email address
COMMAND="hashcash -cdb '%s' -r '%s' -f /home/user/.mutt/hashcash.db '%s'"
EMAILADDR="foo@bar.com"

tokens = []
token_status = []

# converting a list to a file-type object for parsing rfc822 headers
original = sys.stdin.read()
emailmsg = StringIO.StringIO(''.join(original))
message = rfc822.Message(emailmsg)

# check for the presence of "X-Hashcash" and "Hashcash" headers
if message.has_key("X-Hashcash"):
    for hc_list in message.getheaders("X-Hashcash"):
        tokens.append(hc_list)
if message.has_key("Hashcash"):
    for hc_list in message.getheaders("Hashcash"):
        tokens.append(hc_list)

# check each token
if tokens:
    token_status.append("[-- Begin Hashcash output --]")
    for hc_token in tokens:
        if hc_token.split(":")[3] == EMAILADDR:
            hc_bits = hc_token.split(":")[1]
            hc_resource = hc_token.split(":")[3]
            p = subprocess.Popen(COMMAND % (hc_bits,hc_resource,hc_token),
                shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            out = p.stderr.read().strip()
            token_status.append(out)
        else:
            token_status.append("No valid tokens for %s found." % EMAILADDR)
    token_status.append("[-- End Hashcash output --]")

print >> sys.stdout, ''.join(message.headers)
for status in token_status:
    print >> sys.stdout, ''.join(status)
if tokens:
    print ''
emailmsg.seek(message.startofbody)
print >> sys.stdout, ''.join(emailmsg.readlines())

One thing I do find odd about the code above, is the hashcash binary, when checking tokens, prints to STDERR rather than STDOUT. I'm guessing this could change in the future, so that's something that will need to be watched out for.

So far, the Python script has been working flawless for me. I haven't noticed a single hiccup, and it's fast, which it should be. However, standard disclaimers apply, such as not coming with any warranty, blah, blah, blah, and if you find any bugs, or have any enhancements (such as supporting multiple email addresses), I'm all ears. At any rate, I hope you find it helpful, should you wish to get into Hashcash with Mutt.

{ 3 } Comments

  1. Ian McEwen using Google Chrome 10.0.648.204 on GNU/Linux 64 bits | March 29, 2011 at 10:47 pm | Permalink

    Looks great to me -- one change I made (other than s/python/python2/ since I'm on Arch Linux!) was to change EMAILADDR into a tuple of strings, and line 33 to have 'in' rather than '=='. This makes it work when you have multiple emails, which is always nice! Even better, although I didn't want to figure it out, would be if it used the 'alternates' setting from .muttrc, where I already have a whole list of email addresses configured!

    One question, probably silly: why does it only work once? If I try to open the same email a second time it indicates the token is spent. I presume this is how it's supposed to work, but why?

    Thanks for a great couple of scripts! Perhaps I'll package them for Arch sometime later.

  2. Aaron using Google Chrome 10.0.648.204 on GNU/Linux 64 bits | March 29, 2011 at 10:58 pm | Permalink

    Ahh. Thanks for the updates on the tuple- I'll check that out. To answer your question about spent tokens, however, it's because you already have a copy of them in your hashcash.db. It is by design. The token was used and placed in the database, so there is no need to use it again. Just part of the protocol.

    Also, I'm being bugged about putting the scripts in a public Git repository. Thoughts?

  3. Ian McEwen using Google Chrome 10.0.648.204 on GNU/Linux 64 bits | March 30, 2011 at 1:08 am | Permalink

    May as well put them somewhere other than your blog, I suppose. And a VCS is the obvious choice for 'elsewhere'; makes patches/packaging/etc. easier.

    Having done a bit more reading the spent tokens thing does make more sense (it can't be cash if you can spend things more than once!), although I've been clearing out the database for testing purposes as I'm setting everything up. Looks to be set now, though.

    I've also successfully made my SpamAssassin on my mailserver understand Hashcash, and figure this is a good place to document what I needed to do: You have to tell it what at which emails you'll be receiving email or it doesn't check, even though the Hashcash plugin is enabled by default (which makes sense -- it doesn't want valid Hashcash signatures, it wants valid Hashcash signatures *for you*). This is done with the hashcash_accept option, which should be in one or the other of your SpamAssassion .cf files. It takes any number of email addresses, and makes available * and ? glob-style patterns, as well as %u for the current user.

    Hope that helps someone trying to use Hashcash; thanks again for the scripts!

Post a Comment

Your email is never published nor shared.

Switch to our mobile site