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

[or-cvs] r16929: {projects} Major patch from kaner! Lots of changes to the configuration (projects/gettor)



Author: ioerror
Date: 2008-09-20 16:54:16 -0400 (Sat, 20 Sep 2008)
New Revision: 16929

Added:
   projects/gettor/gettor_opt.py
Modified:
   projects/gettor/gettor.py
   projects/gettor/gettor_blacklist.py
   projects/gettor/gettor_config.py
   projects/gettor/gettor_requests.py
Log:
Major patch from kaner! Lots of changes to the configuration system.


Modified: projects/gettor/gettor.py
===================================================================
--- projects/gettor/gettor.py	2008-09-19 19:22:43 UTC (rev 16928)
+++ projects/gettor/gettor.py	2008-09-20 20:54:16 UTC (rev 16929)
@@ -55,50 +55,35 @@
     antigravity = None
 
 import sys
-import getopt
+import os
 import gettext
 import gettor_blacklist
 import gettor_requests
 import gettor_responses
-from gettor_log import gettorLogger
-from gettor_config import gettorConf
+import gettor_log
+import gettor_config
+import gettor_opt
 
 
-def usage():
-    print "Usage: gettor.py [-c CONFIG|-h]"
-    print ""
+# Somewhat poor hack to get what we want: Use different languages for logging
+# and for reply mails
+def switchLocale(newlocale):
+    trans = gettext.translation("gettor", "/usr/share/locale", [newlocale])
+    trans.install()
 
 if __name__ == "__main__":
 
-    # Parse args
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], 'hc:', ['help', 'config='])
-    except getopt.GetoptError:
-        usage()
-        sys.exit(1)
- 
-    config = None
-    for c, optarg in opts:
-        if c in ("-h", "--help"):
-            usage()
-            sys.exit(0)
-        if c in ("-c", "--config"):
-            config = optarg
-
-    if config != None:
-        conf = gettorConf(config)
-    else:
-        conf = gettorConf()
-    log  = gettorLogger()
-    locale = conf.getLocale()
-    trans = gettext.translation("gettor", "/usr/share/locale", [locale]) 
-    trans.install()
+    options, arguments = gettor_opt.parseOpts()
+    conf = gettor_config.gettorConf(options.configfile)
+    log  = gettor_log.gettorLogger()
+    logLang = conf.getLocale()
+    switchLocale(logLang)
     rawMessage = gettor_requests.getMessage()
     parsedMessage = gettor_requests.parseMessage(rawMessage)
 
     if not parsedMessage:
         log.log(_("No parsed message. Dropping message."))
-        exit(0)
+        exit(1)
 
     signature = False
     signature = gettor_requests.verifySignature(rawMessage)
@@ -114,6 +99,10 @@
     #   vidalia-bundle-0.2.0.29-rc-0.1.6.exe.asc
     #
     distDir = conf.getDistDir()
+    if not os.path.isdir(distDir):
+        log.log(_("Sorry, %s is not a directory.") % distDir)
+        exit(1)
+
     packageList = {
         "windows-bundle": distDir + "windows-bundle.z",
         "macosx-panther-ppc-bundle": distDir + "macosx-panther-ppc-bundle.z",
@@ -121,24 +110,40 @@
         "source-bundle": distDir + "source-bundle.z"
         }
 
+    # Check package list sanity
+    for key, val in packageList.items():
+        # Remove invalid packages
+        if not os.access(val, os.R_OK):
+            log.log(_("Warning: %s not accessable. Removing from list." % val))
+            del packageList[key]
+    if len(packageList) < 1:
+        log.log(_("Sorry, your package list is unusable."))
+        exit(1)
+
     # XXX TODO: Ensure we have a proper replyTO or bail out (majorly malformed mail).
     replyTo = gettor_requests.parseReply(parsedMessage)
-    
+
+    # Get disired reply language, if any
+    replyLang = gettor_requests.parseLocale(parsedMessage)
+    if not replyLang:
+        replyLang = logLang
+
     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:
         log.log(_("No help dispatched. Invalid reply address for user."))
