home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / arm / cli / controller.py < prev    next >
Encoding:
Python Source  |  2012-05-18  |  25.5 KB  |  806 lines

  1. """
  2. Main interface loop for arm, periodically redrawing the screen and issuing
  3. user input to the proper panels.
  4. """
  5.  
  6. import os
  7. import time
  8. import curses
  9. import threading
  10.  
  11. import cli.menu.menu
  12. import cli.wizard
  13. import cli.popups
  14. import cli.headerPanel
  15. import cli.logPanel
  16. import cli.configPanel
  17. import cli.torrcPanel
  18. import cli.interpretorPanel
  19. import cli.graphing.graphPanel
  20. import cli.graphing.bandwidthStats
  21. import cli.graphing.connStats
  22. import cli.graphing.resourceStats
  23. import cli.connections.connPanel
  24.  
  25. from TorCtl import TorCtl
  26.  
  27. from util import connections, conf, enum, hostnames, log, panel, sysTools, torConfig, torTools
  28.  
  29. ARM_CONTROLLER = None
  30.  
  31. CONFIG = {"startup.events": "N3",
  32.           "startup.dataDirectory": "~/.arm",
  33.           "startup.blindModeEnabled": False,
  34.           "features.offerTorShutdownOnQuit": False,
  35.           "features.panels.show.graph": True,
  36.           "features.panels.show.log": True,
  37.           "features.panels.show.connection": True,
  38.           "features.panels.show.config": True,
  39.           "features.panels.show.torrc": True,
  40.           "features.panels.show.interpretor": True,
  41.           "features.redrawRate": 5,
  42.           "features.refreshRate": 5,
  43.           "features.confirmQuit": True,
  44.           "features.graph.type": 1,
  45.           "features.graph.bw.prepopulate": True,
  46.           "wizard.default": {},
  47.           "log.startTime": log.INFO,
  48.           "log.torEventTypeUnrecognized": log.INFO,
  49.           "log.configEntryUndefined": log.NOTICE,
  50.           "log.unknownTorPid": log.WARN}
  51.  
  52. GraphStat = enum.Enum("BANDWIDTH", "CONNECTIONS", "SYSTEM_RESOURCES")
  53.  
  54. # maps 'features.graph.type' config values to the initial types
  55. GRAPH_INIT_STATS = {1: GraphStat.BANDWIDTH, 2: GraphStat.CONNECTIONS, 3: GraphStat.SYSTEM_RESOURCES}
  56.  
  57. def getController():
  58.   """
  59.   Provides the arm controller instance.
  60.   """
  61.   
  62.   return ARM_CONTROLLER
  63.  
  64. def initController(stdscr, startTime):
  65.   """
  66.   Spawns the controller, and related panels for it.
  67.   
  68.   Arguments:
  69.     stdscr - curses window
  70.   """
  71.   
  72.   global ARM_CONTROLLER
  73.   config = conf.getConfig("arm")
  74.   
  75.   # initializes the panels
  76.   stickyPanels = [cli.headerPanel.HeaderPanel(stdscr, startTime, config),
  77.                   LabelPanel(stdscr)]
  78.   pagePanels, firstPagePanels = [], []
  79.   
  80.   # first page: graph and log
  81.   if CONFIG["features.panels.show.graph"]:
  82.     firstPagePanels.append(cli.graphing.graphPanel.GraphPanel(stdscr))
  83.   
  84.   if CONFIG["features.panels.show.log"]:
  85.     expandedEvents = cli.logPanel.expandEvents(CONFIG["startup.events"])
  86.     firstPagePanels.append(cli.logPanel.LogPanel(stdscr, expandedEvents, config))
  87.   
  88.   if firstPagePanels: pagePanels.append(firstPagePanels)
  89.   
  90.   # second page: connections
  91.   if not CONFIG["startup.blindModeEnabled"] and CONFIG["features.panels.show.connection"]:
  92.     pagePanels.append([cli.connections.connPanel.ConnectionPanel(stdscr, config)])
  93.   
  94.   # third page: config
  95.   if CONFIG["features.panels.show.config"]:
  96.     pagePanels.append([cli.configPanel.ConfigPanel(stdscr, cli.configPanel.State.TOR, config)])
  97.   
  98.   # fourth page: torrc
  99.   if CONFIG["features.panels.show.torrc"]:
  100.     pagePanels.append([cli.torrcPanel.TorrcPanel(stdscr, cli.torrcPanel.Config.TORRC, config)])
  101.   
  102.   if CONFIG["features.panels.show.interpretor"]:
  103.     pagePanels.append([cli.interpretorPanel.InterpretorPanel(stdscr)])
  104.   
  105.   # initializes the controller
  106.   ARM_CONTROLLER = Controller(stdscr, stickyPanels, pagePanels)
  107.   
  108.   # additional configuration for the graph panel
  109.   graphPanel = ARM_CONTROLLER.getPanel("graph")
  110.   
  111.   if graphPanel:
  112.     # statistical monitors for graph
  113.     bwStats = cli.graphing.bandwidthStats.BandwidthStats(config)
  114.     graphPanel.addStats(GraphStat.BANDWIDTH, bwStats)
  115.     graphPanel.addStats(GraphStat.SYSTEM_RESOURCES, cli.graphing.resourceStats.ResourceStats())
  116.     if not CONFIG["startup.blindModeEnabled"]:
  117.       graphPanel.addStats(GraphStat.CONNECTIONS, cli.graphing.connStats.ConnStats())
  118.     
  119.     # sets graph based on config parameter
  120.     try:
  121.       initialStats = GRAPH_INIT_STATS.get(CONFIG["features.graph.type"])
  122.       graphPanel.setStats(initialStats)
  123.     except ValueError: pass # invalid stats, maybe connections when in blind mode
  124.     
  125.     # prepopulates bandwidth values from state file
  126.     if CONFIG["features.graph.bw.prepopulate"] and torTools.getConn().isAlive():
  127.       isSuccessful = bwStats.prepopulateFromState()
  128.       if isSuccessful: graphPanel.updateInterval = 4
  129.  
  130. class LabelPanel(panel.Panel):
  131.   """
  132.   Panel that just displays a single line of text.
  133.   """
  134.   
  135.   def __init__(self, stdscr):
  136.     panel.Panel.__init__(self, stdscr, "msg", 0, height=1)
  137.     self.msgText = ""
  138.     self.msgAttr = curses.A_NORMAL
  139.   
  140.   def setMessage(self, msg, attr = None):
  141.     """
  142.     Sets the message being displayed by the panel.
  143.     
  144.     Arguments:
  145.       msg  - string to be displayed
  146.       attr - attribute for the label, normal text if undefined
  147.     """
  148.     
  149.     if attr == None: attr = curses.A_NORMAL
  150.     self.msgText = msg
  151.     self.msgAttr = attr
  152.   
  153.   def draw(self, width, height):
  154.     self.addstr(0, 0, self.msgText, self.msgAttr)
  155.  
  156. class Controller:
  157.   """
  158.   Tracks the global state of the interface
  159.   """
  160.   
  161.   def __init__(self, stdscr, stickyPanels, pagePanels):
  162.     """
  163.     Creates a new controller instance. Panel lists are ordered as they appear,
  164.     top to bottom on the page.
  165.     
  166.     Arguments:
  167.       stdscr       - curses window
  168.       stickyPanels - panels shown at the top of each page
  169.       pagePanels   - list of pages, each being a list of the panels on it
  170.     """
  171.     
  172.     self._screen = stdscr
  173.     self._stickyPanels = stickyPanels
  174.     self._pagePanels = pagePanels
  175.     self._page = 0
  176.     self._isPaused = False
  177.     self._forceRedraw = False
  178.     self._isDone = False
  179.     self._torManager = TorManager(self)
  180.     self._lastDrawn = 0
  181.     self.setMsg() # initializes our control message
  182.   
  183.   def getScreen(self):
  184.     """
  185.     Provides our curses window.
  186.     """
  187.     
  188.     return self._screen
  189.   
  190.   def getPageCount(self):
  191.     """
  192.     Provides the number of pages the interface has. This may be zero if all
  193.     page panels have been disabled.
  194.     """
  195.     
  196.     return len(self._pagePanels)
  197.   
  198.   def getPage(self):
  199.     """
  200.     Provides the number belonging to this page. Page numbers start at zero.
  201.     """
  202.     
  203.     return self._page
  204.   
  205.   def setPage(self, pageNumber):
  206.     """
  207.     Sets the selected page, raising a ValueError if the page number is invalid.
  208.     
  209.     Arguments:
  210.       pageNumber - page number to be selected
  211.     """
  212.     
  213.     if pageNumber < 0 or pageNumber >= self.getPageCount():
  214.       raise ValueError("Invalid page number: %i" % pageNumber)
  215.     
  216.     if pageNumber != self._page:
  217.       self._page = pageNumber
  218.       self._forceRedraw = True
  219.       self.setMsg()
  220.   
  221.   def nextPage(self):
  222.     """
  223.     Increments the page number.
  224.     """
  225.     
  226.     self.setPage((self._page + 1) % len(self._pagePanels))
  227.   
  228.   def prevPage(self):
  229.     """
  230.     Decrements the page number.
  231.     """
  232.     
  233.     self.setPage((self._page - 1) % len(self._pagePanels))
  234.   
  235.   def isPaused(self):
  236.     """
  237.     True if the interface is paused, false otherwise.
  238.     """
  239.     
  240.     return self._isPaused
  241.   
  242.   def setPaused(self, isPause):
  243.     """
  244.     Sets the interface to be paused or unpaused.
  245.     """
  246.     
  247.     if isPause != self._isPaused:
  248.       self._isPaused = isPause
  249.       self._forceRedraw = True
  250.       self.setMsg()
  251.       
  252.       for panelImpl in self.getAllPanels():
  253.         panelImpl.setPaused(isPause)
  254.   
  255.   def getPanel(self, name):
  256.     """
  257.     Provides the panel with the given identifier. This returns None if no such
  258.     panel exists.
  259.     
  260.     Arguments:
  261.       name - name of the panel to be fetched
  262.     """
  263.     
  264.     for panelImpl in self.getAllPanels():
  265.       if panelImpl.getName() == name:
  266.         return panelImpl
  267.     
  268.     return None
  269.   
  270.   def getStickyPanels(self):
  271.     """
  272.     Provides the panels visibile at the top of every page.
  273.     """
  274.     
  275.     return list(self._stickyPanels)
  276.   
  277.   def getDisplayPanels(self, pageNumber = None, includeSticky = True):
  278.     """
  279.     Provides all panels belonging to a page and sticky content above it. This
  280.     is ordered they way they are presented (top to bottom) on the page.
  281.     
  282.     Arguments:
  283.       pageNumber    - page number of the panels to be returned, the current
  284.                       page if None
  285.       includeSticky - includes sticky panels in the results if true
  286.     """
  287.     
  288.     returnPage = self._page if pageNumber == None else pageNumber
  289.     
  290.     if self._pagePanels:
  291.       if includeSticky:
  292.         return self._stickyPanels + self._pagePanels[returnPage]
  293.       else: return list(self._pagePanels[returnPage])
  294.     else: return self._stickyPanels if includeSticky else []
  295.   
  296.   def getDaemonPanels(self):
  297.     """
  298.     Provides thread panels.
  299.     """
  300.     
  301.     threadPanels = []
  302.     for panelImpl in self.getAllPanels():
  303.       if isinstance(panelImpl, threading.Thread):
  304.         threadPanels.append(panelImpl)
  305.     
  306.     return threadPanels
  307.   
  308.   def getAllPanels(self):
  309.     """
  310.     Provides all panels in the interface.
  311.     """
  312.     
  313.     allPanels = list(self._stickyPanels)
  314.     
  315.     for page in self._pagePanels:
  316.       allPanels += list(page)
  317.     
  318.     return allPanels
  319.   
  320.   def redraw(self, force = True):
  321.     """
  322.     Redraws the displayed panel content.
  323.     
  324.     Arguments:
  325.       force - redraws reguardless of if it's needed if true, otherwise ignores
  326.               the request when there arne't changes to be displayed
  327.     """
  328.     
  329.     force |= self._forceRedraw
  330.     self._forceRedraw = False
  331.     
  332.     currentTime = time.time()
  333.     if CONFIG["features.refreshRate"] != 0:
  334.       if self._lastDrawn + CONFIG["features.refreshRate"] <= currentTime:
  335.         force = True
  336.     
  337.     displayPanels = self.getDisplayPanels()
  338.     
  339.     occupiedContent = 0
  340.     for panelImpl in displayPanels:
  341.       panelImpl.setTop(occupiedContent)
  342.       occupiedContent += panelImpl.getHeight()
  343.     
  344.     # apparently curses may cache display contents unless we explicitely
  345.     # request a redraw here...
  346.     # https://trac.torproject.org/projects/tor/ticket/2830#comment:9
  347.     if force: self._screen.clear()
  348.     
  349.     for panelImpl in displayPanels:
  350.       panelImpl.redraw(force)
  351.     
  352.     if force: self._lastDrawn = currentTime
  353.   
  354.   def requestRedraw(self):
  355.     """
  356.     Requests that all content is redrawn when the interface is next rendered.
  357.     """
  358.     
  359.     self._forceRedraw = True
  360.   
  361.   def getLastRedrawTime(self):
  362.     """
  363.     Provides the time when the content was last redrawn, zero if the content
  364.     has never been drawn.
  365.     """
  366.     
  367.     return self._lastDrawn
  368.   
  369.   def setMsg(self, msg = None, attr = None, redraw = False):
  370.     """
  371.     Sets the message displayed in the interfaces control panel. This uses our
  372.     default prompt if no arguments are provided.
  373.     
  374.     Arguments:
  375.       msg    - string to be displayed
  376.       attr   - attribute for the label, normal text if undefined
  377.       redraw - redraws right away if true, otherwise redraws when display
  378.                content is next normally drawn
  379.     """
  380.     
  381.     if msg == None:
  382.       msg = ""
  383.       
  384.       if attr == None:
  385.         if not self._isPaused:
  386.           msg = "page %i / %i - m: menu, p: pause, h: page help, q: quit" % (self._page + 1, len(self._pagePanels))
  387.           attr = curses.A_NORMAL
  388.         else:
  389.           msg = "Paused"
  390.           attr = curses.A_STANDOUT
  391.     
  392.     controlPanel = self.getPanel("msg")
  393.     controlPanel.setMessage(msg, attr)
  394.     
  395.     if redraw: controlPanel.redraw(True)
  396.     else: self._forceRedraw = True
  397.   
  398.   def getDataDirectory(self):
  399.     """
  400.     Provides the path where arm's resources are being placed. The path ends
  401.     with a slash and is created if it doesn't already exist.
  402.     """
  403.     
  404.     dataDir = os.path.expanduser(CONFIG["startup.dataDirectory"])
  405.     if not dataDir.endswith("/"): dataDir += "/"
  406.     if not os.path.exists(dataDir): os.makedirs(dataDir)
  407.     return dataDir
  408.   
  409.   def getTorManager(self):
  410.     """
  411.     Provides management utils for an arm managed tor instance.
  412.     """
  413.     
  414.     return self._torManager
  415.   
  416.   def isDone(self):
  417.     """
  418.     True if arm should be terminated, false otherwise.
  419.     """
  420.     
  421.     return self._isDone
  422.   
  423.   def quit(self):
  424.     """
  425.     Terminates arm after the input is processed. Optionally if we're connected
  426.     to a arm generated tor instance then this may check if that should be shut
  427.     down too.
  428.     """
  429.     
  430.     self._isDone = True
  431.     
  432.     # check if the torrc has a "ARM_SHUTDOWN" comment flag, if so then shut
  433.     # down the instance
  434.     
  435.     isShutdownFlagPresent = False
  436.     torrcContents = torConfig.getTorrc().getContents()
  437.     
  438.     if torrcContents:
  439.       for line in torrcContents:
  440.         if "# ARM_SHUTDOWN" in line:
  441.           isShutdownFlagPresent = True
  442.           break
  443.     
  444.     if isShutdownFlagPresent:
  445.       try: torTools.getConn().shutdown()
  446.       except IOError, exc: cli.popups.showMsg(str(exc), 3, curses.A_BOLD)
  447.     
  448.     if CONFIG["features.offerTorShutdownOnQuit"]:
  449.       conn = torTools.getConn()
  450.       
  451.       if self.getTorManager().isManaged(conn):
  452.         while True:
  453.           msg = "Shut down the Tor instance arm started (y/n)?"
  454.           confirmationKey = cli.popups.showMsg(msg, attr = curses.A_BOLD)
  455.           
  456.           if confirmationKey in (ord('y'), ord('Y')):
  457.             # attempts a graceful shutdown of tor, showing the issue if
  458.             # unsuccessful then continuing the shutdown
  459.             try: conn.shutdown()
  460.             except IOError, exc: cli.popups.showMsg(str(exc), 3, curses.A_BOLD)
  461.             
  462.             break
  463.           elif confirmationKey in (ord('n'), ord('N')):
  464.             break
  465.  
  466. class TorManager:
  467.   """
  468.   Bundle of utils for starting and manipulating an arm generated tor instance.
  469.   """
  470.   
  471.   def __init__(self, controller):
  472.     self._controller = controller
  473.   
  474.   def getTorrcPath(self):
  475.     """
  476.     Provides the path to a wizard generated torrc.
  477.     """
  478.     
  479.     return self._controller.getDataDirectory() + "torrc"
  480.   
  481.   def isTorrcAvailable(self):
  482.     """
  483.     True if a wizard generated torrc exists and the user has permissions to
  484.     run it, false otherwise.
  485.     """
  486.     
  487.     torrcLoc = self.getTorrcPath()
  488.     if os.path.exists(torrcLoc):
  489.       # If we aren't running as root and would be trying to bind to low ports
  490.       # then the startup will fail due to permissons. Attempts to check for
  491.       # this in the torrc. If unable to read the torrc then we probably
  492.       # wouldn't be able to use it anyway with our permissions.
  493.       
  494.       if os.getuid() != 0:
  495.         try:
  496.           return not torConfig.isRootNeeded(torrcLoc)
  497.         except IOError, exc:
  498.           log.log(log.INFO, "Failed to read torrc at '%s': %s" % (torrcLoc, exc))
  499.           return False
  500.       else: return True
  501.     
  502.     return False
  503.   
  504.   def isManaged(self, conn):
  505.     """
  506.     Returns true if the given tor instance is managed by us, false otherwise.
  507.     
  508.     Arguments:
  509.       conn - controller instance to be checked
  510.     """
  511.     
  512.     return conn.getInfo("config-file") == self.getTorrcPath()
  513.   
  514.   def startManagedInstance(self):
  515.     """
  516.     Starts a managed instance of tor, logging a warning if unsuccessful. This
  517.     returns True if successful and False otherwise.
  518.     """
  519.     
  520.     torrcLoc = self.getTorrcPath()
  521.     os.system("tor --quiet -f %s&" % torrcLoc)
  522.     startTime = time.time()
  523.     
  524.     # attempts to connect for five seconds (tor might or might not be
  525.     # immediately available)
  526.     raisedExc = None
  527.     
  528.     while time.time() - startTime < 5:
  529.       try:
  530.         self.connectManagedInstance()
  531.         return True
  532.       except IOError, exc:
  533.         raisedExc = exc
  534.         time.sleep(0.5)
  535.     
  536.     if raisedExc: log.log(log.WARN, str(raisedExc))
  537.     return False
  538.   
  539.   def connectManagedInstance(self):
  540.     """
  541.     Attempts to connect to a managed tor instance, raising an IOError if
  542.     unsuccessful.
  543.     """
  544.     
  545.     torctlConn, authType, authValue = TorCtl.preauth_connect(controlPort = int(CONFIG["wizard.default"]["Control"]))
  546.     
  547.     if not torctlConn:
  548.       msg = "Unable to start tor, try running \"tor -f %s\" to see the error output" % self.getTorrcPath()
  549.       raise IOError(msg)
  550.     
  551.     if authType == TorCtl.AUTH_TYPE.COOKIE:
  552.       try:
  553.         authCookieSize = os.path.getsize(authValue)
  554.         if authCookieSize != 32:
  555.           raise IOError("authentication cookie '%s' is the wrong size (%i bytes instead of 32)" % (authValue, authCookieSize))
  556.         
  557.         torctlConn.authenticate(authValue)
  558.         torTools.getConn().init(torctlConn)
  559.       except Exception, exc:
  560.         raise IOError("Unable to connect to Tor: %s" % exc)
  561.  
  562. def shutdownDaemons():
  563.   """
  564.   Stops and joins on worker threads.
  565.   """
  566.   
  567.   # prevents further worker threads from being spawned
  568.   torTools.NO_SPAWN = True
  569.   
  570.   # stops panel daemons
  571.   control = getController()
  572.   for panelImpl in control.getDaemonPanels(): panelImpl.stop()
  573.   for panelImpl in control.getDaemonPanels(): panelImpl.join()
  574.   
  575.   # joins on TorCtl event thread
  576.   torTools.getConn().close()
  577.   
  578.   # joins on utility daemon threads - this might take a moment since the
  579.   # internal threadpools being joined might be sleeping
  580.   hostnames.stop()
  581.   resourceTrackers = sysTools.RESOURCE_TRACKERS.values()
  582.   resolver = connections.getResolver("tor") if connections.isResolverAlive("tor") else None
  583.   for tracker in resourceTrackers: tracker.stop()
  584.   if resolver: resolver.stop()  # sets halt flag (returning immediately)
  585.   for tracker in resourceTrackers: tracker.join()
  586.   if resolver: resolver.join()  # joins on halted resolver
  587.  
  588. def heartbeatCheck(isUnresponsive):
  589.   """
  590.   Logs if its been ten seconds since the last BW event.
  591.   
  592.   Arguments:
  593.     isUnresponsive - flag for if we've indicated to be responsive or not
  594.   """
  595.   
  596.   conn = torTools.getConn()
  597.   lastHeartbeat = conn.getHeartbeat()
  598.   if conn.isAlive() and "BW" in conn.getControllerEvents():
  599.     if not isUnresponsive and (time.time() - lastHeartbeat) >= 10:
  600.       isUnresponsive = True
  601.       log.log(log.NOTICE, "Relay unresponsive (last heartbeat: %s)" % time.ctime(lastHeartbeat))
  602.     elif isUnresponsive and (time.time() - lastHeartbeat) < 10:
  603.       # really shouldn't happen (meant Tor froze for a bit)
  604.       isUnresponsive = False
  605.       log.log(log.NOTICE, "Relay resumed")
  606.   
  607.   return isUnresponsive
  608.  
  609. def connResetListener(conn, eventType):
  610.   """
  611.   Pauses connection resolution when tor's shut down, and resumes with the new
  612.   pid if started again.
  613.   """
  614.   
  615.   if connections.isResolverAlive("tor"):
  616.     resolver = connections.getResolver("tor")
  617.     resolver.setPaused(eventType == torTools.State.CLOSED)
  618.     
  619.     if eventType in (torTools.State.INIT, torTools.State.RESET):
  620.       # Reload the torrc contents. If the torrc panel is present then it will
  621.       # do this instead since it wants to do validation and redraw _after_ the
  622.       # new contents are loaded.
  623.       
  624.       if getController().getPanel("torrc") == None:
  625.         torConfig.getTorrc().load(True)
  626.       
  627.       torPid = conn.getMyPid()
  628.       
  629.       if torPid and torPid != resolver.getPid():
  630.         resolver.setPid(torPid)
  631.  
  632. def startTorMonitor(startTime):
  633.   """
  634.   Initializes the interface and starts the main draw loop.
  635.   
  636.   Arguments:
  637.     startTime - unix time for when arm was started
  638.   """
  639.   
  640.   # initializes interface configs
  641.   config = conf.getConfig("arm")
  642.   config.update(CONFIG, {
  643.     "features.redrawRate": 1,
  644.     "features.refreshRate": 0})
  645.   
  646.   cli.graphing.graphPanel.loadConfig(config)
  647.   cli.connections.connEntry.loadConfig(config)
  648.   cli.wizard.loadConfig(config)
  649.   
  650.   # attempts to fetch the tor pid, warning if unsuccessful (this is needed for
  651.   # checking its resource usage, among other things)
  652.   conn = torTools.getConn()
  653.   torPid = conn.getMyPid()
  654.   
  655.   if not torPid and conn.isAlive():
  656.     msg = "Unable to determine Tor's pid. Some information, like its resource usage will be unavailable."
  657.     log.log(CONFIG["log.unknownTorPid"], msg)
  658.   
  659.   # adds events needed for arm functionality to the torTools REQ_EVENTS
  660.   # mapping (they're then included with any setControllerEvents call, and log
  661.   # a more helpful error if unavailable)
  662.   
  663.   torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function"
  664.   
  665.   if not CONFIG["startup.blindModeEnabled"]:
  666.     # The DisableDebuggerAttachment will prevent our connection panel from really
  667.     # functioning. It'll have circuits, but little else. If this is the case then
  668.     # notify the user and tell them what they can do to fix it.
  669.     
  670.     if conn.getOption("DisableDebuggerAttachment") == "1":
  671.       log.log(log.NOTICE, "Tor is preventing system utilities like netstat and lsof from working. This means that arm can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313")
  672.       connections.getResolver("tor").setPaused(True)
  673.     else:
  674.       torTools.REQ_EVENTS["CIRC"] = "may cause issues in identifying client connections"
  675.       
  676.       # Configures connection resoultions. This is paused/unpaused according to
  677.       # if Tor's connected or not.
  678.       conn.addStatusListener(connResetListener)
  679.       
  680.       if torPid:
  681.         # use the tor pid to help narrow connection results
  682.         torCmdName = sysTools.getProcessName(torPid, "tor")
  683.         connections.getResolver(torCmdName, torPid, "tor")
  684.       else:
  685.         # constructs singleton resolver and, if tor isn't connected, initizes
  686.         # it to be paused
  687.         connections.getResolver("tor").setPaused(not conn.isAlive())
  688.       
  689.       # hack to display a better (arm specific) notice if all resolvers fail
  690.       connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")."
  691.   
  692.   # provides a notice about any event types tor supports but arm doesn't
  693.   missingEventTypes = cli.logPanel.getMissingEventTypes()
  694.   
  695.   if missingEventTypes:
  696.     pluralLabel = "s" if len(missingEventTypes) > 1 else ""
  697.     log.log(CONFIG["log.torEventTypeUnrecognized"], "arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)" % (pluralLabel, ", ".join(missingEventTypes)))
  698.   
  699.   try:
  700.     curses.wrapper(drawTorMonitor, startTime)
  701.   except KeyboardInterrupt:
  702.     # Skip printing stack trace in case of keyboard interrupt. The
  703.     # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw
  704.     # (which would leave the user's terminal in a screwed up state). There is
  705.     # still a tiny timing issue here (after the exception but before the flag
  706.     # is set) but I've never seen it happen in practice.
  707.     
  708.     panel.HALT_ACTIVITY = True
  709.     shutdownDaemons()
  710.  
  711. def drawTorMonitor(stdscr, startTime):
  712.   """
  713.   Main draw loop context.
  714.   
  715.   Arguments:
  716.     stdscr    - curses window
  717.     startTime - unix time for when arm was started
  718.   """
  719.   
  720.   initController(stdscr, startTime)
  721.   control = getController()
  722.   
  723.   # provides notice about any unused config keys
  724.   for key in conf.getConfig("arm").getUnusedKeys():
  725.     log.log(CONFIG["log.configEntryUndefined"], "Unused configuration entry: %s" % key)
  726.   
  727.   # tells daemon panels to start
  728.   for panelImpl in control.getDaemonPanels(): panelImpl.start()
  729.   
  730.   # allows for background transparency
  731.   try: curses.use_default_colors()
  732.   except curses.error: pass
  733.   
  734.   # makes the cursor invisible
  735.   try: curses.curs_set(0)
  736.   except curses.error: pass
  737.   
  738.   # logs the initialization time
  739.   msg = "arm started (initialization took %0.3f seconds)" % (time.time() - startTime)
  740.   log.log(CONFIG["log.startTime"], msg)
  741.   
  742.   # main draw loop
  743.   overrideKey = None     # uses this rather than waiting on user input
  744.   isUnresponsive = False # flag for heartbeat responsiveness check
  745.   if not torTools.getConn().isAlive(): overrideKey = ord('w') # shows wizard
  746.   
  747.   while not control.isDone():
  748.     displayPanels = control.getDisplayPanels()
  749.     isUnresponsive = heartbeatCheck(isUnresponsive)
  750.     
  751.     # sets panel visability
  752.     for panelImpl in control.getAllPanels():
  753.       panelImpl.setVisible(panelImpl in displayPanels)
  754.     
  755.     # redraws the interface if it's needed
  756.     control.redraw(False)
  757.     stdscr.refresh()
  758.     
  759.     # wait for user keyboard input until timeout, unless an override was set
  760.     if overrideKey:
  761.       key, overrideKey = overrideKey, None
  762.     else:
  763.       curses.halfdelay(CONFIG["features.redrawRate"] * 10)
  764.       key = stdscr.getch()
  765.     
  766.     if key == curses.KEY_RIGHT:
  767.       control.nextPage()
  768.     elif key == curses.KEY_LEFT:
  769.       control.prevPage()
  770.     elif key == ord('p') or key == ord('P'):
  771.       control.setPaused(not control.isPaused())
  772.     elif key == ord('m') or key == ord('M'):
  773.       cli.menu.menu.showMenu()
  774.     elif key == ord('q') or key == ord('Q'):
  775.       # provides prompt to confirm that arm should exit
  776.       if CONFIG["features.confirmQuit"]:
  777.         msg = "Are you sure (q again to confirm)?"
  778.         confirmationKey = cli.popups.showMsg(msg, attr = curses.A_BOLD)
  779.         quitConfirmed = confirmationKey in (ord('q'), ord('Q'))
  780.       else: quitConfirmed = True
  781.       
  782.       if quitConfirmed: control.quit()
  783.     elif key == ord('x') or key == ord('X'):
  784.       # provides prompt to confirm that arm should issue a sighup
  785.       msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
  786.       confirmationKey = cli.popups.showMsg(msg, attr = curses.A_BOLD)
  787.       
  788.       if confirmationKey in (ord('x'), ord('X')):
  789.         try: torTools.getConn().reload()
  790.         except IOError, exc:
  791.           log.log(log.ERR, "Error detected when reloading tor: %s" % sysTools.getFileErrorMsg(exc))
  792.     elif key == ord('h') or key == ord('H'):
  793.       overrideKey = cli.popups.showHelpPopup()
  794.     elif key == ord('w') or key == ord('W'):
  795.       cli.wizard.showWizard()
  796.     elif key == ord('l') - 96:
  797.       # force redraw when ctrl+l is pressed
  798.       control.redraw(True)
  799.     else:
  800.       for panelImpl in displayPanels:
  801.         isKeystrokeConsumed = panelImpl.handleKey(key)
  802.         if isKeystrokeConsumed: break
  803.   
  804.   shutdownDaemons()
  805.  
  806.