[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] r20886: {projects} Move more stuff around (in projects/gettor: . lib lib/gettor)



Author: kaner
Date: 2009-11-01 06:50:51 -0500 (Sun, 01 Nov 2009)
New Revision: 20886

Added:
   projects/gettor/MANIFEST.in
   projects/gettor/lib/gettor/
   projects/gettor/lib/gettor/__init__.py
   projects/gettor/lib/gettor/blacklist.py
   projects/gettor/lib/gettor/config.py
   projects/gettor/lib/gettor/constants.py
   projects/gettor/lib/gettor/gtlog.py
   projects/gettor/lib/gettor/opt.py
   projects/gettor/lib/gettor/packages.py
   projects/gettor/lib/gettor/requests.py
   projects/gettor/lib/gettor/responses.py
   projects/gettor/lib/gettor/utils.py
   projects/gettor/setup.cfg
Removed:
   projects/gettor/MANIFEST
   projects/gettor/gettor/
Modified:
   projects/gettor/setup.py
Log:
Move more stuff around


Deleted: projects/gettor/MANIFEST
===================================================================
--- projects/gettor/MANIFEST	2009-11-01 11:45:23 UTC (rev 20885)
+++ projects/gettor/MANIFEST	2009-11-01 11:50:51 UTC (rev 20886)
@@ -1,17 +0,0 @@
-LICENSE
-README
-TODO
-i18n/de/gettor_de.po
-i18n/en/gettor_en.po
-GetTor.py
-gettor/config.py
-gettor/packages.py
-gettor/responses.py
-gettor/opt.py
-gettor/requests.py
-gettor/__init__.py
-gettor/gtlog.py
-gettor/blacklist.py
-sample-emails/positive-DKIM-header.eml
-sample-emails/negative-DKIM-header.eml
-sample-emails/negative-DKIM-header-package-sample.eml

Copied: projects/gettor/MANIFEST.in (from rev 20885, projects/gettor/MANIFEST)
===================================================================
--- projects/gettor/MANIFEST.in	                        (rev 0)
+++ projects/gettor/MANIFEST.in	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1 @@
+include README TODO

Copied: projects/gettor/lib/gettor/__init__.py (from rev 20885, projects/gettor/gettor/__init__.py)
===================================================================
--- projects/gettor/lib/gettor/__init__.py	                        (rev 0)
+++ projects/gettor/lib/gettor/__init__.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1 @@
+# :)

Copied: projects/gettor/lib/gettor/blacklist.py (from rev 20885, projects/gettor/gettor/blacklist.py)
===================================================================
--- projects/gettor/lib/gettor/blacklist.py	                        (rev 0)
+++ projects/gettor/lib/gettor/blacklist.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,104 @@
+#!/usr/bin/python2.5
+"""This library implements all of the black listing features needed for gettor.
+Basically, it offers creation, removal and lookup of email addresses stored as
+SHA1 hashes in a dedicated directory on the filesystem.
+"""
+
+import hashlib
+import os
+import re
+import gettor.config
+import gettor.gtlog
+
+log = gettor.gtlog.getLogger()
+
+conf = gettor.config.Config()
+stateDir = conf.getStateDir()
+blStateDir = conf.getBlStateDir()
+
+# XXX
+def createDir(path):
+    try:
+        log.info("Creating directory %s.." % path)
+        os.makedirs(path)
+    except OSError, e:
+        log.error("Failed to create directory %s: %s" % (path, e))
+        return False
+    return True
+
+class BWList:
+    def __init__(self, listdir):
+        self.listDir = listdir
+        if not os.path.isdir(self.listDir):
+            if not createDir(self.listDir):
+                # XXX Change this to something more appropriate
+                raise IOError("Bad dir: %s" % self.listDir)
+
+    def lookupListEntry(self, address):
+        """Check to see if we have a list entry for the given address."""
+        if address is None:
+           log.error("Argument 'address' is None")
+           return False
+        emailonly = self.stripEmail(address)
+        entry = self.listDir + "/" + str(hashlib.sha1(emailonly).hexdigest())
+        try:
+            entry = os.stat(entry)
+        except OSError:
+            return False
+        return True
+
+    def createListEntry(self, address):
+        """ Create a black- or whitelist entry """
+        if address is None:
+           log.error("Argument 'address' is None")
+           return False
+        emailonly = self.stripEmail(address)
+        entry = self.listDir + "/" + str(hashlib.sha1(emailonly).hexdigest())
+        if self.lookupListEntry(address) == False:
+            try:
+                fd = open(entry, 'w')
+                fd.close
+                return True
+            except:
+                log.error("Creating list entry %s failed." % entry)
+                return False
+        else:
+            # List entry already exists
+            return False
+
+    def removeListEntry(self, address):
+        """ Remove an entry from the black- or whitelist """
+        if address is None:
+           log.error("Argument 'address' is None")
+           return False
+        emailonly = self.stripEmail(address)
+        entry = self.listDir + "/" + str(hashlib.sha1(emailonly).hexdigest())
+        if (self.lookupListEntry(address) == True):
+            try:
+                os.unlink(entry)
+            except OSError:
+                log.error("Could not unlink entry %s" % entry)
+                return False
+        else:
+            log.info("Requested removal of non-existing entry %s. Abord." 
+                    % entry)
+            return False
+
+    def removeAll(self):
+        print "Removing all entries from list!"
+        for root, dirs, files in os.walk(self.listDir):
+            for file in files:
+                try:
+                    rmfile = os.path.join(root, file)
+                    os.remove(rmfile)
+                except:
+                    log.error("Could not remove %s." % rmfile)
+                    return False
+        return True
+
+    def stripEmail(self, address):
+        '''Strip "Bart Foobar <bart@xxxxxxxxxx>" to "<bart@xxxxxxxxxx">'''
+        match = re.search('<.*?>', address)
+        if match is not None:
+            return match.group()
+        return address

