[or-cvs] r21852: {arm} Weekend bugfix bundle. added: returned option to reload torr (in arm/trunk: . init interface)

Damian Johnson atagar1 at gmail.com
Mon Mar 8 05:38:39 UTC 2010


Author: atagar
Date: 2010-03-08 05:38:39 +0000 (Mon, 08 Mar 2010)
New Revision: 21852

Modified:
   arm/trunk/ChangeLog
   arm/trunk/TODO
   arm/trunk/init/starter.py
   arm/trunk/interface/confPanel.py
   arm/trunk/interface/connPanel.py
   arm/trunk/interface/controller.py
   arm/trunk/interface/fileDescriptorPopup.py
   arm/trunk/interface/headerPanel.py
Log:
Weekend bugfix bundle.
added: returned option to reload torrc and added option to issue a sighup
fix: header panel wasn't interpreting multi-line exit policies (caught by dun)
fix: substantial display bug when stripping comments and torrc is bigger than the panel (caught by Paul Menzel)
fix: deb specific hack for estimating the file descriptor limit was broken (caught by Paul Menzel)
fix: skip printing stack trace in case of keyboard interrupt
fix: updated listing of directory authorities (for tor version 0.2.1.24)
fix: several uncaught exceptions when the network consensus couldn't be fetched
fix: torrc comment stripping wasn't removing comments on the same lines as commands
fix: torrc validation was failing under some conditions for CSV values (like ExitPolicy)



Modified: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/ChangeLog	2010-03-08 05:38:39 UTC (rev 21852)
@@ -1,6 +1,19 @@
 CHANGE LOG
 
-2/27/10 - version 1.3.3
+3/7/10 - version 1.3.4
+Weekend bugfix bundle.
+
+    * added: returned option to reload torrc and added option to issue a sighup
+    * fix: header panel wasn't interpreting multi-line exit policies (caught by dun)
+    * fix: substantial display bug when stripping comments and torrc is bigger than the panel (caught by Paul Menzel)
+    * fix: deb specific hack for estimating the file descriptor limit was broken (caught by Paul Menzel)
+    * fix: skip printing stack trace in case of keyboard interrupt
+    * fix: updated listing of directory authorities (for tor version 0.2.1.24)
+    * fix: several uncaught exceptions when the network consensus couldn't be fetched
+    * fix: torrc comment stripping wasn't removing comments on the same lines as commands
+    * fix: torrc validation was failing under some conditions for CSV values (like ExitPolicy)
+
+2/27/10 - version 1.3.3 (r21772)
 Hiding client/exit information to address privacy concerns and fixes for numerous issues brought up in irc.
 
     * added: scrubbing connection details of possible client and exit connections

Modified: arm/trunk/TODO
===================================================================
--- arm/trunk/TODO	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/TODO	2010-03-08 05:38:39 UTC (rev 21852)
@@ -30,6 +30,9 @@
 			be given the "UNKNOWN" type.
 	* regex fails for multiline log entries
 	* when logging no events still showing brackets
+	* scrolling in the torrc isn't working properly when comments are stripped
+			Current method of displaying torrc is pretty stupid (lots of repeated
+			work in display loop). When rewritten fixing this bug should be trivial.
 	* quitting can hang several seconds when there's hostnames left to resolve
 			Not sure how to address this - problem is that the calls to 'host' can 
 			take a while to time out. Might need another thread to kill the calls?

Modified: arm/trunk/init/starter.py
===================================================================
--- arm/trunk/init/starter.py	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/init/starter.py	2010-03-08 05:38:39 UTC (rev 21852)
@@ -17,8 +17,8 @@
 from TorCtl import TorCtl, TorUtil
 from interface import controller, logPanel
 
-VERSION = "1.3.3"
-LAST_MODIFIED = "Feb 27, 2010"
+VERSION = "1.3.4"
+LAST_MODIFIED = "Mar 7, 2010"
 
 DEFAULT_CONTROL_ADDR = "127.0.0.1"
 DEFAULT_CONTROL_PORT = 9051
@@ -151,7 +151,11 @@
       conn.authenticate("")
     elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
       # password authentication, promts for password if it wasn't provided
-      if not authPassword: authPassword = getpass.getpass()
+      try:
+        if not authPassword: authPassword = getpass.getpass()
+      except KeyboardInterrupt:
+        sys.exit()
+      
       conn.authenticate(authPassword)
     elif authInfo.startswith("AUTH METHODS=COOKIE"):
       # cookie authtication, parses path to authentication cookie

