[or-cvs] r15874: Initial checkin of gettor. This is a program designed to be (in tor/trunk/contrib: . gettor gettor/sample-emails)

ioerror at seul.org ioerror at seul.org
Sun Jul 13 17:13:35 UTC 2008


Author: ioerror
Date: 2008-07-13 13:13:34 -0400 (Sun, 13 Jul 2008)
New Revision: 15874

Added:
   tor/trunk/contrib/gettor/
   tor/trunk/contrib/gettor/gettor.py
   tor/trunk/contrib/gettor/gettor_blacklist.py
   tor/trunk/contrib/gettor/gettor_requests.py
   tor/trunk/contrib/gettor/gettor_responses.py
   tor/trunk/contrib/gettor/sample-emails/
   tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header-package-sample.eml
   tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header.eml
   tor/trunk/contrib/gettor/sample-emails/positive-DKIM-header.eml
Log:
Initial checkin of gettor. This is a program designed to be invoked in a .forward file. It will respond with specific payloads for a given request. It requires that all requests be signed with DKIM. It's not quite finished yet.


Added: tor/trunk/contrib/gettor/gettor.py
===================================================================
--- tor/trunk/contrib/gettor/gettor.py	                        (rev 0)
+++ tor/trunk/contrib/gettor/gettor.py	2008-07-13 17:13:34 UTC (rev 15874)
@@ -0,0 +1,141 @@
+#!/usr/bin/python2.5
+# -*- coding: utf-8 -*-
+"""
+
+ gettor.py by Jacob Appelbaum <jacob at appelbaum.net>
+ This program will hand out Tor via email to supported systems.
+ This program is Free Software released under the GPLv3.
+
+ It is intended to be used in a .forward file as part of a pipe like so:
+
+     cat <<'EOF'> .forward
+     |/usr/local/bin/gettor.py
+     EOF
+
+ You should have a dist/current/ mirror in a directory that gettor can read.
+ Such a mirror can be created like so:
+
+     cd /usr/local/
+     rsync -av rsync://rsync.torproject.org/tor/dist/current tor-dist-current/
+
+ You can keep it updated with a cronjob like so:
+
+     MirrorDir=/usr/local/tor-dist-current/
+     0 3 * * * rsync -a rsync://rsync.torproject.org/tor/dist/current/ $MirrorDir
+ 
+ You should ensure that for each file and signature pair you wish to 
+ distribute, you have created a zip file containing both.
+
+ While this program isn't written in a threaded manner per se, it is designed to function 
+ as if it will be called as a pipe many times at once. There is a slight 
+ desynchronization with blacklist entry checking and may result in false 
+ negatives. This isn't perfect but it is designed to be lightweight. It could 
+ be fixed easily with a shared locking system but this isn't implemented yet.
+
+"""
+
+__program__ = 'gettor.py'
+__version__ = '20080713.00'
+__url__ = 'https://tor-svn.freehaven.net/svn/tor/trunk/contrib/gettor/'
+__author__ = 'Jacob Appelbaum <jacob at appelbaum.net>'
+__copyright__ = 'Copyright (c) 2008, Jacob Appelbaum'
+__license__ = 'See LICENSE for licensing information'
+
+try:
+    from future import antigravity
+except ImportError:
+    antigravity = None
+
+import syslog
+import gettor_blacklist
+import gettor_requests
+import gettor_responses
+
+if __name__ == "__main__":
+
+    rawMessage = gettor_requests.getMessage()
+    parsedMessage = gettor_requests.parseMessage(rawMessage)
+
+    if not parsedMessage:
+        syslog.syslog("gettor: No parsed message. Dropping message.")
+        print "gettor: No parsed message. Dropping message."
+        exit(1)
+
+    signature = False
+    signature = gettor_requests.verifySignature(rawMessage)
+    print "Signature is : " + str(signature)
+    replyTo = False
+    srcEmail = "gettor at torproject.org"
+
+    # TODO XXX:
+    # Make the zip files and ensure they match packageList
+    # Make each zip file like so:
+    # zip -9 windows-bindle.z \
+    #   vidalia-bundle-0.2.0.29-rc-0.1.6.exe \
+    #   vidalia-bundle-0.2.0.29-rc-0.1.6.exe.asc
+    #
+    packageList = {
+        "windows-bundle": "/tmp/windows-bundle.z",
+        "macosx-bundle": "/tmp/macosx-bundle.z",
+        "linux-bundle": "/tmp/linux-bundle.z",
+        "source-bundle": "/tmp/source-bundle.z"
+        }
+
+    # XXX TODO: Ensure we have a proper replyTO or bail out (majorly malformed mail).
+    replyTo = gettor_requests.parseReply(parsedMessage)
+    
+    if not signature:
+        # Check to see if we've helped them to understand that they need DKIM in the past
+        previouslyHelped = gettor_blacklist.blackList(replyTo)
+    
+    if not replyTo:
+        syslog.syslog("No help dispatched. Invalid reply address for user.")
+        print "No help dispatched. Invalid reply address for user."
+        exit(1)
+
+    if not signature and previouslyHelped:
+        syslog.syslog("gettor: Unsigned messaged to gettor by blacklisted user dropped.")
+        print "No help dispatched. Unsigned and unhelped for blacklisted user."
+        exit(1)
+
+    if not signature and not previouslyHelped:
+        # Reply with some help and bail out
+        # Someday call blackList(replyTo)
+        message = """
+        You should try your request again with a provider that implements DKIM. Sorry.
+        """
+        gettor_responses.sendHelp(message, srcEmail, replyTo)
+        print "attempting to send email from: " + srcEmail + "The mail is sent to: " + replyTo
+        syslog.syslog("gettor: Unsigned messaged to gettor. We issued some help about using DKIM.")
+        print "gettor: Unsigned messaged to gettor. We issued some help about using DKIM."
+        exit(0)
+
+    if signature:
+        syslog.syslog("gettor: Signed messaged to gettor.")
+        print "gettor: Signed messaged to gettor."
+        
+        try:
+            print "gettor: Parsing now."
+            package = gettor_requests.parseRequest(parsedMessage, packageList)
+        except:
+            package = None
+
+        if package == "windows-bundle":
+            print "gettor: " + package + " selected."
+            syslog.syslog("gettor: " + package + " selected.")
+            message = "Here's your requested software as a zip file. Please \
+            verify the signature."
+            print "attempting to send email from: " +
+            srcEmail + "The mail is sent to: " + replyTo
+            gettor_responses.sendPackage(message, srcEmail, replyTo, packageList[package])  
+            exit(0)
+        else:
+            print "Package request is unknown: " + package 
+            message = " Your request was misunderstood. Please select one of the \
+            following packages: " + packageList.keys()
+
+            gettor_responses.sendHelp(message, srcEmail, replyTo)
+            print "attempting to send email from: " + srcEmail + "The mail is sent to: " + replyTo
+            syslog.syslog("gettor: Signed messaged to gettor. We issued some help about proper email formatting.")
+            print "gettor: Signed messaged to gettor. We issued some help about proper email formatting."
+            exit(0)