Copied: projects/gettor/lib/gettor/config.py (from rev 20885, projects/gettor/gettor/config.py)
===================================================================
--- projects/gettor/lib/gettor/config.py	                        (rev 0)
+++ projects/gettor/lib/gettor/config.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,184 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+ gettor_config.py - Parse configuration file for gettor
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+
+ We grab configurable values from the users' gettor config file. If that file 
+ is not present, we will supply reasonable defaults. Config files are ini-style
+ formatted. We know this is ugly, but we prefer it to XML and also ConfigParser
+ seems handy. ;-)
+
+ A valid config file should look like this:
+    
+     [global]
+     stateDir = /var/lib/gettor/
+     blStateDir = /var/lib/gettor/bl/
+     distDir = /var/lib/gettor/pkg/
+     srcEmail = gettor@xxxxxxx
+     locale = en
+     logSubSystem = nothing
+     logFile = /dev/null
+     localeDir = /usr/share/locale
+     delayAlert = True
+
+ Note that you can set from none to any of these values in your config file.
+ Values you dont provide will be taken from the defaults in 'useConf'.
+
+ Here is what each of them is used for individually:
+
+ blStateDir:    Blacklisted (hashed) email addresses go here
+ wlStateDir:    Whitelisted (hashed) email addresses go here
+ distDir:       Sent-out Tor packages are found here
+ srcEmail:      The email containing the Tor package will use this as 'From:'
+ locale:        Choose your default mail and log locale
+ logFile:       If 'file' logging is chosen, log to this file
+ logSubSystem:  This has to be one of the following strings:
+                'nothing':  Nothing is logged anywhere (Recommended)
+                'stdout':   Log to stdout
+                'syslog':   Logmessages will be written to syslog
+                'file':     Logmessages will be written to a file (Not that 
+                            this needs the 'logFile' option in the config file
+                            also set to something useful
+ localeDir:     This is where the 'en/LC_MESSAGES/gettor.mo' or 
+                'whateverlang/LC_MESSAGES/gettor.mo' should go
+ delayAlert:    If set to True (the default), a message will be sent to any
+                user who has properly requested a package. The message confirms
+                that a package was selected and will be sent.
+
+ If no valid config file is provided to __init__, gettorConf will try to use
+ '~/.gettorrc' as default config file. If that fails, the default values from
+ useConf will be used.
+
+ Run this module from the commandline to have it print a useful default config
+ like so:
+
+     $ ./gettor_config.py > ~/.gettorrc
+
+'''
+
+import os
+import sys
+import ConfigParser
+
+__all__ = ["Config"]
+
+class Config:
+    '''
+    Initialize gettor with default values if one or more values are missing 
+    from the config file. This will return entirely default values if the 
+    configuration file is missing. Our default file location is ~/.gettorrc 
+    of $USER.
+    '''
+
+    def __init__(self, path = os.path.expanduser("~/.gettorrc")):
+        '''
+        Most of the work happens here. Parse config, merge with default values,
+        prepare outConf.
+        '''
+
+        self.configFile = os.path.expanduser(path)
+
+        #               Variable name   |  Default value           | Section
+        self.useConf = {"stateDir":     ("/var/lib/gettor/",        "global"),
+                        "blStateDir":   ("/var/lib/gettor/bl/",     "global"),
+                        "wlStateDir":   ("/var/lib/gettor/wl/",     "global"),
+                        "srcEmail":     ("gettor@xxxxxxxxxxxxxx",   "global"),
+                        "distDir":      ("/var/lib/gettor/dist/",   "global"),
+                        "packDir":      ("/var/lib/gettor/pkg/",    "global"),
+                        "locale":       ("en",                      "global"),
+                        "logSubSystem": ("nothing",                 "global"),
+                        "logFile":      ("/dev/null",               "global"),
+                        "localeDir":    ("/usr/share/locale",       "global"),
+                        "cmdPassFile":  ("/var/lib/gettor/cmdpass", "global"),
+                        "delayAlert":   (True,                    "global")}
+
+        # One ConfigParser instance to read the actual values from config
+        self.config = ConfigParser.ConfigParser()
+        # And another to provide a useable default config as output. This is
+        # only because the user may have strange stuff inside his config file.
+        # We're trying to be failsafe here
+        self.outConf = ConfigParser.ConfigParser()
+
+        try:
+            if os.access(self.configFile, os.R_OK):
+                self.config.read(self.configFile)
+        except:
+            pass
+
+        # Main parser loop:
+        # * Merge default values with those from the config file, if there are
+        #   any
+        # * Update values from config file into useConf
+        # * Ignore sections and values that are not found in useConf, but in
+        #   the config file (wtf?)
+        # * Prepare outConf
+        for dkey, (dval, sec) in self.useConf.items():
+            if not self.outConf.has_section(sec):
+                self.outConf.add_section(sec)
+            try:
+                for key, val in self.config.items(sec):
+                    # Unfortunatly, keys from the config are not case-sensitive
+                    if key.lower() == dkey.lower():
+                        self.useConf[dkey] = val, sec
+                        self.outConf.set(sec, dkey, val)
+                        break
+                else:
+                    # Add default value for dkey
+                    self.outConf.set(sec, dkey, dval)
+                    
+            except:
+                self.outConf.set(sec, dkey, dval)
+
+    def printConfiguration(self):
+        '''
+        Print out config file. This works even if there is none
+        '''
+        return self.outConf.write(sys.stdout)
+
+    # All getter routines live below
+    def getStateDir(self):
+        return self.useConf["stateDir"][0]
+
+    def getBlStateDir(self):
+        return self.useConf["blStateDir"][0]
+
+    def getWlStateDir(self):
+        return self.useConf["wlStateDir"][0]
+
+    def getSrcEmail(self):
+        return self.useConf["srcEmail"][0]
+
+    def getDistDir(self):
+        return self.useConf["distDir"][0]
+
+    def getPackDir(self):
+        return self.useConf["packDir"][0]
+
+    def getLocale(self):
+        return self.useConf["locale"][0]
+
+    def getLogSubSystem(self):
+        return self.useConf["logSubSystem"][0]
+
+    def getLogFile(self):
+        return self.useConf["logFile"][0]
+
+    def getLocaleDir(self):
+        return self.useConf["localeDir"][0]
+
+    def getCmdPassFile(self):
+        return self.useConf["cmdPassFile"][0]
+
+    def getDelayAlert(self):
+        return self.useConf["delayAlert"][0]
+
+if __name__ == "__main__" :
+    c = Config()
+    print "# This is a suitable default configuration. Tune to fit your needs."
+    c.printConfiguration()

Copied: projects/gettor/lib/gettor/constants.py (from rev 20885, projects/gettor/gettor/constants.py)
===================================================================
--- projects/gettor/lib/gettor/constants.py	                        (rev 0)
+++ projects/gettor/lib/gettor/constants.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,164 @@
+#!/usr/bin/python2.5
+# -*- coding: utf-8 -*-
+"""
+ constants.py
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+"""
+
+import gettext
+
+_ = gettext.gettext
+
+helpmsg = _("""
+    Hello! This is the "GetTor" robot.
+
+    Unfortunately, we won't answer you at this address. You should make
+    an account with GMAIL.COM or YAHOO.CN and send the mail from
+    one of those.
+
+    We only process requests from email services that support "DKIM",
+    which is an email feature that lets us verify that the address in the
+    "From" line is actually the one who sent the mail.
+
+    (We apologize if you didn't ask for this mail. Since your email is from
+    a service that doesn't use DKIM, we're sending a short explanation,
+    and then we'll ignore this email address for the next day or so.)
+
+    Please note that currently, we can't process HTML emails or base 64
+    mails. You will need to send plain text.
+
+    If you have any questions or it doesn't work, you can contact a
+    human at this support email address: tor-assistants@xxxxxxxxxxxxxx
+        """)
+
+packagehelpmsg = _("""
+    Hello, This is the "GetTor" robot.
+
+    I will mail you a Tor package, if you tell me which one you want.
+    Please select one of the following package names:
+
+        tor-browser-bundle
+        macosx-universal-bundle
+        panther-bundle
+        tor-im-browser-bundle
+        source-bundle
+
+    Please reply to this mail (to gettor@xxxxxxxxxxxxxx), and tell me
+    a single package name anywhere in the body of your email.
+
+    Please note that currently we can't process HTML emails or base64
+    emails. You will need to send plain text.
+
+    If you have any questions or it doesn't work, you can contact a
+    human at this support email address: tor-assistants@xxxxxxxxxxxxxx
+
+        """)
+
+packagemsg = _("""
+    Hello! This is the "GetTor" robot.
+
+    Here's your requested software as a zip file. Please unzip the
+    package and verify the signature.
+
+    Hint: If your computer has GnuPG installed, use the gpg
+    commandline tool as follows after unpacking the zip file:
+
+       gpg --verify <packagename>.asc <packagename>
+
+    The output should look somewhat like this:
+
+       gpg: Good signature from "Roger Dingledine <arma@xxxxxxx>"
+
+    If you're not familiar with commandline tools, try looking for
+    a graphical user interface for GnuPG on this website:
+
+       http://www.gnupg.org/related_software/frontends.html
+
+    If your Internet connection blocks access to the Tor network, you
+    may need a bridge relay. Bridge relays (or "bridges" for short)
+    are Tor relays that aren't listed in the main directory. Since there
+    is no complete public list of them, even if your ISP is filtering
+    connections to all the known Tor relays, they probably won't be able
+    to block all the bridges.
+
+    You can acquire a bridge by sending an email that contains "get bridges"
+    in the body of the email to the following email address:
+    bridges@xxxxxxxxxxxxxx
+
+    It is also possible to fetch bridges with a web browser at the following
+    url: https://bridges.torproject.org/
+
+    If you have any questions or it doesn't work, you can contact a
+    human at this support email address: tor-assistants@xxxxxxxxxxxxxx
+
+        """)
+
+splitpackagemsg = _("""
+    Hello! This is the "GetTor" robot.
+
+    Here's your requested software as a zip file. Please unzip the
+    package and verify the signature.
+
+    IMPORTANT NOTE:
+    Since this is part of a split-file request, you need to wait for 
+    all split files to be received by you before you can save them all
+    into the same directory and unpack them by double-clicking the 
+    first file. 
+
+    Packages might come out of order! Please make sure you received
+    all packages before you attempt to unpack them!
+
+    Hint: If your computer has GnuPG installed, use the gpg
+    commandline tool as follows after unpacking the zip file:
+
+       gpg --verify <packagename>.asc <packagename>
+
+    The output should look somewhat like this:
+
+       gpg: Good signature from "Roger Dingledine <arma@xxxxxxx>"
+
+    If you're not familiar with commandline tools, try looking for
+    a graphical user interface for GnuPG on this website:
+
+       http://www.gnupg.org/related_software/frontends.html
+
+    If your Internet connection blocks access to the Tor network, you
+    may need a bridge relay. Bridge relays (or "bridges" for short)
+    are Tor relays that aren't listed in the main directory. Since there
+    is no complete public list of them, even if your ISP is filtering
+    connections to all the known Tor relays, they probably won't be able
+    to block all the bridges.
+
+    You can acquire a bridge by sending an email that contains "get bridges"
+    in the body of the email to the following email address:
+    bridges@xxxxxxxxxxxxxx
+
+    It is also possible to fetch bridges with a web browser at the following
+    url: https://bridges.torproject.org/
+
+    If you have any questions or it doesn't work, you can contact a
+    human at this support email address: tor-assistants@xxxxxxxxxxxxxx
+
+        """)
+
+delayalertmsg = _("""
+    Hello, This is the "GetTor" robot.
+
+    Thank you for your request. It was successfully understood. Your request is
+    currently being processed. Your package should arrive within the next ten
+    minutes.
+
+    If it doesn't arrive, the package might be too big for your mail provider.
+    Try resending the mail from a gmail.com or yahoo.cn account. Also,
+    try asking for tor-browser-bundle rather than tor-im-browser-bundle,
+    since it's smaller.
+
+    If you have any questions or it doesn't work, you can contact a
+    human at this support email address: tor-assistants@xxxxxxxxxxxxxx
+
+            """)

Copied: projects/gettor/lib/gettor/gtlog.py (from rev 20885, projects/gettor/gettor/gtlog.py)
===================================================================
--- projects/gettor/lib/gettor/gtlog.py	                        (rev 0)
+++ projects/gettor/lib/gettor/gtlog.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,92 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+ gettor_log.py - gettor logging configuration
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+ gettor may log information, this is how we handle that logging requirement.
+ A user may log to 'syslog', a 'file', 'stdout' or 'nothing'.
+ The user can choose one of those four options in a configuration file.
+
+ Note that this module will silently fall back to 'nothing' if anything is
+ minconfigured. Might be harder to debug, but is safer for now.
+'''
+
+import os
+import sys
+from time import gmtime, strftime
+import ConfigParser
+import syslog
+import logging
+import gettor.config
+from logging import handlers
+
+__all__ = ["initalize", "getLogger", "getLogSubSystem"]
+
+# Leave this to INFO for now
+loglevel = logging.INFO
+format = '%(asctime)-15s (%(process)d) %(message)s'
+logger = None
+logSubSystem = None
+initialized = False
+
+def initialize():
+    global logger
+    global logSubSystem
+    global initialized
+    # Don't add handlers twice
+    if initialized == True:
+        return
+    conf = gettor.config.Config() 
+    logger = logging.getLogger('gettor')
+    logger.setLevel(loglevel)
+    logSubSystem = conf.getLogSubSystem()
+
+    if logSubSystem == "stdout":
+        handler = logging.StreamHandler()
+    elif logSubSystem == "file":
+        # Silently fail if things are misconfigured
+        logFile = conf.getLogFile()
+        try:
+            if os.access(os.path.dirname(logFile), os.W_OK):
+                handler = logging.FileHandler(logFile)
+            else:
+                logSubSystem = "nothing"
+        except:
+            logSubSystem = "nothing"
+    elif logSubSystem == "syslog":
+        handler = logging.handlers.SysLogHandler(address="/dev/log")
+    else:
+        # Failsafe fallback
+        logSubSystem = "nothing"
+
+    # If anything went wrong or the user doesn't want to log
+    if logSubSystem == "nothing":
+        handler = logging.FileHandler(os.devnull)
+
+    formatter = logging.Formatter(fmt=format)
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+    initialized = True
+
+def getLogSubSystem():
+    global logSubSystem
+    return logSubSystem
+
+def getLogger():
+    global logger
+    if logger is None:
+        initialize()
+    return logger
+
+if __name__ == "__main__" :
+    initialize()
+    print "This is the logging module. You probably do not want to call it by hand."
+    print "We'll send a test logging message now with the following subsystem: " + \
+    getLogSubSystem()
+    log = getLogger()
+    log.info("I'm a logger, logging!")

Copied: projects/gettor/lib/gettor/opt.py (from rev 20885, projects/gettor/gettor/opt.py)
===================================================================
--- projects/gettor/lib/gettor/opt.py	                        (rev 0)
+++ projects/gettor/lib/gettor/opt.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+ gettor_config.py: Command line parser for gettor
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+
+ This is the option parser module for gettor.
+'''
+
+import optparse
+
+__all__ = ["parseOpts"]
+
+def parseOpts():
+    cmdParser = optparse.OptionParser()
+    cmdParser.add_option("-c", "--config", dest="configfile",
+                        default="~/.gettorrc",
+                        help="set config file to FILE", metavar="FILE")
+    cmdParser.add_option("-m", "--use-mirror", dest="mirror",
+                        default="rsync.torproject.org",
+                        help="set Tor package mirror to MIRROR", 
+                        metavar="MIRROR")
+    cmdParser.add_option("-i", "--install-crontab", dest="installcron",
+                        action="store_true", default=False,
+                        help="install crontab to refresh packagelist")
+    cmdParser.add_option("-f", "--fetch-packages", dest="fetchpackages",
+                        action="store_true", default=False,
+                        help="fetch Tor packages from mirror")
+    cmdParser.add_option("-p", "--prep-packages", dest="preppackages",
+                        action="store_true", default=False,
+                        help="prepare packages (zip them)")
+    cmdParser.add_option("-t", "--run-tests", dest="runtests",
+                        action="store_true", default=False,
+                        help="run some tests")
+    cmdParser.add_option("-w", "--whitelist", dest="whitelist",
+                         default="",
+                         help="add an email address to the whitelist",
+                         metavar="WHITELIST")
+    cmdParser.add_option("-b", "--blacklist", dest="blacklist",
+                         default="",
+                         help="add an email address to the blacklist",
+                         metavar="BLACKLIST")
+    cmdParser.add_option("-l", "--lookup", dest="lookup",
+                         default="",
+                         help="check black/white list presence of address",
+                         metavar="CHECKADDRESS")
+    cmdParser.add_option("-x", "--clear-whitelist", dest="clearwl",
+                        action="store_true", default=False,
+                        help="clear all entrys in the whitelist")
+    cmdParser.add_option("-y", "--clear-blacklist", dest="clearbl",
+                        action="store_true", default=False,
+                        help="clear all entrys in the blacklist")
+    cmdParser.add_option("-r", "--install-translations", dest="insttrans",
+                        action="store_true", default=False,
+                        help="Compile and install translation files [check -d]")
+    cmdParser.add_option("-s", "--set-cmdpassword", dest="cmdpass",
+                        default="",
+                        help="Set the password for mail commands",
+                        metavar="CMDPASS")
+    cmdParser.add_option("-d", "--i18n-dir", dest="i18ndir",
+                        default="./i18n",
+                        help="Set your locale src dir to DIR [default = %default]",
+                        metavar="DIR")
+
+    return cmdParser.parse_args()
+
+if __name__ == "__main__":
+    print >> sys.stderr, "You shouldn't run this directly."

Copied: projects/gettor/lib/gettor/packages.py (from rev 20885, projects/gettor/gettor/packages.py)
===================================================================
--- projects/gettor/lib/gettor/packages.py	                        (rev 0)
+++ projects/gettor/lib/gettor/packages.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,171 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+ packages.py: Package related stuff
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+ This module handles all package related functionality
+'''
+
+import os
+import zipfile
+import subprocess
+import gettor.gtlog
+import gettor.config
+import re
+
+__all__ = ["Packages"]
+
+log = gettor.gtlog.getLogger()
+
+# XXX
+def createDir(path):
+    try:
+        log.info("Creating directory %s.." % path)
+        os.makedirs(path)
+    except OSError, e:
+        log.error("Failed to create directory %s: %s" % (path, e))
+        return False
+    return True
+
+class Packages:
+    #                "bundle name": ("single file regex", "split file regex")
+    packageRegex = { "windows-bundle": ("vidalia-bundle-.*.exe$", "vidalia-bundle-.*_split"),
+                     "panther-bundle": ("vidalia-bundle-.*-ppc.dmg$", "vidalia-bundle-.*-ppc_split"),
+                     "macosx-universal-bundle": ("vidalia-bundle-.*-universal.dmg$", "vidalia-bundle-.*-universal_split"),
+                     "source-bundle": ("tor-.*.tar.gz$", "Now to something completely different"),
+                     "tor-browser-bundle": ("tor-browser-.*_en-US.exe$", "tor-browser-.*_en-US_split"),
+                     "tor-im-browser-bundle": ("tor-im-browser-.*_en-US.exe$", "tor-im-browser-.*_en-US_split"),
+                     # Mike won't sign Torbutton; He doesn't get gettor support
+                     #"torbutton": "torbutton-current.xpi$",
+                   }
+
+    def __init__(self, config):
+        self.packageList = {}
+        self.distDir = config.getDistDir()
+        try:
+            entry = os.stat(self.distDir)
+        except OSError, e:
+            if not createDir(self.distDir):
+                log.error("Bad dist dir %s: %s" % (self.distDir, e))
+                raise IOError
+        self.packDir = config.getPackDir()
+        try:
+            entry = os.stat(self.packDir)
+        except OSError, e:
+            if not createDir(self.packDir):
+                log.error("Bad pack dir %s: %s" % (self.packDir, e))
+                raise IOError
+
+    def getPackageList(self):
+        # Build dict like 'name': 'name.z'
+        try:
+            for filename in os.listdir(self.packDir):
+                self.packageList[filename[:-2]] = self.packDir + "/" + filename
+        except OSError, (strerror):
+            log.error("Failed to build package list: %s" % strerror)
+            return None
+
+        # Check sanity
+        for key, val in self.packageList.items():
+            # Remove invalid packages
+            if not os.access(val, os.R_OK):
+                log.info("Warning: %s not accessable. Removing from list." \
+                            % val)
+                del self.packageList[key]
+        return self.packageList
+
+    def buildPackages(self):
+        for filename in os.listdir(self.distDir):
+            for (pack, (regex_single, regex_split)) in self.packageRegex.items():
+                # Splitfile hacks. XXX: Refactor
+                if re.compile(regex_split).match(filename):
+                    packSplitDir = None
+                    try:
+                        packSplitDir = self.packDir + "/" + pack + ".split"
+                        if not os.access(packSplitDir, os.R_OK):
+                            os.mkdir(packSplitDir)
+                    except OSError, e:
+                        log.error("Could not create dir %s: %s" \
+                                        % (packSplitDir, e))
+                    # Loop through split dir, look if every partXX.ZZZ has a 
+                    # matching signature, pack them together in a .z
+                    splitdir = self.distDir + "/" + filename
+                    for splitfile in os.listdir(splitdir):
+                        # Skip signature files
+                        if splitfile.endswith(".asc"):
+                            continue
+                        if re.compile(".*split.part.*").match(splitfile):
+                            ascfile = splitdir + "/" + splitfile + ".asc"
+                            file = splitdir + "/" + splitfile
+                            zipFileName = packSplitDir + "/" + splitfile + ".z"
+                            if os.access(ascfile, os.R_OK) and os.access(file, os.R_OK):
+                                print "ok: ", zipFileName
+                                zip = zipfile.ZipFile(zipFileName, "w")
+                                zip.write(splitdir + "/" + splitfile, os.path.basename(file))
+                                zip.write(ascfile, os.path.basename(ascfile))
+                                zip.close()
+                            else:
+                                log.error("Uhm, expected signature file for %s to be: %s" % (file, ascfile))
+                                return False
+                if re.compile(regex_single).match(filename):
+                    file = self.distDir + "/" + filename
+                    ascfile = file + ".asc"
+                    zipFileName  = self.packDir + "/" + pack + ".z"
+                    # If .asc file is there, build Zip file
+                    if os.access(ascfile, os.R_OK):
+                        zip = zipfile.ZipFile(zipFileName, "w")
+                        zip.write(file, os.path.basename(file))
+                        zip.write(ascfile, os.path.basename(ascfile))
+                        zip.close()
+                        self.packageList[pack] = zipFileName
+                        break
+        if len(self.packageList) > 0:
+            return True
+        else:
+            log.error("Failed to build packages")
+            return False
+
+    def syncWithMirror(self, mirror, silent):
+        rsync = ["rsync"]
+        rsync.append("-a")
+        # Don't download dotdirs
+        rsync.append("--exclude='.*'")
+        if not silent:
+            rsync.append("--progress")
+        rsync.append("rsync://%s/tor/dist/current/" % mirror)
+        rsync.append(self.distDir)
+        process = subprocess.Popen(rsync)
+        process.wait()
+        return process.returncode
+
+    def getCommandToStr(self, mirror, silent):
+        """This is useful for cronjob installations
+        """
+        rsync = ["rsync"]
+        rsync.append("-a")
+        # Don't download dotdirs
+        rsync.append("--exclude='.*'")
+        if not silent:
+            rsync.append("--progress")
+        rsync.append("rsync://%s/tor/dist/current/" % mirror)
+        rsync.append(self.distDir)
+        return ''.join(self.rsync)
+
+if __name__ == "__main__" :
+    c = gettor_config.Config()
+    p = gettorPackages("rsync.torproject.org", c)
+    print "Building packagelist.."
+    if p.syncwithMirror() != 0:
+        print "Failed."
+        exit(1)
+    if not p.buildPackageList():
+        print "Failed."
+        exit(1)
+    print "Done."
+    for (pack, file) in p.getPackageList().items():
+        print "Bundle is mapped to file: ", pack, file

Copied: projects/gettor/lib/gettor/requests.py (from rev 20885, projects/gettor/gettor/requests.py)
===================================================================
--- projects/gettor/lib/gettor/requests.py	                        (rev 0)
+++ projects/gettor/lib/gettor/requests.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,143 @@
+#!/usr/bin/python2.5
+# -*- coding: utf-8 -*-
+"""
+ gettor_config.py - Parse configuration file for gettor
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+ This library implements all of the email parsing features needed for gettor.
+"""
+
+import sys
+import email
+import dkim
+import re
+
+import gettor.gtlog
+import gettor.packages
+
+__all__ = ["requestMail"]
+
+log = gettor.gtlog.getLogger()
+
+class requestMail:
+
+    defaultLang = "en"
+    # XXX
+    supportedLangs = { "en": "English", 
+                       "de": "Deutsch" }
+
+    def __init__(self, config):
+        """ Read message from stdin, parse all the stuff we want to know
+        """
+        self.rawMessage = sys.stdin.read()
+        self.parsedMessage = email.message_from_string(self.rawMessage)
+        self.signature = False
+        self.config = config
+        # TODO XXX:
+        # This should catch DNS exceptions and fail to verify if we have a 
+        # dns timeout
+        # We also should catch totally malformed messages here
+        try:
+            if dkim.verify(self.rawMessage):
+                self.signature = True
+        except:
+            pass
+
+        # TODO XXX: 
+        # Scrub this data
+        self.replytoAddress = self.parsedMessage["from"]
+        assert self.replytoAddress is not None, "Replyto address is None"
+        # If no package name could be recognized, use 'None'
+        self.returnPackage = None
+        self.splitDelivery = False
+        self.commandaddress = None
+        self.replyLocale = "en"
+        packager = gettor.packages.Packages(config)
+        self.packages = packager.getPackageList()
+        assert len(self.packages) > 0, "Empty package list"
+
+    def parseMail(self):
+        # Parse line by line
+        for line in email.Iterators.body_line_iterator(self.parsedMessage):
+            # Remove quotes
+            if line.startswith(">"):
+                continue
+            # XXX This is a bit clumsy, but i cant think of a better way
+            # currently. A map also doesnt really help i think. -kaner
+            for package in self.packages.keys():
+                matchme = ".*" + package + ".*"
+                match = re.match(matchme, line)    
+                if match: 
+                    self.returnPackage = package
+                    log.info("User requested package %s" % self.returnPackage)
+                    break
+            # If we find 'split' somewhere in the mail, we assume that the user 
+            # wants a split delivery
+            match = re.match(".*split.*", line)
+            if match:
+                self.splitDelivery = True
+                log.info("User requested a split delivery")
+            # Default locale is english
+            match = re.match(".*[Ll]ang:\s+(.*)$", line)
+            if match:
+                self.replyLocale = match.group(1)
+                log.info("User requested locale %s" % self.replyLocale)
+            # Check if this is a command
+            match = re.match(".*[Cc]ommand:\s+(.*)$", line)
+            if match:
+                log.info("Command received from %s" % self.replytoAddress) 
+                cmd = match.group(1).split()
+                length = len(cmd)
+                assert length == 3, "Wrong command syntax"
+                auth = cmd[0]
+                # Package is parsed by the usual package parsing mechanism
+                package = cmd[1]
+                address = cmd[2]
+                verified = gettor.utils.verifyPassword(self.config, auth)
+                assert verified == True, \
+                        "Unauthorized attempt to command from: %s" \
+                        % self.replytoAddress
+                self.commandaddress = address
+
+        if self.returnPackage is None:
+            log.info("User didn't select any packages")
+        # Actually use a map here later XXX
+        for (key, lang) in self.supportedLangs.items():
+            if self.replyLocale == key:
+                break
+        else:
+            log.info("Requested language %s not supported. Falling back to %s" \
+                        % (self.replyLocale, self.defaultLang))
+            self.replyLocale = self.defaultLang
+
+        return (self.replytoAddress, self.replyLocale, self.returnPackage, \
+                self.splitDelivery, self.signature, self.commandaddress)
+
+    def getRawMessage(self):
+        return self.rawMessage
+
+    def hasVerifiedSignature(self):
+        return self.signature
+
+    def getParsedMessage(self):
+        return self.parsedMessage
+
+    def getReplyTo(self):
+        return self.replytoAddress
+
+    def getPackage(self):
+        return self.returnPackage
+
+    def getLocale(self):
+        return self.replyLocale
+
+    def getSplitDelivery(self):
+        return self.splitDelivery
+
+    def getAll(self):
+        return (self.replytoAddress, self.replyLocale, \
+                self.returnPackage, self.splitDelivery, self.signature)

Copied: projects/gettor/lib/gettor/responses.py (from rev 20885, projects/gettor/gettor/responses.py)
===================================================================
--- projects/gettor/lib/gettor/responses.py	                        (rev 0)
+++ projects/gettor/lib/gettor/responses.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,247 @@
+#!/usr/bin/python2.5
+# -*- coding: utf-8 -*-
+"""
+ gettor_config.py - Parse configuration file for gettor
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+ This library implements all of the email replying features needed for gettor. 
+"""
+
+import os
+import smtplib
+import MimeWriter
+import StringIO
+import base64
+import gettext
+import re
+
+import gettor.gtlog
+import gettor.blacklist
+import gettor.constants
+
+__all__ = ["Response"]
+
+log = gettor.gtlog.getLogger()
+
+class Response:
+
+    def __init__(self, config, replyto, lang, package, split, signature, caddr):
+        self.config = config
+        self.srcEmail = "GetTor <gettor@xxxxxxxxxxxxxx>"
+        self.replyTo = replyto
+        assert self.replyTo is not None, "Empty replyto address."
+        self.mailLang = lang
+        self.package = package
+        self.splitsend = split
+        self.signature = signature
+        self.cmdAddr = caddr
+        # If cmdAddr is set, we are forwarding mail rather than sending a 
+        # reply to someone
+        if self.cmdAddr is not None:
+            self.sendTo = self.cmdAddr
+        else:
+            self.sendTo = self.replyTo
+        self.whiteList = gettor.blacklist.BWList(config.getWlStateDir())
+        self.blackList = gettor.blacklist.BWList(config.getBlStateDir())
+        try:
+            trans = gettext.translation("gettor", config.getLocaleDir(), [lang])
+            trans.install()
+        except IOError:
+            log.error("Translation fail. Trying running with -r.")
+            raise
+
+    def sendReply(self):
+        """All routing decisions take place here."""
+        # Check we're happy with sending this user a package
+        if not self.signature and not self.cmdAddr \
+           and not self.whiteList.lookupListEntry(self.replyTo) \
+           and not re.compile(".*@yahoo.com.cn").match(self.replyTo) \
+           and not re.compile(".*@yahoo.cn").match(self.replyTo) \
+           and not re.compile(".*@gmail.com").match(self.replyTo):
+            blackListed = self.blackList.lookupListEntry(self.replyTo)
+            if blackListed:
+                log.info("Unsigned messaged to gettor by blacklisted user dropped.")
+                return False
+            else:
+                # Reply with some help and bail out
+                self.blackList.createListEntry(self.replyTo)
+                log.info("Unsigned messaged to gettor. We will issue help.")
+                return self.sendHelp()
+        else:
+            if self.cmdAddr is not None:
+                success = self.sendPackage()
+                if not success:
+                    log.error("Failed to forward mail to '%s'" % self.cmdAddr)
+                return self.sendForwardReply(success)
+                
+            if self.package is None:
+                return self.sendPackageHelp()
+            delayAlert = self.config.getDelayAlert()
+            if delayAlert:
+                ret = self.sendDelayAlert()
+                if ret != True:
+                    log.error("Failed to sent delay alert.")
+            if self.splitsend:
+                return self.sendSplitPackage()
+            else:
+                return self.sendPackage()
+
+    def sendPackage(self):
+        """ Send a message with an attachment to the user"""
+        log.info("Sending out %s to %s." % (self.package, self.sendTo))
+        packages = gettor.packages.Packages(self.config)
+        packageList = packages.getPackageList()
+        filename = packageList[self.package]
+        message = gettor.constants.packagemsg
+        package = self.constructMessage(message, "", filename)
+        try:
+            status = self.sendMessage(package)
+        except:
+            log.error("Could not send package to user")
+            status = False
+
+        log.info("Send status: %s" % status)
+        return status
+
+    def sendSplitPackage(self):
+        """XXX XXX XXX alpha state XXX XXX XXX"""
+        splitdir = self.config.getPackDir() + "/" + self.package + ".split"
+        try:
+            entry = os.stat(splitdir)
+        except OSError, e:
+            log.error("Not a valid directory: %s" % splitdir)
+            return False
+        files = os.listdir(splitdir)
+        # Sort the files, so we can send 01 before 02 and so on..
+        files.sort()
+        nFiles = len(files)
+        num = 0
+        for filename in files:
+            fullPath = splitdir + "/" + filename
+            num = num + 1
+            subj = "[GetTor] Split package [%02d / %02d] " % (num, nFiles) 
+            message = gettor.constants.splitpackagemsg
+            package = self.constructMessage(message, subj, fullPath)
+            try:
+                status = self.sendMessage(package)
+            except:
+                log.error("Could not send package %s to user" % filename)
+                # XXX What now? Keep on sending? Bail out? Use might have 
+                # already received 10 out of 12 packages..
+                status = False
+
+        return status
+
+    def sendDelayAlert(self):
+        """ Send a delay notification """
+        log.info("Sending delay alert to %s" % self.sendTo)
+        return self.sendGenericMessage(gettor.constants.delayalertmsg)
+            
+    def sendHelp(self):
+        """ Send a helpful message to the user interacting with us """
+        log.info("Sending out help message to %s" % self.sendTo)
+        return self.sendGenericMessage(gettor.constants.helpmsg)
+
+## XXX the following line was used below to automatically list the names
+## of available packages. But they were being named in an arbitrary
+## order, which caused people to always pick the first one. I listed
+## tor-browser-bundle first since that's the one they should want.
+## Somebody should figure out how to automate yet sort. -RD
+##    """ + "".join([ "\t%s\n" % key for key in packageList.keys()]) + """
+
+    def sendPackageHelp(self):
+        """ Send a helpful message to the user interacting with us """
+        log.info("Sending package help to %s" % self.sendTo)
+        return self.sendGenericMessage(gettor.constants.packagehelpmsg)
+
+    def sendForwardReply(self, status):
+        " Send a message to the user that issued the forward command """
+        log.info("Sending reply to forwarder '%s'" % self.replyTo)
+        message = "Forwarding mail to '%s' status: %s" % (self.sendTo, status)
+        # Magic: We're now returning to the original issuer
+        self.sendTo = self.replyTo
+        return self.sendGenericMessage(message)
+
+    def sendGenericMessage(self, text):
+        """ Send a message of some sort """
+        message = self.constructMessage(text, "")
+        try:
+            status = self.sendMessage(message)
+        except:
+            log.error("Could not send message to user %s" % self.sendTo)
+            status = False
+
+        log.info("Send status: %s" % status)
+        return status
+
+    def constructMessage(self, messageText, subj, fileName=None):
+        """ Construct a multi-part mime message, including only the first part
+        with plaintext."""
+
+        if subj == "":
+            subj =_('[GetTor] Your request')
+        message = StringIO.StringIO()
+        mime = MimeWriter.MimeWriter(message)
+        mime.addheader('MIME-Version', '1.0')
+        mime.addheader('Subject', subj)
+        mime.addheader('To', self.sendTo)
+        mime.addheader('From', self.srcEmail)
+        mime.startmultipartbody('mixed')
+
+        firstPart = mime.nextpart()
+        emailBody = firstPart.startbody('text/plain')
+        emailBody.write(messageText)
+
+        # Add a file if we have one
+        if fileName:
+            filePart = mime.nextpart()
+            filePart.addheader('Content-Transfer-Encoding', 'base64')
+            emailBody = filePart.startbody('application/zip; name=%s' % os.path.basename(fileName))
+            base64.encode(open(fileName, 'rb'), emailBody)
+
+        # Now end the mime messsage
+        mime.lastpart()
+        return message
+
+    def sendMessage(self, message, smtpserver="localhost:25"):
+        try:
+            smtp = smtplib.SMTP(smtpserver)
+            smtp.sendmail(self.srcEmail, self.sendTo, message.getvalue())
+            smtp.quit()
+            status = True
+        except smtplib.SMTPAuthenticationError:
+            log.error("SMTP authentication error")
+            return False
+        except smtplib.SMTPHeloError:
+            log.error("SMTP HELO error")
+            return False
+        except smtplib.SMTPConnectError:
+            log.error("SMTP connection error")
+            return False
+        except smtplib.SMTPDataError:
+            log.error("SMTP data error")
+            return False
+        except smtplib.SMTPRecipientsRefused:
+            log.error("SMTP refused to send to recipients")
+            return False
+        except smtplib.SMTPSenderRefused:
+            log.error("SMTP sender address refused")
+            return False
+        except smtplib.SMTPResponseException:
+            log.error("SMTP response exception received")
+            return False
+        except smtplib.SMTPServerDisconnected:
+            log.error("SMTP server disconnect exception received")
+            return False
+        except smtplib.SMTPException:
+            log.error("General SMTP error caught")
+            return False
+        except:
+            log.error("Unknown SMTP error while trying to send via local MTA")
+            return False
+
+        return status

Copied: projects/gettor/lib/gettor/utils.py (from rev 20885, projects/gettor/gettor/utils.py)
===================================================================
--- projects/gettor/lib/gettor/utils.py	                        (rev 0)
+++ projects/gettor/lib/gettor/utils.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,280 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+ utils.py: Useful helper routines
+
+ Copyright (c) 2008, Jacob Appelbaum <jacob@xxxxxxxxxxxxx>, 
+                     Christian Fromme <kaner@xxxxxxxxxx>
+
+ This is Free Software. See LICENSE for license information.
+
+ This module handles all package related functionality
+'''
+
+import os
+import subprocess
+import hashlib
+
+import gettor.gtlog
+import gettor.blacklist
+import gettor.packages
+
+log = gettor.gtlog.getLogger()
+
+def createDir(path):
+    """Helper routine that creates a given directory"""
+    try:
+        log.info("Creating directory %s.." % path)
+        os.makedirs(path)
+    except OSError, e:
+        log.error("Failed to create directory %s: %s" % (path, e))
+        return False
+    return True
+
+def installTranslations(conf, localeSrcdir):
+    """Install all translation files to 'dir'"""
+    log.info("Installing translation files..")
+    hasDirs = None
+
+    if conf is None:
+        log.error("Bad arg.")
+        return False
+    if not os.path.isdir(localeSrcdir):
+        log.info("Not a directory: %s" % localeSrcdir)
+        if not createDir(localeSrcdir):
+            log.error("Giving up on %s" % localeSrcdir)
+            return False
+    localeDir = conf.getLocaleDir()
+    if not os.path.isdir(localeDir):
+        log.info("Not a directory: %s" % localeDir)
+        if not createDir(localeDir):
+            log.error("Giving up on %s" % localeDir)
+            return False
+
+    # XXX: Warn if there is no translation files anywhere..
+    for root, dirs, files in os.walk(localeSrcdir):
+        # Python lacks 'depth' feature for os.walk()
+        if root != localeSrcdir:
+            continue
+        for dir in dirs:
+            hasDirs = True
+            if dir.startswith("."):
+                continue
+            # We ignore the templates dir for now
+            if dir.startswith("templates"):
+                continue
+            try:
+                poFile = os.path.join(root, dir) + "/gettor.po"
+                # Construct target dir
+                targetDir = localeDir + "/" + dir + "/LC_MESSAGES"
+                if not os.path.isdir(targetDir):
+                    log.info("Not a directory: %s" % targetDir)
+                    if not createDir(targetDir):
+                        log.error("Giving up on %s" % targetDir)
+                        return False
+                if installMo(poFile, targetDir) == False:
+                    log.error("Installing .mo files failed.")
+                    return False
+            except Exception:
+                log.error("Error accessing translation files.")
+                return False
+    if hasDirs is None:
+        log.errpr("Empty locale dir: " % localeSrcdir)
+        return False
+
+    return True
+
+def fetchPackages(conf, mirror):
+    """Fetch Tor packages from a mirror"""
+    log.info("Fetching package files..")
+    try:
+        packs = gettor.packages.Packages(conf)
+    except IOError:
+        log.error("Error initiating package list.")
+        return False
+    if packs.syncWithMirror(mirror, False) != 0:
+        log.error("Syncing Tor packages failed.")
+        return False
+    else:
+        log.info("Syncing Tor packages done.")
+        return True
+
+def prepPackages(conf):
+    """Prepare the downloaded packages in a way so GetTor can work with them"""
+    log.info("Preparing package files..")
+    try:
+        packs = gettor.packages.Packages(conf)
+    except IOError:
+        log.error("Error initiating package list.")
+        return False
+    if not packs.buildPackages():
+        log.error("Building packages failed.")
+        return False
+    else:
+        log.info("Building packages done.")
+        return True
+
+def installCron():
+    log.info("Installing cronjob..")
+    # XXX: Check if cron is installed and understands our syntax?
+    currentCronTab = getCurrentCrontab()
+    path = os.getcwd() + "/" + os.path.basename(sys.argv[0])
+    args = " --clear-blacklist --fetch-packages --prep-packages"
+    newCronTab = currentCronTab + '\n' + '3 2 * * * ' + path + args
+    echoCmd = ['echo', newCronTab ]
+    cronCmd = ['crontab', '-']
+    echoProc = subprocess.Popen(echoCmd, stdout=subprocess.PIPE)
+    cronProc = subprocess.Popen(cronCmd, stdin=echoProc.stdout)
+    cronProc.communicate()[0]
+    return cronProc.returncode
+
+def addWhitelistEntry(conf, address):
+    log.info("Adding address to whitelist: %s" % address)
+    try:
+        whiteList = gettor.blacklist.BWList(conf.getWlStateDir())
+    except IOError, e:
+        log.error("Whitelist error: %s" % e)
+        return False
+    if not whiteList.createListEntry(address):
+        log.error("Creating whitelist entry failed.")
+        return False
+    else:
+        log.info("Creating whitelist entry ok.")
+        return True
+
+def addBlacklistEntry(conf, address):
+    log.info("Adding address to blacklist: %s" % address)
+    try:
+        blackList = gettor.blacklist.BWList(conf.getBlStateDir())
+    except IOError, e:
+        log.error("Blacklist error: %s" % e)
+        return False
+    if not blackList.createListEntry(address):
+        log.error("Creating blacklist entry failed.")
+        return False
+    else:
+        log.info("Creating whitelist entry ok.")
+        return True
+
+def lookupAddress(conf, address):
+    log.info("Lookup address: %s" % address)
+    found = False
+    try:
+        whiteList = gettor.blacklist.BWList(conf.getWlStateDir())
+        blackList = gettor.blacklist.BWList(conf.getBlStateDir())
+    except IOError, e:
+        log.error("White/Blacklist error: %s" % e)
+        return False
+    if whiteList.lookupListEntry(address):
+        log.info("Address '%s' is present in the whitelist." % address)
+        found = True
+    if blackList.lookupListEntry(address):
+        log.info("Address '%s' is present in the blacklist." % address)
+        found = True
+    if not found:
+        log.info("Address '%s' neither in blacklist or whitelist." % address)
+        found = True
+
+    # Always True
+    return found
+
+def clearWhitelist(conf):
+    try:
+        whiteList = gettor.blacklist.BWList(conf.getWlStateDir())
+    except IOError, e:
+        log.error("Whitelist error: %s" % e)
+        return False
+    log.info("Clearing whitelist..")
+    if not whiteList.removeAll():
+        log.error("Deleting whitelist failed.")
+        return False
+    else:
+        log.info("Deleting whitelist done.")
+        return True
+
+def clearBlacklist(conf):
+    log.info("Clearing blacklist..")
+    try:
+        blackList = gettor.blacklist.BWList(conf.getBlStateDir())
+    except IOError, e:
+        log.error("Blacklist error: %s" % e)
+        return False
+    if not blackList.removeAll():
+        log.error("Deleting blacklist failed.")
+        return False
+    else:
+        log.info("Deleting blacklist done.")
+        return True
+
+def setCmdPassword(conf, password):
+    log.info("Setting command password")
+    passwordHash = str(hashlib.sha1(password).hexdigest())
+    cmdPassFile = conf.getCmdPassFile()
+    try:
+        fd = open(cmdPassFile, 'w')
+        fd.write(passwordHash)
+        fd.close
+        return True
+    except Exception, e:
+        log.error("Creating list entry %s failed: %s" % (entry, e))
+        return False
+
+def verifyPassword(conf, password):
+    candidateHash = str(hashlib.sha1(password).hexdigest())
+    cmdPassFile = conf.getCmdPassFile()
+    try:
+        fd = open(cmdPassFile, 'r')
+        passwordHash = fd.read()
+        fd.close
+        if candidateHash == passwordHash:
+            log.info("Verification succeeded")
+            return True
+        else:
+            log.info("Verification failed")
+            return False
+    except Exception, e:
+        log.error("Verifying password failed: %s" % e)
+        return False
+
+# Helper routines go here ####################################################
+
+def installMo(poFile, targetDir):
+    global log
+    args = os.getcwd() + "/" + poFile + " -o " + targetDir + "/gettor.mo"
+    try:
+        ret = subprocess.call("msgfmt" + " " + args, shell=True)
+        if ret < 0:
+            log.error("Error in msgfmt execution: %s" % ret)
+            return False
+    except OSError, e:
+        log.error("Comilation failed: " % e)
+        return False
+    return True
+
+def installTrans(config, localeSrcdir):
+    global log
+    hasDirs = None
+
+    if config is None:
+        log.error("Bad arg.")
+        return False
+    if not os.path.isdir(localeSrcdir):
+        log.info("Not a directory: %s" % localeSrcdir)
+        if not createDir(localeSrcdir):
+            log.error("Giving up on %s" % localeSrcdir)
+            return False
+    localeDir = config.getLocaleDir()
+    if not os.path.isdir(localeDir):
+        log.info("Not a directory: %s" % localeDir)
+        if not createDir(localeDir):
+            log.error("Giving up on %s" % localeDir)
+            return False
+
+def getCurrentCrontab():
+    # This returns our current crontab
+    savedTab = "# This crontab has been tampered with by gettor.py\n"
+    currentTab = os.popen("crontab -l")
+    for line in currentTab:
+        savedTab += line
+    return savedTab
+

Added: projects/gettor/setup.cfg
===================================================================
--- projects/gettor/setup.cfg	                        (rev 0)
+++ projects/gettor/setup.cfg	2009-11-01 11:50:51 UTC (rev 20886)
@@ -0,0 +1,4 @@
+[install]
+install-purelib=~/opt/gettor
+install-scripts=~/opt/gettor
+install-data=~/opt/gettor

Modified: projects/gettor/setup.py
===================================================================
--- projects/gettor/setup.py	2009-11-01 11:45:23 UTC (rev 20885)
+++ projects/gettor/setup.py	2009-11-01 11:50:51 UTC (rev 20886)
@@ -2,6 +2,8 @@
 # (c) 2009 The Tor project
 # GetTor installer & packer
 
+import glob
+
 from distutils.core import setup
 
 setup(name='GetTor',
@@ -9,8 +11,10 @@
       description='GetTor enables users to obtain Tor via email',
       author='Jacob Appelbaum, Christian Fromme',
       author_email='jacob at appelbaum dot net, kaner at strace dot org',
-      url='https://www.torpeoject.org/gettor/',
-      package_dir={'': '.'},
+      url='https://www.torproject.org/gettor/',
+      package_dir={'': 'lib'},
       packages=['gettor'],
-      py_modules=['GetTor']
+      scripts = ["GetTor.py"],
+      py_modules=['GetTor'],
+      long_description = """Really long text here."""
      )