Modified: arm/trunk/interface/confPanel.py
===================================================================
--- arm/trunk/interface/confPanel.py	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/interface/confPanel.py	2010-03-08 05:38:39 UTC (rev 21852)
@@ -54,11 +54,15 @@
     
     self.reset()
   
-  def reset(self):
+  def reset(self, logErrors=True):
     """
-    Reloads torrc contents and resets scroll height.
+    Reloads torrc contents and resets scroll height. Returns True if
+    successful, else false.
     """
+    
     try:
+      resetSuccessful = True
+      
       confFile = open(self.confLocation, "r")
       self.confContents = confFile.readlines()
       confFile.close()
@@ -114,10 +118,27 @@
             else:
               # general case - fetch all valid values
               for key, val in self.conn.get_option(command):
-                actualValues.append(val)
+                # TODO: check for a better way of figuring out CSV parameters
+                # (kinda doubt this is right... in config.c its listed as being
+                # a 'LINELIST') - still, good enough for common cases
+                if command in MULTI_LINE_PARAM: toAdd = val.split(",")
+                else: toAdd = [val]
+                
+                for newVal in toAdd:
+                  newVal = newVal.strip()
+                  if newVal not in actualValues: actualValues.append(newVal)
             
-            if not argument in actualValues:
-              self.corrections[lineNumber + 1] = ", ".join(actualValues)
+            # there might be multiple values on a single line - if so, check each
+            if command in MULTI_LINE_PARAM and "," in argument:
+              arguments = []
+              for entry in argument.split(","):
+                arguments.append(entry.strip())
+            else:
+              arguments = [argument]
+            
+            for entry in arguments:
+              if not entry in actualValues:
+                self.corrections[lineNumber + 1] = ", ".join(actualValues)
           except (TypeError, socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
             # TODO: for some reason the above provided:
             # TypeError: sequence item 0: expected string, NoneType found
@@ -127,21 +148,25 @@
             # reproduce. Catching the TypeError to just drop the torrc
             # validation for those systems
             
-            self.logger.monitor_event("WARN", "Unable to validate torrc")
+            if logErrors: self.logger.monitor_event("WARN", "Unable to validate torrc")
       
       # logs issues that arose
-      if self.irrelevantLines:
+      if self.irrelevantLines and logErrors:
         if len(self.irrelevantLines) > 1: first, second, third = "Entries", "are", ", including lines"
         else: first, second, third = "Entry", "is", " on line"
         baseMsg = "%s in your torrc %s ignored due to duplication%s" % (first, second, third)
         
         self.logger.monitor_event("NOTICE", "%s: %s (highlighted in blue)" % (baseMsg, ", ".join([str(val) for val in self.irrelevantLines])))
-      if self.corrections:
+      
+      if self.corrections and logErrors:
         self.logger.monitor_event("WARN", "Tor's state differs from loaded torrc")
     except IOError, exc:
+      resetSuccessful = False
       self.confContents = ["### Unable to load torrc ###"]
-      self.logger.monitor_event("WARN", "Unable to load torrc (%s)" % str(exc))
+      if logErrors: self.logger.monitor_event("WARN", "Unable to load torrc (%s)" % str(exc))
+    
     self.scroll = 0
+    return resetSuccessful
   
   def handleKey(self, key):
     self._resetBounds()
@@ -163,7 +188,24 @@
     numFieldWidth = int(math.log10(len(self.confContents))) + 1
     lineNum, displayLineNum = self.scroll + 1, 1 # lineNum corresponds to torrc, displayLineNum concerns what's presented
     
-    for i in range(self.scroll, min(len(self.confContents), self.scroll + pageHeight)):
+    # determine the ending line in the display (prevents us from going to the 
+    # effort of displaying lines that aren't visible - isn't really a 
+    # noticeable improvement unless the torrc is bazaarly long) 
+    if not self.stripComments:
+      endingLine = min(len(self.confContents), self.scroll + pageHeight)
+    else:
+      # checks for the last line of displayable content (ie, non-comment)
+      endingLine = self.scroll
+      displayedLines = 0        # number of lines of content
+      for i in range(self.scroll, len(self.confContents)):
+        endingLine += 1
+        lineText = self.confContents[i].strip()
+        
+        if lineText and lineText[0] != "#":
+          displayedLines += 1
+          if displayedLines == pageHeight: break
+    
+    for i in range(self.scroll, endingLine):
       lineText = self.confContents[i].strip()
       skipLine = False # true if we're not presenting line due to stripping
       
@@ -184,6 +226,7 @@
         if argEnd == -1: argEnd = len(lineText)
         
         command, argument, comment = lineText[:ctlEnd], lineText[ctlEnd:argEnd], lineText[argEnd:]
+        if self.stripComments: comment = ""
         
         # changes presentation if value's incorrect or irrelevant
         if lineNum in self.corrections.keys():

Modified: arm/trunk/interface/connPanel.py
===================================================================
--- arm/trunk/interface/connPanel.py	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/interface/connPanel.py	2010-03-08 05:38:39 UTC (rev 21852)
@@ -22,14 +22,16 @@
 # CHANGE THIS UNLESS YOU HAVE A DAMN GOOD REASON!)
 SCRUB_PRIVATE_DATA = True
 
-# directory servers (IP, port) for tor version 0.2.2.1-alpha-dev
+# directory servers (IP, port) for tor version 0.2.1.24
+# this comes from the dirservers array in src/or/config.c
 DIR_SERVERS = [("86.59.21.38", "80"),         # tor26
-               ("128.31.0.34", "9031"),       # moria1
+               ("128.31.0.39", "9031"),       # moria1
                ("216.224.124.114", "9030"),   # ides
-               ("80.190.246.100", "80"),      # gabelmoo
+               ("80.190.246.100", "8180"),    # gabelmoo
                ("194.109.206.212", "80"),     # dizum
-               ("213.73.91.31", "80"),        # dannenberg
-               ("208.83.223.34", "443")]      # urras
+               ("193.23.244.244", "80"),      # dannenberg
+               ("208.83.223.34", "443"),      # urras
+               ("82.94.251.203", "80")]       # Tonga
 
 # enums for listing types
 LIST_IP, LIST_HOSTNAME, LIST_FINGERPRINT, LIST_NICKNAME = range(4)