Property changes on: tor/trunk/contrib/gettor/gettor.py
___________________________________________________________________
Name: svn:executable
   + *

Added: tor/trunk/contrib/gettor/gettor_blacklist.py
===================================================================
--- tor/trunk/contrib/gettor/gettor_blacklist.py	                        (rev 0)
+++ tor/trunk/contrib/gettor/gettor_blacklist.py	2008-07-13 17:13:34 UTC (rev 15874)
@@ -0,0 +1,116 @@
+#!/usr/bin/python2.5
+"""
+This library implements all of the black listing features needed for gettor.
+"""
+
+import hashlib
+import os
+
+stateDir = "/tmp/gettor/"
+blStateDir = stateDir + "bl/"
+
+def blackList(address, createEntry=False):
+    """ 
+    Check to see if an address is on our blacklist. If it is - we'll return true.
+    If requested, we'll attempt to create a blacklist entry and return true if 
+    everything works out.
+    """
+    # XXX TODO: Eventually we may want to sort entries with netcom
+    # style /tmp/gettor/2-digits-of-hash/2-more-digits/rest-of-hash files
+
+    privateAddress = makeAddressPrivate(address)
+    blackListed = lookupBlackListEntry(privateAddress)
+
+    if blackListed:
+        return True
+    elif createEntry:
+        return createBlackListEntry(privateAddress)
+
+    return False
+
+def lookupBlackListEntry(privateAddress, stateDir="/tmp/gettor/bl/"):
+    """ Check to see if we have a blacklist entry for the given address. """
+    entry = stateDir + str(privateAddress)
+    try:
+        entry = os.stat(entry)
+    except OSError:
+        return False
+    return True
+
+def createBlackListEntry(privateAddress, stateDir="/tmp/gettor/bl/"):
+    """ Create a zero byte file that represents the address in our blacklist. """
+    entry = stateDir + str(privateAddress)
+    stat = None
+    try:
+        stat = os.stat(entry)
+    except OSError:
+        try:
+            fd = open(entry, 'w')
+            fd.close()
+            return True
+        except:
+            print "Entry not found. We were unable to create an entry."
+            return False
+    print "It appears that we already had an entry."
+    return False
+
+def removeBlackListEntry(privateAddress, stateDir="/tmp/gettor/bl/"):
+    """ Remove the zero byte file that represents an entry in our blacklist."""
+    entry = stateDir + str(privateAddress)
+    stat = None
+    try:
+        entry = os.unlink(entry)
+    except OSError:
+        return False
+    return True
+
+def makeAddressPrivate(address):
+    """ Creates a unique identifier for the user. """
+    hash = hashlib.sha1(address)
+    privateAddress = hash.hexdigest()
+    return privateAddress
+
+def prepBLStateDir(stateDir = "/tmp/gettor/bl/"):
+    print "Preparing the state directory for gettor."
+    stat = None
+    try:
+        stat = os.stat(stateDir)
+        print "We found a state directory"
+        return True
+    except OSError:
+        try:
+            os.mkdir(stateDir)
+            print "No state directory was found, we created one"
+            return True
+        except:
+            print "Unable to make a state directory"
+            return False
+
+def blackListtests(address):
+    """ This is a basic evaluation of our blacklist functionality """
+    prepBLStateDir()
+    privateAddress = makeAddressPrivate(address)
+    print "We have a private address of: "  + privateAddress
+    print "Testing creation of blacklist entry: "
+    blackListEntry = createBlackListEntry(privateAddress)
+    print blackListEntry
+    print "We're testing a lookup of a known positive blacklist entry: "
+    blackListEntry = lookupBlackListEntry(privateAddress)
+    print blackListEntry
+    print "We're testing removal of a known blacklist entry: "
+    blackListEntry = removeBlackListEntry(privateAddress)
+    print blackListEntry
+    print "We're testing a lookup of a known false blacklist entry: "
+    blackListEntry = lookupBlackListEntry(privateAddress)
+    print blackListEntry
+    print "Now we'll test the higher level blacklist functionality..."
+    print "This should not find an entry in the blacklist: "
+    print blackList(address)
+    print "This should add an entry to the blacklist: "
+    print blackList(address, True)
+    print "This should find the previous addition to the blacklist: "
+    print blackList(address)
+    print "Please ensure the tests match what you would expect for your environment."
+
+if __name__ == "__main__" :
+    blackListtests("requestingUser at example.com")


