[or-cvs] r22014: {weather} - Code cleanups - Make user input validation more bulletproo (in weather/trunk: . lib/weather)

Christian Fromme kaner at strace.org
Thu Mar 18 17:02:57 UTC 2010


Author: kaner
Date: 2010-03-18 17:02:56 +0000 (Thu, 18 Mar 2010)
New Revision: 22014

Modified:
   weather/trunk/Weather.py
   weather/trunk/lib/weather/config.py
   weather/trunk/lib/weather/constants.py
   weather/trunk/lib/weather/poller.py
   weather/trunk/lib/weather/queries.py
   weather/trunk/lib/weather/torping.py
   weather/trunk/lib/weather/utils.py
Log:
- Code cleanups
- Make user input validation more bulletproof
- Introduce logging


Modified: weather/trunk/Weather.py
===================================================================
--- weather/trunk/Weather.py	2010-03-18 09:06:10 UTC (rev 22013)
+++ weather/trunk/Weather.py	2010-03-18 17:02:56 UTC (rev 22014)
@@ -9,48 +9,85 @@
 import os
 import re
 import sys
+import logging
 from twisted.web import resource, static, server, http
 from twisted.enterprise import adbapi
 from twisted.internet import reactor
 from twisted.internet.task import LoopingCall 
 
 from weather.torping import TorPing
-from weather.constants import PAGE_TEMPLATE, PAGE_SIGNUP, CONFIRMATION_MAIL, THANKS_OUT, PAGE_SUB_FIN, SUBS_MAIL
-from weather.queries import CHECK_SUBS_Q, INSERT_SUBS_Q, CHECK_SUBS_AUTH_Q, ACK_SUB_Q, UNSUBSCRIBE_Q
+import weather.constants as constants
+import weather.queries as queries
 from weather.poller import WeatherPoller
-from weather.config import pollPeriod, URLbase, mailFrom, databaseName
+import weather.config as config
 import weather.utils as utils
 
 class WeatherIndex(resource.Resource):
+    """The index page"""
     def render(self, request):
-        return utils.pageOut(PAGE_SIGNUP)
+        return utils.pageOut(constants.PAGE_SIGNUP)
 
 class SubscribeRequest(resource.Resource):
+    """Implementation of the 'page' that receives a subscription request by
+       a user."""
     def __init__(self, dbConn):
         self.dbConn = dbConn
         resource.Resource.__init__(self)
 
-    def render(self, request):
-        self.request = request
-        self.email = request.args['email'][0]
-        self.node = request.args['node'][0]
-        self.downtime = int(request.args['downtime'][0])
+    def _parseEmail(self):
+        if not self.request.args.has_key("email"):
+            logging.error("Request provided no email address")
+            return False
+        self.email = self.request.args['email'][0]
+        if not utils.checkMail(self.email):
+            logging.error("Bad email address '%s'" % self.email)
+            return False
+        return True
+
+    def _parseNodeID(self):
+        if not self.request.args.has_key("node"):
+            logging.error("Request provided no node ID")
+            return False
+        self.node = self.request.args['node'][0]
+        if not utils.checkNodeID(self.node):
+            logging.error("Bad node ID '%s'" % self.email)
+            return False
+        return True
+
+    def _parseDowntime(self):
+        if not self.request.args.has_key("downtime"):
+            logging.error("Request provided no downtime number")
+            return False
+        self.downtime = 1
+        try:
+            self.downtime = int(self.request.args['downtime'][0])
+        except:
+            d = self.request.args['downtime'][0]
+            logging.error("Bad downtime number: '%s'" % d)
+            return False
         # Don't accept downtime grace values longer than 8760 hours = 1 year
         if self.downtime > 8760:
             self.downtime = 8760
         if self.downtime < 1:
             self.downtime = 1
+        return True
 
-        if not utils.checkMail(self.email):
-            return "Error: Bad email address '%s'" % self.email
+    def render(self, request):
+        self.request = request
+        if not self._parseEmail():
+            return self._errOut("Please check the email address you entered")
+        if not self._parseNodeID():
+            return self._errOut("Please check the node ID you entered")
+        if not self._parseDowntime():
+            return self._errOut("Please check the downtime hours you entered")
 
         self.subs_auth = utils.getRandString()
         self.unsubs_auth = utils.getRandString()
-        self._isSubscribedAlready()
+        self._checkSubscription()
         return server.NOT_DONE_YET
 