@@ -286,7 +288,9 @@
     isGuard = False
     try:
       myFingerprint = self.conn.get_info("fingerprint")
-      isGuard = "Guard" in self.conn.get_network_status("id/%s" % myFingerprint)[0].flags
+      nsCall = self.conn.get_network_status("id/%s" % myFingerprint)
+      if nsCall: isGuard = "Guard" in nsCall[0].flags
+      else: raise TorCtl.ErrorReply # network consensus couldn't be fetched
     except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass
     
     try:
@@ -379,9 +383,9 @@
       tmpCounter = 0 # used for unique port of unresolved family entries (funky hack)
       for fingerprint in self.family:
         try:
-          nsCommand = "ns/id/%s" % fingerprint
-          familyInfo = self.conn.get_info(nsCommand)[nsCommand].split()
-          familyAddress, familyPort = familyInfo[6], familyInfo[7]
+          nsCall = self.conn.get_network_status("id/%s" % fingerprint)
+          if nsCall: familyAddress, familyPort = nsCall[0][6], nsCall[0][7]
+          else: raise TorCtl.ErrorReply # network consensus couldn't be fetched
           
           countryCodeQuery = "ip-to-country/%s" % familyAddress
           familyCountryCode = self.conn.get_info(countryCodeQuery)[countryCodeQuery]
@@ -706,9 +710,9 @@
             # gets router description to see if 'down' is set
             toRemove = False
             try:
-              nsData = self.conn.get_network_status("id/%s" % entryFingerprint)
-              if len(nsData) != 1: raise TorCtl.ErrorReply() # ns lookup failed... weird
-              else: nsEntry = nsData[0]
+              nsCall = self.conn.get_network_status("id/%s" % entryFingerprint)
+              if not nsCall: raise TorCtl.ErrorReply() # network consensus couldn't be fetched
+              else: nsEntry = nsCall[0]
               
               descLookupCmd = "desc/id/%s" % entryFingerprint
               descEntry = TorCtl.Router.build_from_desc(self.conn.get_info(descLookupCmd)[descLookupCmd].split("\n"), nsEntry)
@@ -740,7 +744,10 @@
       match = self.getFingerprint(ipAddr, port)
       
       try:
-        if match != "UNKNOWN": match = self.conn.get_network_status("id/%s" % match)[0].nickname
+        if match != "UNKNOWN":
+          nsCall = self.conn.get_network_status("id/%s" % match)
+          if nsCall: match = nsCall[0].nickname
+          else: raise TorCtl.ErrorReply # network consensus couldn't be fetched
       except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): return "UNKNOWN" # don't cache result
       
       self.nicknameLookupCache[(ipAddr, port)] = match

Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/interface/controller.py	2010-03-08 05:38:39 UTC (rev 21852)
@@ -554,6 +554,9 @@
           
           lineNumLabel = "on" if panels["torrc"].showLineNum else "off"
           popup.addfstr(3, 41, "n: line numbering (<b>%s</b>)" % lineNumLabel)
+          
+          popup.addstr(4, 2, "r: reload torrc")
+          popup.addstr(4, 41, "x: reset tor (issue sighup)")
         
         popup.addstr(7, 2, "Press any key...")
         popup.refresh()
@@ -861,15 +864,16 @@
               if selection in relayLookupCache.keys(): nsEntry, descEntry = relayLookupCache[selection]
               else:
                 # ns lookup fails, can happen with localhost lookups if relay's having problems (orport not reachable)
-                try: nsData = conn.get_network_status("id/%s" % fingerprint)
+                # and this will be empty if network consensus couldn't be fetched
+                try: nsCall = conn.get_network_status("id/%s" % fingerprint)
                 except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): lookupErrored = True
                 
-                if not lookupErrored:
-                  if len(nsData) > 1:
+                if not lookupErrored and nsCall:
+                  if len(nsCall) > 1:
                     # multiple records for fingerprint (shouldn't happen)
                     panels["log"].monitor_event("WARN", "Multiple consensus entries for fingerprint: %s" % fingerprint)
                   
-                  nsEntry = nsData[0]
+                  nsEntry = nsCall[0]
                   
                   try:
                     descLookupCmd = "desc/id/%s" % fingerprint
@@ -1087,6 +1091,74 @@
         setPauseState(panels, isPaused, page)
       finally:
         panel.CURSES_LOCK.release()
+    elif page == 2 and key == ord('r') or key == ord('R'):
+      # reloads torrc, providing a notice if successful or not
+      isSuccessful = panels["torrc"].reset(False)
+      resetMsg = "torrc reloaded" if isSuccessful else "failed to reload torrc"
+      if isSuccessful: panels["torrc"].redraw()
+      
+      panels["control"].setMsg(resetMsg, curses.A_STANDOUT)
+      panels["control"].redraw()
+      time.sleep(1)
+      
+      panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
+    elif page == 2 and (key == ord('x') or key == ord('X')):
+      # provides prompt to confirm that arm should issue a sighup
+      panel.CURSES_LOCK.acquire()
+      try:
+        setPauseState(panels, isPaused, page, True)
+        
+        # provides prompt
+        panels["control"].setMsg("This will reset Tor's internal state. Are you sure (x again to confirm)?", curses.A_BOLD)
+        panels["control"].redraw()
+        
+        curses.cbreak()
+        confirmationKey = stdscr.getch()
+        if confirmationKey in (ord('x'), ord('X')):
+          try:
+            # Redirects stderr to stdout so we can check error status (output
+            # should be empty if successful). Example error:
+            # pkill: 5592 - Operation not permitted
+            #
+            # note that this may provide multiple errors, even if successful,
+            # hence this:
+            #   - only provide an error if Tor fails to log a sighup
+            #   - provide the error message associated with the tor pid (others
+            #     would be a red herring)
+            pkillCall = os.popen("pkill -sighup tor 2> /dev/stdout")
+            pkillOutput = pkillCall.readlines()
+            pkillCall.close()
+            
+            # Give the sighupTracker a moment to detect the sighup signal. This
+            # is, of course, a possible concurrency bug. However I'm not sure
+            # of a better method for blocking on this...
+            waitStart = time.time()
+            while time.time() - waitStart < 1:
+              time.sleep(0.1)
+              if sighupTracker.isReset: break
+            
+            if not sighupTracker.isReset:
+              errorLine = ""
+              if torPid:
+                for line in pkillOutput:
+                  if line.startswith("pkill: %s - " % torPid):
+                    errorLine = line
+                    break
+              
+              if errorLine: raise IOError(" ".join(errorLine.split()[3:]))
+              else: raise IOError()
+          except IOError, err:
+            errorMsg = " (%s)" % str(err) if str(err) else ""
+            panels["control"].setMsg("Sighup failed%s" % errorMsg, curses.A_STANDOUT)
+            panels["control"].redraw()
+            time.sleep(2)
+        
+        # reverts display settings
+        curses.halfdelay(REFRESH_RATE * 10)
+        panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
+        setPauseState(panels, isPaused, page)
+      finally:
+        panel.CURSES_LOCK.release()
     elif page == 0:
       panels["log"].handleKey(key)
     elif page == 1:
@@ -1095,5 +1167,8 @@
       panels["torrc"].handleKey(key)
 
 def startTorMonitor(conn, loggedEvents, isBlindMode):
-  curses.wrapper(drawTorMonitor, conn, loggedEvents, isBlindMode)
+  try:
+    curses.wrapper(drawTorMonitor, conn, loggedEvents, isBlindMode)
+  except KeyboardInterrupt:
+    pass # skip printing stack trace in case of keyboard interrupt
 

Modified: arm/trunk/interface/fileDescriptorPopup.py
===================================================================
--- arm/trunk/interface/fileDescriptorPopup.py	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/interface/fileDescriptorPopup.py	2010-03-08 05:38:39 UTC (rev 21852)
@@ -68,7 +68,7 @@
       # get the file descriptor limit for an arbitrary process. What we need is
       # for the tor process to provide the return value of the "getrlimit"
       # function via a GET_INFO call.
-      if torUser == "debian-tor":
+      if torUser.strip() == "debian-tor":
         # probably loaded via /etc/init.d/tor which changes descriptor limit
         self.fdLimit = 8192
       else:

Modified: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py	2010-03-08 03:34:17 UTC (rev 21851)
+++ arm/trunk/interface/headerPanel.py	2010-03-08 05:38:39 UTC (rev 21852)
@@ -18,8 +18,8 @@
                "Stable": "blue",      "Running": "yellow",  "Unnamed": "magenta",     "Valid": "green",
                "V2Dir": "cyan",       "V3Dir": "white"}
 
-VERSION_STATUS_COLORS = {"new": "blue",     "new in series": "blue",    "recommended": "green",
-                         "old": "red",      "obsolete": "red",          "unrecommended": "red"}
+VERSION_STATUS_COLORS = {"new": "blue",      "new in series": "blue",  "recommended": "green",  "old": "red",
+                         "obsolete": "red",  "unrecommended": "red",   "unknown": "cyan"}
 
 class HeaderPanel(panel.Panel):
   """
@@ -191,9 +191,16 @@
       
       try:
         # parameters from the user's torrc
-        configFields = ["Nickname", "ORPort", "DirPort", "ControlPort", "ExitPolicy"]
+        configFields = ["Nickname", "ORPort", "DirPort", "ControlPort"]
         self.vals.update(dict([(key, self.conn.get_option(key)[0][1]) for key in configFields]))
         
+        # fetch exit policy (might span over multiple lines)
+        exitPolicyEntries = []
+        for (key, value) in self.conn.get_option("ExitPolicy"):
+          exitPolicyEntries.append(value)
+        
+        self.vals["ExitPolicy"] = ", ".join(exitPolicyEntries)
+        
         # simply keeps booleans for if authentication info is set
         self.vals["IsPasswordAuthSet"] = not self.conn.get_option("HashedControlPassword")[0][1] == None
         self.vals["IsCookieAuthSet"] = self.conn.get_option("CookieAuthentication")[0][1] == "1"
@@ -231,7 +238,10 @@
     # flags held by relay
     self.vals["flags"] = []
     if self.vals["fingerprint"] != "Unknown":
-      try: self.vals["flags"] = self.conn.get_network_status("id/%s" % self.vals["fingerprint"])[0].flags
+      try:
+        nsCall = self.conn.get_network_status("id/%s" % self.vals["fingerprint"])
+        if nsCall: self.vals["flags"] = nsCall[0].flags
+        else: raise TorCtl.ErrorReply # network consensus couldn't be fetched
       except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass
     
     psParams = ["%cpu", "rss", "%mem", "etime"]



More information about the tor-commits mailing list