Property changes on: tor/trunk/contrib/gettor/gettor_blacklist.py
___________________________________________________________________
Name: svn:executable
   + *

Added: tor/trunk/contrib/gettor/gettor_requests.py
===================================================================
--- tor/trunk/contrib/gettor/gettor_requests.py	                        (rev 0)
+++ tor/trunk/contrib/gettor/gettor_requests.py	2008-07-13 17:13:34 UTC (rev 15874)
@@ -0,0 +1,86 @@
+#!/usr/bin/python2.5
+# -*- coding: utf-8 -*-
+"""
+This library implements all of the email parsing features needed for gettor.
+"""
+
+import sys
+import email
+import dkim
+import re
+
+def getMessage():
+    """ Read the message into a buffer and return it """
+    rawMessage = sys.stdin.read()
+    return rawMessage
+
+def verifySignature(rawMessage):
+    """ Attempt to verify the DKIM signature of a message and return a positive or negative status """
+    signature = False
+
+    # TODO XXX:
+    # This should catch DNS exceptions and fail to verify if we have a 
+    # dns timeout
+    if dkim.verify(rawMessage):
+        signature = True
+        return signature
+    else:
+        return signature
+
+def parseMessage(message):
+    """ parse an email message and return a parsed email object """
+    return email.message_from_string(message)
+
+def parseReply(parsedMessage):
+    """ Return an email address that we want to email """
+    # TODO XXX: 
+    # Scrub this data
+    address = parsedMessage["from"]
+    return address
+
+def parseRequest(parsedMessage, packages):
+    """ This parses the request and returns the first specific package name for
+    sending. If we do not understand the request, we return None as the package
+    name."""
+    # XXX TODO:
+    # Should we pick only the first line of the email body. Drop the rest?
+    # It may be too unfriendly to our users
+    for line in email.Iterators.body_line_iterator(parsedMessage):
+        for package in packages.keys():
+            print "Line is: " + line
+            print "Package is " + package
+            match = re.match(package, line)    
+            if match: 
+                return package
+    # If we get here, we didn't find a package we're currently serving
+    return None
+
+if __name__ == "__main__" :
+    """ Give us an email to understand what we think of it. """
+    packageList = { 
+        "windows-bundle": "/tmp/windows-bundle.z",
+        "macosx-bundle": "/tmp/macosx-bundle.z",
+        "linux-bundle": "/tmp/linux-bundle.z",
+        "source-bundle": "/tmp/source-bundle.z"
+        }
+
+    print "Fetching raw message."
+    rawMessage = getMessage()
+    # This doesn't work without DNS ( no wifi on board current airplane )
+    print "Verifying signature of message."
+    signature = verifySignature(rawMessage)
+    print "Parsing Message."
+    parsedMessage = parseMessage(rawMessage)
+    print "Parsing reply."
+    parsedReply = parseReply(parsedMessage)
+    print "Parsing package request."
+    package = parseRequest(parsedMessage, packageList)
+    if package == None:
+        package = "help"        
+    else:
+        package = packageList[package]
+
+    print "The signature status of the email is: " + str(signature)
+    print "The email requested the following reply address: " + parsedReply
+    print "It looks like the email requested the following package: " + package
+    print "We would select the following package file: " + package