-    def _isSubscribedAlready(self):
-        dbQuery = CHECK_SUBS_Q % (self.email, self.node)
+    def _checkSubscription(self):
+        dbQuery = queries.CHECK_SUBS_Q % (self.email, self.node)
         self.dbConn.runQuery(dbQuery).addCallback(
             self._checkHasSubRet).addErrback(
             self._errback)
@@ -58,135 +95,153 @@
     def _checkHasSubRet(self, result):
         # Do we already have a subscription for this address for this node?
         if len(result) is not 0:
-            self.request.setResponseCode(http.OK)
-            self.request.write(utils.pageOut("Error: Already subscribed."))
-            self.request.finish()
+            self._errOut("Error: Already subscribed.")
         else:
             # Alright, subscribe it
             self._runSaveQuery()
 
     def _runSaveQuery(self):
-        dbQuery = INSERT_SUBS_Q % (self.email, self.node, \
+        dbQuery = queries.INSERT_SUBS_Q % (self.email, self.node, \
                               self.subs_auth, self.unsubs_auth, self.downtime)
         self.dbConn.runOperation(dbQuery).addCallback(
             self._saved).addErrback(
             self._errback)
 
     def _saved(self, result):
-        url = URLbase + "/confirm-subscribe?auth=" + self.subs_auth
+        url = config.URLbase + "/confirm-subscribe?auth=" + self.subs_auth
         try:
-            mailText = CONFIRMATION_MAIL % (self.node, url)
+            mailText = constants.CONFIRMATION_MAIL % (self.node, url)
             subject = "Confirmation needed"
-            utils.sendMail(mailFrom, self.email, mailText, subject)
+            utils.sendMail(config.mailFrom, self.email, mailText, subject)
         except Exception, e:
-            self.error = "Unknown error while sending confirmation mail. " + \
-                         "Please try again later."
-            #             "[Exception %s]" % sys.exc_info()[0]
-            self._rollBack()
+            error = "Unknown error while sending confirmation mail. " + \
+                    "Please try again later."
+            logging.error(e)
+            self._rollBack(error)
             return
-
         self.request.setResponseCode(http.OK)
-        text = THANKS_OUT % self.email
+        text = constants.THANKS_OUT % self.email
         self.request.write(utils.pageOut(text))
         self.request.finish()
 
-    def _rollBack(self):
-        dbQuery = CHECK_SUBS_Q % (self.email, self.node)
+    def _rollBack(self, errorMsg):
+        dbQuery = queries.ROLLBACK_Q % (self.email, self.node)
         self.dbConn.runQuery(dbQuery).addCallback(
-            self._errOut).addErrback(
+            self._errOut, errorMsg).addErrback(
             self._errback)
 
-    def _errOut(self):
+    def _errback(self, failure):
+        self._errOut("Error in subscribe: %s" % (failure.getErrorMessage()))
+
+    def _errOut(self, error):
+        logging.error(error)
         self.request.setResponseCode(http.INTERNAL_SERVER_ERROR)
-        self.request.write(utils.pageOut(self.error))
+        self.request.write(utils.pageOut(error))
         self.request.finish()
+        return server.NOT_DONE_YET
 
-    def _errback(self, failure):
-        self.error = "Error: %s" % (failure.getErrorMessage())
-        self._errOut()
 
 class ConfirmSubscribeRequest(resource.Resource):
+    """The implementation of the 'page' the user gets to see if he confirms
+       his subscription"""
     def __init__(self, dbConn):
         self.dbConn = dbConn
         resource.Resource.__init__(self)
 
     def render(self, request):
-        self.subs_auth = request.args['auth'][0]
-        self._lookupSubsAuth(request)
+        self.request = request
+        if not self.request.args.has_key("auth"):
+            self._errOut("Bad auth string")
+            return server.NOT_DONE_YET
+        self.subs_auth = self.request.args['auth'][0]
+        self._lookupSubsAuth()
         return server.NOT_DONE_YET
 
-    def _lookupSubsAuth(self, request):
-        dbQuery = CHECK_SUBS_AUTH_Q % self.subs_auth
+    def _lookupSubsAuth(self):
+        dbQuery = queries.CHECK_SUBS_AUTH_Q % self.subs_auth
         self.dbConn.runQuery(dbQuery).addCallback(
-            self._checkRet, request).addErrback(
-            self._errback, request)
+            self._checkRet).addErrback(
+            self._sqlErrback)
 
-    def _checkRet(self, result, request):
+    def _checkRet(self, result):
         if len(result) is 0:
-            request.setResponseCode(http.OK)
-            text = "Error: No subscription with your auth code"
-            request.write(utils.pageOut(text))
-            request.finish()
+            error = "Error: No unconfirmed subscription with given auth code"
+            self._errOut(error)
         else:
             self.unsubs_auth = str(result[0][0])
             self.node = str(result[0][1])
             self.email = str(result[0][2])
-            return self._ackSubscription(request)
+            return self._ackSubscription()
 
-    def _ackSubscription(self, request):
-        dbQuery = ACK_SUB_Q % self.subs_auth
+    def _ackSubscription(self):
+        dbQuery = queries.ACK_SUB_Q % self.subs_auth
         self.dbConn.runQuery(dbQuery).addCallback(
-            self._subDone, request).addErrback(
-            self._errback, request)
+            self._subDone).addErrback(
+            self._sqlErrback)
 
-    def _subDone(self, result, request):
-        url = URLbase + "/unsubscribe?auth=" + self.unsubs_auth
+    def _subDone(self, result):
+        url = config.URLbase + "/unsubscribe?auth=" + self.unsubs_auth
         link = "<a href=\"" + url + "\">" + url + "</a>"
-        pageout = PAGE_SUB_FIN % link
+        pageout = constants.PAGE_SUB_FIN % link
         try:
-            mailText = SUBS_MAIL % (self.node, url)
+            mailText = constants.SUBS_MAIL % (self.node, url)
             subject = "Subscription successfull"
-            utils.sendMail(mailFrom, self.email, mailText, subject)
+            utils.sendMail(config.mailFrom, self.email, mailText, subject)
         except Exception, e:
             pageout += "\n\nUnknown error while sending confirmation mail."
-            print e
-        request.write(utils.pageOut(pageout))
-        request.finish()
+            logging.error("Error while confirmation mail: %s" % e)
+        self.request.write(utils.pageOut(pageout))
+        self.request.finish()
 
-    def _errback(self, failure, request):
-        request.setResponseCode(http.INTERNAL_SERVER_ERROR)
-        text = "Error: %s" % (failure.getErrorMessage())
-        request.write(utils.pageOut(text))
-        request.finish()
+    def _sqlErrback(self, failure):
+        error = "Error in ConfirmSubscribe: %s" % (failure.getErrorMessage())
+        self._errOut(error)
 
+    def _errOut(self, error):
+        logging.error(error)
+        self.request.setResponseCode(http.INTERNAL_SERVER_ERROR)
+        self.request.write(utils.pageOut(error))
+        self.request.finish()
+
 class UnsubscribeRequest(resource.Resource):
+    """The implementation of the 'page' the user gets to see if he wishes to 
+       unsubscribe"""
     def __init__(self, dbConn):
         self.dbConn = dbConn
         resource.Resource.__init__(self)
 
     def render(self, request):
+        self.request = request
+        if not self.request.args.has_key("auth"):
+            self._errOut("Bad auth value")
+            return server.NOT_DONE_YET
         self.unsubs_auth = request.args['auth'][0]
-        self._deleteSub(request)
+        self._deleteSub()
         return server.NOT_DONE_YET
 
-    def _deleteSub(self, request):
-        dbQuery = UNSUBSCRIBE_Q % self.unsubs_auth
+    def _deleteSub(self):
+        dbQuery = queries.UNSUBSCRIBE_Q % self.unsubs_auth
         self.dbConn.runQuery(dbQuery).addCallback(
-            self._deleteDone, request).addErrback(
-            self._errback, request)
+            self._deleteDone).addErrback(
+            self._errback)
 
-    def _deleteDone(self, result, request):
-        request.setResponseCode(http.OK)
-        request.write(utils.pageOut("Subscription deleted. Goodbye."))
-        request.finish()
+    def _deleteDone(self, result):
+        self.request.setResponseCode(http.OK)
+        self.request.write(utils.pageOut("Subscription deleted. Goodbye."))
+        self.request.finish()
 
-    def _errback(self, failure, request):
-        request.setResponseCode(http.INTERNAL_SERVER_ERROR)
-        text = "Error: %s" % (failure.getErrorMessage())
-        request.write(utils.pageOut(text))
-        request.finish()
+    def _errback(self, failure):
+        error = "Error in UnsubscribeRequest: %s" % (failure.getErrorMessage())
+        self._errOut(error)
 
+    def _errOut(self, error):
+        logging.error(error)
+        self.request.setResponseCode(http.INTERNAL_SERVER_ERROR)
+        self.request.write(utils.pageOut(error))
+        self.request.finish()
+
 class RootResource(resource.Resource):