-        exit(0)
+        exit(1)
 
     if not signature and previouslyHelped:
         log.log(_("Unsigned messaged to gettor by blacklisted user dropped."))
-        exit(0)
+        exit(1)
 
     if not signature and not previouslyHelped:
         # Reply with some help and bail out
         gettor_blacklist.blackList(replyTo, True)
+        switchLocale(replyLang)
         message = _("""
 Hello! This is the "get tor" robot.
 
@@ -154,6 +159,7 @@
 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.
         """)
+        switchLocale(logLang)
         gettor_responses.sendHelp(message, srcEmail, replyTo)
         log.log(_("Unsigned messaged to gettor. We issued some help about using DKIM."))
         exit(0)
@@ -175,12 +181,14 @@
             gettor_responses.sendPackage(message, srcEmail, replyTo, packageList[package])  
             exit(0)
         else:
+            switchLocale(replyLang)
             message = [_("Hello, I'm a robot. ")]
             message.append(_("Your request was not understood. Please select one of the following package names:\n"))
 
             for key in packageList.keys():
                 message.append(key + "\n")
             message.append(_("Please send me another email. It only needs a single package name anywhere in the body of your email.\n"))
+            switchLocale(logLang)
             gettor_responses.sendHelp(''.join(message), srcEmail, replyTo)
             log.log(_("Signed messaged to gettor. We issued some help about proper email formatting."))
             exit(0)

Modified: projects/gettor/gettor_blacklist.py
===================================================================
--- projects/gettor/gettor_blacklist.py	2008-09-19 19:22:43 UTC (rev 16928)
+++ projects/gettor/gettor_blacklist.py	2008-09-20 20:54:16 UTC (rev 16929)
@@ -5,9 +5,9 @@
 
 import hashlib
 import os
-from gettor_config import gettorConf
+import gettor_config
 
-conf = gettorConf()
+conf = gettor_config.gettorConf()
 stateDir = conf.getStateDir()
 blStateDir = conf.getBlStateDir()
 

Modified: projects/gettor/gettor_config.py
===================================================================
--- projects/gettor/gettor_config.py	2008-09-19 19:22:43 UTC (rev 16928)
+++ projects/gettor/gettor_config.py	2008-09-20 20:54:16 UTC (rev 16929)
@@ -1,8 +1,56 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 '''
-This grabs configurable values from the users' gettor config file
-if that file is not present, it will supply reasonable defaults.
+ 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
+
+ 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
+ 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)
+                '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
+
+ 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
@@ -11,26 +59,37 @@
 
 class gettorConf:
     '''