Property changes on: tor/trunk/contrib/gettor/gettor_requests.py
___________________________________________________________________
Name: svn:executable
   + *

Added: tor/trunk/contrib/gettor/gettor_responses.py
===================================================================
--- tor/trunk/contrib/gettor/gettor_responses.py	                        (rev 0)
+++ tor/trunk/contrib/gettor/gettor_responses.py	2008-07-13 17:13:34 UTC (rev 15874)
@@ -0,0 +1,72 @@
+#!/usr/bin/python2.5
+# -*- coding: utf-8 -*-
+""" This library implements all of the email replying features needed for gettor. """
+
+import smtplib
+import MimeWriter
+import syslog
+import StringIO
+import base64
+
+def sendHelp(message, source, destination):
+    """ Send a helpful message to the user interacting with us """
+    help = constructMessage(message, source, destination)
+    try:
+        print "Attempting to send the following message: "
+        status = sendMessage(help, source, destination)
+    except:
+        print "Message sending failed."
+        status = False
+    return status
+
+def sendPackage(message, source, destination, filelist):
+    """ Send a message with an attachment to the user interacting with us """
+    package = constructMessage(message, source, destination, filelist)
+    try:
+        print "Attempting to send the following message: "
+        status = sendMessage(package, destination)
+    except:
+        print "Message sending failed."
+        status = False
+    return status
+
+def constructMessage(messageText, ourAddress, recipient, fileList=None, fileName="requested-files.z"):
+    """ Construct a multi-part mime message, including only the first part
+    with plaintext."""
+
+    message = StringIO.StringIO()
+    mime = MimeWriter.MimeWriter(message)
+    mime.addheader('MIME-Version', '1.0')
+    mime.addheader('Subject', 'Your request has been processed')
+    mime.addheader('To', recipient)
+    mime.addheader('From', ourAddress)
+    mime.startmultipartbody('mixed')
+
+    firstPart = mime.nextpart()
+    emailBody = firstPart.startbody('text/plain')
+    emailBody.write(messageText)
+
+    # Add a file if we have one
+    if fileList:
+        # XXX TODO: Iterate over each file eventually
+        filePart = mime.nextpart()
+        filePart.addheader('Content-Transfer-Encoding', 'base64')
+        emailBody = filePart.startbody('application/zip; name=%s' % fileName)
+        base64.encode(open(fileList, 'rb'), emailBody)
+
+    # Now end the mime messsage
+    mime.lastpart()
+    return message
+
+def sendMessage(message, dst, src="gettor at torproject.org", smtpserver="localhost:2700"):
+    try:
+        smtp = smtplib.SMTP(smtpserver)
+        smtp.sendmail(src, dst, message.getvalue())
+        smtp.quit()
+        status = True
+    except:
+        return False
+
+    return status
+
+