+    """Root resource: Collect all resources needed by weather"""
     def __init__(self, dbConn):
         resource.Resource.__init__(self)
         self.putChild('top-left.png', static.File("./top-left.png"))
@@ -199,17 +254,20 @@
         self.putChild('unsubscribe', UnsubscribeRequest(dbConn))
 
 def main():
+    # Set up logging
+    utils.initLogging()
     # Set up database connection
-    dbConn = utils.setupDBConn(databaseName)
+    dbConn = utils.setupDBConn(config.databaseName)
     # Set up polling timer
     # XXX Have one main TorPing instance until TotCtl fixes its thread leak
     torPing = TorPing()
     weatherPoller = WeatherPoller(dbConn, torPing)
     pollTimer = LoopingCall(weatherPoller.poller)
-    pollTimer.start(pollPeriod)
+    pollTimer.start(config.pollPeriod)
     # Set up webserver
     weatherSite = server.Site(RootResource(dbConn))
     reactor.listenTCP(8000, weatherSite)
+    logging.info("Weather service started")
     reactor.run()
 
 if __name__ == "__main__":

Modified: weather/trunk/lib/weather/config.py
===================================================================
--- weather/trunk/lib/weather/config.py	2010-03-18 09:06:10 UTC (rev 22013)
+++ weather/trunk/lib/weather/config.py	2010-03-18 17:02:56 UTC (rev 22014)
@@ -1,18 +1,15 @@
-#!/usr/bin/env python2.5
+# Tor Weather
+# by Jacob Appelbaum <jacob at appelbaum.net>, Christian Fromme <kaner at strace.org>
+# Copyright (c) 2009, 2010 The Tor Project
+# See LICENSE for licensing information
 
 URLbase = "https://weather.torproject.org"
 
+# XXX: Make bulletproof
 authenticator = open("auth_token").read().strip()
 
 mailFrom = "tor-ops at torproject.org"
 
-# these respond to pings (for now!) and are geographically dispersed
-
-pingTargets = ["google.com", "telstra.com.au", "yahoo.co.uk"]
-
-failureThreshold = 4 # this number of failures in a row counts as being
-                      # down
-
 pollPeriod = 3600 # Check every hour
 
 databaseName = "subscriptions.db"