-    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 for the current user.
+    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.
     '''
 
-    stateDir = "/var/lib/gettor/"
-    blStateDir = stateDir + "bl/"
-    srcEmail = "gettor@xxxxxxxxxxxxxx"
-    distDir = "/var/lib/gettor/pkg/"
-    locale = "en"
-    logSubSystem = "nothing"
-    logFile = "/dev/null"
-    configFile = "~/.gettorrc"
-    config = ConfigParser.ConfigParser()
+    def __init__(self, path = os.path.expanduser("~/.gettorrc")):
+        '''
+        Most of the work happens here. Parse config, merge with default values,
+        prepare outConf.
+        '''
 
-    def __init__(self, path = os.path.expanduser(configFile)):
-
         self.configFile = os.path.expanduser(path)
 
+        #               Variable name   |  Default value           | Section
+        self.useConf = {"stateDir":     ("/var/lib/gettor/",        "global"),
+                        "blStateDir":   ("/var/lib/gettor/bl/",     "global"),
+                        "srcEmail":     ("gettor@xxxxxxxxxxxxxx",   "global"),
+                        "distDir":      ("/var/lib/gettor/pkg/",    "global"),
+                        "locale":       ("en",                      "global"),
+                        "logSubSystem": ("nothing",                 "global"),
+                        "logFile":      ("/dev/null",               "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()
+
+        # See if config file is accessable
         try:
             if os.access(self.configFile, os.R_OK):
                 readableConfigFile = True
@@ -46,69 +105,59 @@
                 # If they make a mistake for now we'll ignore *everything* :-)
                 self.config.read(self.configFile)
             except:
-                self.config.add_section("global")
-        else:
-            self.config.add_section("global")
+                pass
 
-        if self.config.has_option("global", "stateDir"):
-            self.stateDir = self.config.get("global", "stateDir")
-        else:
-            self.config.set("global", "stateDir", self.stateDir)
+        # 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)
 
-        if self.config.has_option("global", "blStateDir"):
-            self.blStateDir = self.config.get("global", "blStateDir")
-        else:
-            self.config.set("global", "blStateDir", self.blStateDir)
-
-        if self.config.has_option("global", "srcEmail"):
-            self.srcEmail = self.config.get("global", "srcEmail")
-        else:
-            self.config.set("global", "srcEmail", self.srcEmail)
-
-        if self.config.has_option("global", "distDir"):
-            self.distDir = self.config.get("global", "distDir")
-        else:
-            self.config.set("global", "distDir", self.distDir)
-
-        if self.config.has_option("global", "locale"):
-            self.locale = self.config.get("global", "locale")
-        else:
-            self.config.set("global", "locale", self.locale)
-
-        if self.config.has_option("global", "logSubSystem"):
-            self.logSubSystem = self.config.get("global", "logSubSystem")
-        else:
-            self.config.set("global", "logSubSystem", self.logSubSystem)
-
-        if self.config.has_option("global", "logFile"):
-            self.logFile = self.config.get("global", "logFile")
-        else:
-            self.config.set("global", "logFile", self.logFile)
-
     def printConfiguration(self):
-        return self.config.write(sys.stdout)
+        '''
+        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.stateDir
+        return self.useConf["stateDir"][0]
 
     def getBlStateDir(self):
-        return self.blStateDir
+        return self.useConf["blStateDir"][0]
 
     def getSrcEmail(self):
-        return self.srcEmail
+        return self.useConf["srcEmail"][0]
 
     def getDistDir(self):
-        return self.distDir
+        return self.useConf["distDir"][0]
 
     def getLocale(self):
-        return self.locale
+        return self.useConf["locale"][0]
 
     def getLogSubSystem(self):
-        return self.logSubSystem
+        return self.useConf["logSubSystem"][0]
 
     def getLogFile(self):
-        return self.logFile
+        return self.useConf["logFile"][0]
 
 if __name__ == "__main__" :
     c = gettorConf()

Added: projects/gettor/gettor_opt.py
===================================================================
--- projects/gettor/gettor_opt.py	                        (rev 0)
+++ projects/gettor/gettor_opt.py	2008-09-20 20:54:16 UTC (rev 16929)
@@ -0,0 +1,23 @@
+#!/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
+
+def parseOpts():
+    cmdParser = optparse.OptionParser()
+    cmdParser.add_option("-c", "--config", dest="configfile",
+                        default="~/.gettorrc",
+                        help="set config file to FILE", metavar="FILE")
+
+    return cmdParser.parse_args()

Modified: projects/gettor/gettor_requests.py
===================================================================
--- projects/gettor/gettor_requests.py	2008-09-19 19:22:43 UTC (rev 16928)
+++ projects/gettor/gettor_requests.py	2008-09-20 20:54:16 UTC (rev 16929)
@@ -53,6 +53,16 @@
     # If we get here, we didn't find a package we're currently serving
     return None
 
+def parseLocale(parsedMessage):
+    """Check if the user wants a reply in a certain language"""
+    pattern = re.compile("^Lang:\s+(.*)$")
+    for line in email.Iterators.body_line_iterator(parsedMessage):
+        match = pattern.match(line)
+        if match:
+            return match.group(1)
+    else:
+        return None
+
 if __name__ == "__main__" :
     """ Give us an email to understand what we think of it. """
     packageList = {