Property changes on: tor/trunk/contrib/gettor/gettor_responses.py
___________________________________________________________________
Name: svn:executable
   + *

Added: tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header-package-sample.eml
===================================================================
--- tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header-package-sample.eml	                        (rev 0)
+++ tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header-package-sample.eml	2008-07-13 17:13:34 UTC (rev 15874)
@@ -0,0 +1,39 @@
+X-Account-Key: account6
+X-UIDL: 1215752284.31537.faustus,S=1469
+X-Mozilla-Status: 0001
+X-Mozilla-Status2: 02000000
+Return-Path: <jacob at appelbaum.net>
+Delivered-To: jpopped at appelbaum.net
+Received: (qmail 31535 invoked by uid 89); 11 Jul 2008 04:58:04 -0000
+Delivered-To: appelbaum.net-jacob at appelbaum.net
+Received: (qmail 31531 invoked by uid 89); 11 Jul 2008 04:58:04 -0000
+Received: from unknown (HELO moria.seul.org) (128.31.0.34)
+  by 0 with (DHE-RSA-AES256-SHA encrypted) SMTP; 11 Jul 2008 04:58:04 -0000
+Received-SPF: none (0: domain at appelbaum.net does not designate permitted sender hosts)
+Received: by moria.seul.org (Postfix)
+	id F09581415CD3; Fri, 11 Jul 2008 00:55:45 -0400 (EDT)
+Delivered-To: gettor at seul.org
+Received: from mail.lostinthenoise.net (mail.lostinthenoise.net [64.142.98.226])
+	(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+	(No client certificate requested)
+	by moria.seul.org (Postfix) with ESMTP id C464A140F476
+	for <gettor at torproject.org>; Fri, 11 Jul 2008 00:55:45 -0400 (EDT)
+Received: (qmail 31524 invoked by uid 89); 11 Jul 2008 04:58:02 -0000
+Received: from unknown (HELO ?127.0.0.1?) (64.142.98.226)
+  by 0 with (DHE-RSA-AES256-SHA encrypted) SMTP; 11 Jul 2008 04:58:02 -0000
+Message-ID: <4876E7D0.5050201 at appelbaum.net>
+Date: Thu, 10 Jul 2008 21:55:44 -0700
+From: Jacob Appelbaum <jacob at appelbaum.net>
+User-Agent: Icedove 1.5.0.14eol (X11/20080509)
+MIME-Version: 1.0
+To:  gettor at torproject.org
+Subject: negative DKIM signature
+X-Enigmail-Version: 0.94.2.0
+OpenPGP: id=9D0FACE4;
+	url=http://www.appelbaum.net/gpg.asc
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+
+source-bundle
+
+

Added: tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header.eml
===================================================================
--- tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header.eml	                        (rev 0)
+++ tor/trunk/contrib/gettor/sample-emails/negative-DKIM-header.eml	2008-07-13 17:13:34 UTC (rev 15874)
@@ -0,0 +1,39 @@
+X-Account-Key: account6
+X-UIDL: 1215752284.31537.faustus,S=1469
+X-Mozilla-Status: 0001
+X-Mozilla-Status2: 02000000
+Return-Path: <jacob at appelbaum.net>
+Delivered-To: jpopped at appelbaum.net
+Received: (qmail 31535 invoked by uid 89); 11 Jul 2008 04:58:04 -0000
+Delivered-To: appelbaum.net-jacob at appelbaum.net
+Received: (qmail 31531 invoked by uid 89); 11 Jul 2008 04:58:04 -0000
+Received: from unknown (HELO moria.seul.org) (128.31.0.34)
+  by 0 with (DHE-RSA-AES256-SHA encrypted) SMTP; 11 Jul 2008 04:58:04 -0000
+Received-SPF: none (0: domain at appelbaum.net does not designate permitted sender hosts)
+Received: by moria.seul.org (Postfix)
+	id F09581415CD3; Fri, 11 Jul 2008 00:55:45 -0400 (EDT)
+Delivered-To: gettor at seul.org
+Received: from mail.lostinthenoise.net (mail.lostinthenoise.net [64.142.98.226])
+	(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+	(No client certificate requested)
+	by moria.seul.org (Postfix) with ESMTP id C464A140F476
+	for <gettor at torproject.org>; Fri, 11 Jul 2008 00:55:45 -0400 (EDT)
+Received: (qmail 31524 invoked by uid 89); 11 Jul 2008 04:58:02 -0000
+Received: from unknown (HELO ?127.0.0.1?) (64.142.98.226)
+  by 0 with (DHE-RSA-AES256-SHA encrypted) SMTP; 11 Jul 2008 04:58:02 -0000
+Message-ID: <4876E7D0.5050201 at appelbaum.net>
+Date: Thu, 10 Jul 2008 21:55:44 -0700
+From: Jacob Appelbaum <jacob at appelbaum.net>
+User-Agent: Icedove 1.5.0.14eol (X11/20080509)
+MIME-Version: 1.0
+To:  gettor at torproject.org
+Subject: negative DKIM signature
+X-Enigmail-Version: 0.94.2.0
+OpenPGP: id=9D0FACE4;
+	url=http://www.appelbaum.net/gpg.asc
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+
+testing
+
+

Added: tor/trunk/contrib/gettor/sample-emails/positive-DKIM-header.eml
===================================================================
--- tor/trunk/contrib/gettor/sample-emails/positive-DKIM-header.eml	                        (rev 0)
+++ tor/trunk/contrib/gettor/sample-emails/positive-DKIM-header.eml	2008-07-13 17:13:34 UTC (rev 15874)
@@ -0,0 +1,57 @@
+X-Account-Key: account6
+X-UIDL: 1214981061.25808.faustus,S=2285
+X-Mozilla-Status: 0001
+X-Mozilla-Status2: 02000000
+Return-Path: <ioerror at gmail.com>
+Delivered-To: jpopped at appelbaum.net
+Received: (qmail 25806 invoked by uid 89); 2 Jul 2008 06:44:21 -0000
+Delivered-To: appelbaum.net-jacob at appelbaum.net
+Received: (qmail 25804 invoked by uid 89); 2 Jul 2008 06:44:21 -0000
+Received: from unknown (HELO wa-out-1112.google.com) (209.85.146.180)
+  by 0 with SMTP; 2 Jul 2008 06:44:21 -0000
+Received-SPF: pass (0: SPF record at _spf.google.com designates 209.85.146.180 as permitted sender)
+Received: by wa-out-1112.google.com with SMTP id j40so170432wah.1
+        for <jacob at appelbaum.net>; Tue, 01 Jul 2008 23:42:01 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:received:message-id:date:from:to
+         :subject:mime-version:content-type;
+        bh=IvFqNkffeoST7vamh2ytuq/b7GpLhg2hStTrQq3I3rE=;
+        b=xQR0hE/J4AXpAqH1UDXTtDrU9Izc6WM8vtFudRBzldWYyRx3Vvfh2I2Opu8+O6wbAv
+         jlDi18anUMbZqlIGSgGOxvXW4CltpX/SFZm1aGL4AisQ1Bi5xEqlrc8OnX3sA2xKeM3g
+         KWsWm+GVSMI4XAqnY9FYAfPx4DmOAfkdMyWCU=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=message-id:date:from:to:subject:mime-version:content-type;
+        b=kyzDtGRDbiC5y4Bz/ylQjyHOChiOP2A6QDzybsVXc0C1hjHLImOQYR8gOxcRY+mRkN
+         1xpBaEF4UloZAxTb79khRRp4TWmjT1DagtLx2MFzIj/F6awtdE/9U3p4QyKr8S43tGcE
+         ET26BSfT5u9zrXblVVAP3JedMPZ8mlIGQxyDs=
+Received: by 10.115.90.1 with SMTP id s1mr6711509wal.51.1214980921268;
+        Tue, 01 Jul 2008 23:42:01 -0700 (PDT)
+Received: by 10.114.184.16 with HTTP; Tue, 1 Jul 2008 23:41:57 -0700 (PDT)
+Message-ID: <7fadd8130807012341n3b3af401mbdb4a29c80310bd3 at mail.gmail.com>
+Date: Tue, 1 Jul 2008 23:41:57 -0700
+From: "Jacob Applebaum" <ioerror at gmail.com>
+To: jacob at appelbaum.net
+Subject: Positive DKIM header
+MIME-Version: 1.0
+Content-Type: multipart/alternative; 
+	boundary="----=_Part_462_28562233.1214980917793"
+
+------=_Part_462_28562233.1214980917793
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+This email should have a positive DKIM header.
+
+------=_Part_462_28562233.1214980917793
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+This email should have a positive DKIM header.<br>
+
+------=_Part_462_28562233.1214980917793--
+
+



More information about the tor-commits mailing list