Modified: weather/trunk/lib/weather/constants.py
===================================================================
--- weather/trunk/lib/weather/constants.py	2010-03-18 09:06:10 UTC (rev 22013)
+++ weather/trunk/lib/weather/constants.py	2010-03-18 17:02:56 UTC (rev 22014)
@@ -1,5 +1,7 @@
-#!/usr/bin/python
-# Some constants needed in weather
+# Tor Weather
+# by Jacob Appelbaum <jacob at appelbaum.net>, Christian Fromme <kaner at strace.org>
+# Copyright (c) 2009, 2010 The Tor Project
+# See LICENSE for licensing information
 
 THANKS_OUT = """
     Thanks for using tor weather. A confirmation request has been sent to '%s'.
@@ -28,7 +30,7 @@
     Somebody (possibly you) has requested that status monitoring information 
     about a tor node (id: %s) be sent to this email address.
 
-    If you wish to confirm this request, please visit the following link:
+    If you wish to confirm this request, please visit the following url:
 
     %s 
 
@@ -100,7 +102,7 @@
 </p>
 <p>
 How many hours of downtime until we send a notification:<br>
-<input type="text" name="downtime" size="50" maxlength="255" value="Default is 1 hour, enter up to 8760 (1 year)" onclick="if (this.value == 'Default is 1 hour, enter up to 8760 (1 year)') {this.value = '1'}" />
+<input type="text" name="downtime" size="50" maxlength="255" value="Default is 1 hour, enter up to 8760 (1 year)" onclick="if (this.value == 'Default is 1 hour, enter up to 8760 (1 year)') {this.value = 'Default is 1 hour, enter up to 8760 (1 year)'}" />
 </p>
 <p>
 <input type="submit" class="submit" value="Subscribe to Tor Weather" name="sa"/>

Modified: weather/trunk/lib/weather/poller.py
===================================================================
--- weather/trunk/lib/weather/poller.py	2010-03-18 09:06:10 UTC (rev 22013)
+++ weather/trunk/lib/weather/poller.py	2010-03-18 17:02:56 UTC (rev 22014)
@@ -1,12 +1,16 @@
-#!/usr/bin/python
+# Tor Weather
+# by Jacob Appelbaum <jacob at appelbaum.net>, Christian Fromme <kaner at strace.org>
+# Copyright (c) 2009, 2010 The Tor Project
+# See LICENSE for licensing information
 
+import logging
 import smtplib
+from twisted.web import server
 
+from weather.torping import TorPing
+import weather.config as config
+import weather.constants as constants
 import weather.queries as queries
-from twisted.web import server
-from weather.torping import TorPing
-from weather.config import URLbase, mailFrom
-from weather.constants import REPORT_MAIL
 import weather.utils as utils
 
 class WeatherPoller():
@@ -29,16 +33,15 @@
         for result in resultList:
             checkHost = result[2]
             if not self._checkHost(checkHost):
-                print "Server %s seems to be offline" % checkHost
+                logging.info("Server %s seems to be offline" % checkHost)
                 self._handleOfflineNode(result)
             else:
-                print "Server %s is ok" % checkHost
+                logging.info("Server %s is ok" % checkHost)
                 # Reset possible seen_down counter
                 self._resetSeendown(result)
     
     def _errBack(self, failure):
-        # XXX: Log
-        print "Error: ", failure.getErrorMessage()
+        logging.error("Error: %s" % failure.getErrorMessage())
 
     def _checkHost(self, hostID):
         return self.torPing.ping(hostID)
@@ -62,18 +65,17 @@
         noticed = result[0][8]
         unsubs_auth = result[0][4]
         if noticed is 0 and (seenDown + 1) >= downGrace:
-            # XXX: Log
+            logging.info("Sending report to %s about %s" % (email, node))
             self._sendNotice(email, node, id, unsubs_auth)
 
     def _sendNotice(self, email, node, id, unsubs_auth):
-        unsubsURL =  URLbase + "/unsubscribe?auth=" + str(unsubs_auth)
-        mailText = REPORT_MAIL % (node, unsubsURL)
+        unsubsURL =  config.URLbase + "/unsubscribe?auth=" + str(unsubs_auth)
+        mailText = constants.REPORT_MAIL % (node, unsubsURL)
         try:
             subject = "Report"
-            utils.sendMail(mailFrom, email, mailText, subject)
+            utils.sendMail(config.mailFrom, email, mailText, subject)
         except:
-            # XXX: Log
-            print "Error, exception!"
+            logging.error("Could not send weather report for %s", email)
         else:
             # Set 'noticed' flag so we don't bother that user again
             self._setNoticed(id)
@@ -91,7 +93,7 @@
             self._errBack)
 
     def _updateDone(self, result):
-        print "Query ok"        
+        pass
 
     def _resetSeendown(self, dbRow):
         dbQuery = queries.RESET_COUNT_Q % dbRow[0]

Modified: weather/trunk/lib/weather/queries.py
===================================================================
--- weather/trunk/lib/weather/queries.py	2010-03-18 09:06:10 UTC (rev 22013)
+++ weather/trunk/lib/weather/queries.py	2010-03-18 17:02:56 UTC (rev 22014)
@@ -1,7 +1,11 @@
-#!/usr/bin/python
+# Tor Weather
+# by Jacob Appelbaum <jacob at appelbaum.net>, Christian Fromme <kaner at strace.org>
+# Copyright (c) 2009, 2010 The Tor Project
+# See LICENSE for licensing information
 
 # Query strings
 CHECK_SUBS_Q = "SELECT id FROM subscriptions WHERE email='%s' and node='%s'"
+ROLLBACK_Q = "DELETE FROM subscriptions WHERE email='%s' and node='%s'"
 INSERT_SUBS_Q = """
     INSERT INTO subscriptions (email, node, subs_auth, unsubs_auth, subscribed, downtime_grace, seen_down, noticed) VALUES ('%s', '%s', '%s', '%s', 0, '%s', 0, 0)
 """

Modified: weather/trunk/lib/weather/torping.py
===================================================================
--- weather/trunk/lib/weather/torping.py	2010-03-18 09:06:10 UTC (rev 22013)
+++ weather/trunk/lib/weather/torping.py	2010-03-18 17:02:56 UTC (rev 22014)
@@ -1,9 +1,14 @@
-#!/usr/bin/python
+# Tor Weather
+# by Jacob Appelbaum <jacob at appelbaum.net>, Christian Fromme <kaner at strace.org>
+# Copyright (c) 2009, 2010 The Tor Project
+# See LICENSE for licensing information
 # Taken from the old weather code
 
+import sys
+import logging
 import socket
 from TorCtl import TorCtl
-from weather.config import authenticator
+import weather.config
 
 debugfile = open("debug", "w")
 
@@ -14,9 +19,17 @@
     self.control_host = control_host
     self.control_port = control_port
     self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    self.sock.connect((control_host,control_port))
+    try:
+        self.sock.connect((control_host,control_port))
+    except:
+        errormsg = "Could not connect to Tor control port" + \
+                   "Is Tor running on %s with its control port opened on %s?" \
+                   % (control_host, control_port)
+        logging.error(errormsg)
+        print >> sys.stderr, errormsg
+        raise
     self.control = TorCtl.Connection(self.sock)
-    self.control.authenticate(authenticator)
+    self.control.authenticate(weather.config.authenticator)
     self.control.debug(debugfile)
 
   def __del__(self):
@@ -28,6 +41,7 @@
     try:
       self.control.close()
     except:
+      logging.error("Exception while closing TorCtl")
       pass
 
     del self.control
@@ -37,15 +51,15 @@
     "Let's see if this tor node is up by only asking Tor."
     try:
        info = self.control.get_info(str("ns/id/" + nodeId))
-    except TorCtl.ErrorReply:
+    except TorCtl.ErrorReply, e:
         # If we're getting here, we're likely seeing:
         # ErrorReply: 552 Unrecognized key "ns/id/46D9..."
         # This means that the node isn't recognized by 
-        # XXX: Log
+        logging.error("ErrorReply: %s" % str(e))
         return False
 
     except:
-        # XXX: Log
+        logging.error("Unknown exception in ping()")
         return False
 
     # If we're here, we were able to fetch information about the router

Modified: weather/trunk/lib/weather/utils.py
===================================================================
--- weather/trunk/lib/weather/utils.py	2010-03-18 09:06:10 UTC (rev 22013)
+++ weather/trunk/lib/weather/utils.py	2010-03-18 17:02:56 UTC (rev 22014)
@@ -1,4 +1,7 @@
-#!/usr/bin/python
+# Tor Weather
+# by Jacob Appelbaum <jacob at appelbaum.net>, Christian Fromme <kaner at strace.org>
+# Copyright (c) 2009, 2010 The Tor Project
+# See LICENSE for licensing information
 
 import os
 import re
@@ -6,19 +9,20 @@
 import base64
 import sqlite3
 import smtplib
+import logging
 
 from email.mime.multipart import MIMEMultipart
 from email.mime.base import MIMEBase
 from email.mime.text import MIMEText
 from twisted.enterprise import adbapi
 
-from weather.constants import PAGE_TEMPLATE, PAGE_SIGNUP
-from weather.queries import CREATE_TABLE_Q
+import weather.constants as constants
+import weather.queries as queries
 
 def setupDBConn(databaseName):
     """Create database and table in case they don't exist yet, return pool"""
     db = sqlite3.connect(databaseName)
-    db.execute(CREATE_TABLE_Q % "subscriptions")
+    db.execute(queries.CREATE_TABLE_Q % "subscriptions")
     db.close()
     dbConn = adbapi.ConnectionPool("sqlite3", databaseName, check_same_thread=False)
     return dbConn
@@ -55,10 +59,10 @@
     r = base64.urlsafe_b64encode(os.urandom(18))[:-1]
     # some email clients don't like URLs ending in -
     if r.endswith("-"):    
-        r.replace("-", "x")
+        r = r.replace("-", "x")
     return r
 
-def isValidNodeID(nodeID):
+def checkNodeID(nodeID):
     """Check if a given Tor node ID looks ok"""
     nodeOk = re.compile("(0x)?[a-fA-F0-9]{40}\Z")
     if nodeOk.match(nodeID):
@@ -68,7 +72,7 @@
 
 def pageOut(text):
     """Our great template engine ;-)"""
-    return PAGE_TEMPLATE % text
+    return constants.PAGE_TEMPLATE % text
 
 def sendMail(fromPart, toPart, messageText, subject=""):
     """Send a certain mail text with certain From: and certain To: field"""
@@ -86,3 +90,9 @@
     smtp = smtplib.SMTP("localhost:25")
     smtp.sendmail(fromPart, toPart, message.as_string())
     smtp.quit()
+
+def initLogging(loglevel=logging.INFO, 
+                logformat='%(asctime)-15s (%(process)d) %(message)s',
+                logfile='./weather.log'):
+    """Very basic log setup"""
+    logging.basicConfig(format=logformat, level=loglevel, filename=logfile)



More information about the tor-commits mailing list