Пример #1
0
    def setSortOrder(self, ordering=None):
        """
    Sets the connection attributes we're sorting by and resorts the contents.
    
    Arguments:
      ordering - new ordering, if undefined then this resorts with the last
                 set ordering
    """

        self.valsLock.acquire()

        if ordering:
            armConf = conf.get_config("arm")

            ordering_keys = [
                entries.SortAttr.keys()[entries.SortAttr.index_of(v)]
                for v in ordering
            ]
            armConf.set("features.connection.order", ", ".join(ordering_keys))

        self._entries.sort(key=lambda i: (i.getSortValues(
            CONFIG["features.connection.order"], self.getListingType())))

        self._entryLines = []
        for entry in self._entries:
            self._entryLines += entry.getLines()
        self.valsLock.release()
Пример #2
0
def load_fingerprint_changes():
    """
  Loads information about prior fingerprint changes we've persisted. This
  provides a dictionary of the form...

    (address, or_port) => {fingerprint: published_timestamp...}
  """

    log.debug("Loading fingerprint changes...")
    config = conf.get_config('fingerprint_changes')

    try:
        config.load(FINGERPRINT_CHANGES_FILE)
        fingerprint_changes = {}

        for key in config.keys():
            address, or_port = key.split(':', 1)

            for value in config.get(key, []):
                fingerprint, published = value.split(':', 1)
                fingerprint_changes.setdefault(
                    (address, int(or_port)),
                    {})[fingerprint] = float(published)

        log.debug("  information for %i relays found" %
                  len(fingerprint_changes))
        return fingerprint_changes
    except IOError as exc:
        log.debug("  unable to read '%s': %s" %
                  (FINGERPRINT_CHANGES_FILE, exc))
        return {}
Пример #3
0
    def startTor_with_config(self, path="/home/reece/dev/soc2013/tor/torrc"):
        self.config = conf.get_config("servertor")
        self.config = config.load(path)

        sys.stdout.write("Starting tor...")
        stem.process.launch_tor(torrc_path=path)  #, completion_percent=100)
        sys.stdout.write("done\n\n")
        print config._contents
Пример #4
0
	def startTor_with_config(self, path="/home/reece/dev/soc2013/tor/torrc"):
		self.config = conf.get_config("servertor")
		self.config = config.load(path)

		sys.stdout.write("Starting tor...")
		stem.process.launch_tor(torrc_path=path)#, completion_percent=100)
		sys.stdout.write("done\n\n")
		print config._contents
Пример #5
0
  def _load_config_options(self):
    """
    Fetches the configuration options available from tor or nyx.
    """

    self.conf_contents = []
    self.conf_important_contents = []

    if self.config_type == State.TOR:
      controller, config_option_lines = tor_controller(), []
      custom_options = tor_config.get_custom_options()
      config_option_query = controller.get_info('config/names', None)

      if config_option_query:
        config_option_lines = config_option_query.strip().split('\n')

      for line in config_option_lines:
        # lines are of the form "<option> <type>[ <documentation>]", like:
        # UseEntryGuards Boolean
        # documentation is aparently only in older versions (for instance,
        # 0.2.1.25)

        line_comp = line.strip().split(' ')
        conf_option, conf_type = line_comp[0], line_comp[1]

        # skips private and virtual entries if not configured to show them

        if not CONFIG['features.config.state.showPrivateOptions'] and conf_option.startswith('__'):
          continue
        elif not CONFIG['features.config.state.showVirtualOptions'] and conf_type == 'Virtual':
          continue

        self.conf_contents.append(ConfigEntry(conf_option, conf_type, conf_option not in custom_options))

    elif self.config_type == State.NYX:
      # loaded via the conf utility

      nyx_config = conf.get_config('nyx')

      for key in nyx_config.keys():
        pass  # TODO: implement

    # mirror listing with only the important configuration options

    self.conf_important_contents = []

    for entry in self.conf_contents:
      if tor_config.is_important(entry.get(Field.OPTION)):
        self.conf_important_contents.append(entry)

    # if there aren't any important options then show everything

    if not self.conf_important_contents:
      self.conf_important_contents = self.conf_contents

    self.set_sort_order()  # initial sorting of the contents
Пример #6
0
    def _loadConfigOptions(self):
        """
    Fetches the configuration options available from tor or arm.
    """

        self.confContents = []
        self.confImportantContents = []

        if self.configType == State.TOR:
            conn, configOptionLines = torTools.getConn(), []
            customOptions = torConfig.getCustomOptions()
            configOptionQuery = conn.getInfo("config/names", None)

            if configOptionQuery:
                configOptionLines = configOptionQuery.strip().split("\n")

            for line in configOptionLines:
                # lines are of the form "<option> <type>[ <documentation>]", like:
                # UseEntryGuards Boolean
                # documentation is aparently only in older versions (for instance,
                # 0.2.1.25)
                lineComp = line.strip().split(" ")
                confOption, confType = lineComp[0], lineComp[1]

                # skips private and virtual entries if not configured to show them
                if not CONFIG["features.config.state.showPrivateOptions"] and confOption.startswith("__"):
                    continue
                elif not CONFIG["features.config.state.showVirtualOptions"] and confType == "Virtual":
                    continue

                self.confContents.append(ConfigEntry(confOption, confType, not confOption in customOptions))
        elif self.configType == State.ARM:
            # loaded via the conf utility
            armConf = conf.get_config("arm")
            for key in armConf.keys():
                pass  # TODO: implement

        # mirror listing with only the important configuration options
        self.confImportantContents = []
        for entry in self.confContents:
            if torConfig.isImportant(entry.get(Field.OPTION)):
                self.confImportantContents.append(entry)

        # if there aren't any important options then show everything
        if not self.confImportantContents:
            self.confImportantContents = self.confContents

        self.setSortOrder()  # initial sorting of the contents
Пример #7
0
 def _loadConfigOptions(self):
   """
   Fetches the configuration options available from tor or arm.
   """
   
   self.confContents = []
   self.confImportantContents = []
   
   if self.configType == State.TOR:
     conn, configOptionLines = torTools.getConn(), []
     customOptions = torConfig.getCustomOptions()
     configOptionQuery = conn.getInfo("config/names", None)
     
     if configOptionQuery:
       configOptionLines = configOptionQuery.strip().split("\n")
     
     for line in configOptionLines:
       # lines are of the form "<option> <type>[ <documentation>]", like:
       # UseEntryGuards Boolean
       # documentation is aparently only in older versions (for instance,
       # 0.2.1.25)
       lineComp = line.strip().split(" ")
       confOption, confType = lineComp[0], lineComp[1]
       
       # skips private and virtual entries if not configured to show them
       if not CONFIG["features.config.state.showPrivateOptions"] and confOption.startswith("__"):
         continue
       elif not CONFIG["features.config.state.showVirtualOptions"] and confType == "Virtual":
         continue
       
       self.confContents.append(ConfigEntry(confOption, confType, not confOption in customOptions))
   elif self.configType == State.ARM:
     # loaded via the conf utility
     armConf = conf.get_config("arm")
     for key in armConf.keys():
       pass # TODO: implement
   
   # mirror listing with only the important configuration options
   self.confImportantContents = []
   for entry in self.confContents:
     if torConfig.isImportant(entry.get(Field.OPTION)):
       self.confImportantContents.append(entry)
   
   # if there aren't any important options then show everything
   if not self.confImportantContents:
     self.confImportantContents = self.confContents
   
   self.setSortOrder() # initial sorting of the contents
Пример #8
0
def set_color_override(color = None):
  """
  Overwrites all requests for color with the given color instead.

  :param str color: color to override all requests with, **None** if color
    requests shouldn't be overwritten

  :raises: **ValueError** if the color name is invalid
  """

  nyx_config = conf.get_config('nyx')

  if color is None:
    nyx_config.set('features.color_override', 'none')
  elif color in COLOR_LIST.keys():
    nyx_config.set('features.color_override', color)
  else:
    raise ValueError(msg('usage.unable_to_set_color_override', color = color))
Пример #9
0
def save_fingerprint_changes(fingerprint_changes):
    log.debug("Saving fingerprint changes for %i relays" %
              len(fingerprint_changes))
    config = conf.get_config('fingerprint_changes')
    config.clear()

    for address, or_port in fingerprint_changes:
        for fingerprint, published in fingerprint_changes[(address,
                                                           or_port)].items():
            config.set('%s:%s' % (address, or_port),
                       '%s:%s' % (fingerprint, published),
                       overwrite=False)

    try:
        config.save(FINGERPRINT_CHANGES_FILE)
    except IOError as exc:
        log.debug("  unable to save '%s': %s" %
                  (FINGERPRINT_CHANGES_FILE, exc))
Пример #10
0
 def setListingType(self, listingType):
   """
   Sets the priority information presented by the panel.
   
   Arguments:
     listingType - Listing instance for the primary information to be shown
   """
   
   if self.getListingType() == listingType: return
   
   self.valsLock.acquire()
   
   armConf = conf.get_config("arm")
   armConf.set("features.connection.listingType", Listing.keys()[Listing.index_of(listingType)])
   
   # if we're sorting by the listing then we need to resort
   if entries.SortAttr.LISTING in CONFIG["features.connection.order"]:
     self.setSortOrder()
   
   self.valsLock.release()
Пример #11
0
    def setListingType(self, listingType):
        """
    Sets the priority information presented by the panel.
    
    Arguments:
      listingType - Listing instance for the primary information to be shown
    """

        if self.getListingType() == listingType: return

        self.valsLock.acquire()

        armConf = conf.get_config("arm")
        armConf.set("features.connection.listingType",
                    Listing.keys()[Listing.index_of(listingType)])

        # if we're sorting by the listing then we need to resort
        if entries.SortAttr.LISTING in CONFIG["features.connection.order"]:
            self.setSortOrder()

        self.valsLock.release()
Пример #12
0
 def setSortOrder(self, ordering = None):
   """
   Sets the connection attributes we're sorting by and resorts the contents.
   
   Arguments:
     ordering - new ordering, if undefined then this resorts with the last
                set ordering
   """
   
   self.valsLock.acquire()
   
   if ordering:
     armConf = conf.get_config("arm")
     
     ordering_keys = [entries.SortAttr.keys()[entries.SortAttr.index_of(v)] for v in ordering]
     armConf.set("features.connection.order", ", ".join(ordering_keys))
   
   self._entries.sort(key=lambda i: (i.getSortValues(CONFIG["features.connection.order"], self.getListingType())))
   
   self._entryLines = []
   for entry in self._entries:
     self._entryLines += entry.getLines()
   self.valsLock.release()
Пример #13
0
def is_notification_suppressed(fingerprint_changes):
    """
  Check to see if we've already notified for all these endpoints today. No
  point in causing too much noise.
  """

    is_all_suppressed = True
    log.debug("Checking if notification should be suppressed...")
    last_notified_config = conf.get_config('last_notified')

    for address, or_port, _ in fingerprint_changes:
        key = '%s:%s' % (address, or_port)
        suppression_time = ONE_DAY - (int(time.time()) -
                                      last_notified_config.get(key, 0))

        if suppression_time < 0:
            log.debug("* notification for %s isn't suppressed" % key)
            is_all_suppressed = False
        else:
            log.debug(
                "* we already notified for %s recently, suppressed for %i hours"
                % (key, suppression_time / 3600))

    return is_all_suppressed
Пример #14
0
def drawTorMonitor(stdscr, startTime):
    """
  Main draw loop context.
  
  Arguments:
    stdscr    - curses window
    startTime - unix time for when arm was started
  """

    initController(stdscr, startTime)
    control = getController()

    # provides notice about any unused config keys
    for key in conf.get_config("arm").unused_keys():
        log.notice("Unused configuration entry: %s" % key)

    # tells daemon panels to start
    for panelImpl in control.getDaemonPanels():
        panelImpl.start()

    # allows for background transparency
    try:
        curses.use_default_colors()
    except curses.error:
        pass

    # makes the cursor invisible
    try:
        curses.curs_set(0)
    except curses.error:
        pass

    # logs the initialization time
    log.info("arm started (initialization took %0.3f seconds)" % (time.time() - startTime))

    # main draw loop
    overrideKey = None  # uses this rather than waiting on user input
    isUnresponsive = False  # flag for heartbeat responsiveness check

    while not control.isDone():
        displayPanels = control.getDisplayPanels()
        isUnresponsive = heartbeatCheck(isUnresponsive)

        # sets panel visability
        for panelImpl in control.getAllPanels():
            panelImpl.setVisible(panelImpl in displayPanels)

        # redraws the interface if it's needed
        control.redraw(False)
        stdscr.refresh()

        # wait for user keyboard input until timeout, unless an override was set
        if overrideKey:
            key, overrideKey = overrideKey, None
        else:
            curses.halfdelay(CONFIG["features.redrawRate"] * 10)
            key = stdscr.getch()

        if key == curses.KEY_RIGHT:
            control.nextPage()
        elif key == curses.KEY_LEFT:
            control.prevPage()
        elif key == ord("p") or key == ord("P"):
            control.setPaused(not control.isPaused())
        elif key == ord("m") or key == ord("M"):
            cli.menu.menu.showMenu()
        elif key == ord("q") or key == ord("Q"):
            # provides prompt to confirm that arm should exit
            if CONFIG["features.confirmQuit"]:
                msg = "Are you sure (q again to confirm)?"
                confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD)
                quitConfirmed = confirmationKey in (ord("q"), ord("Q"))
            else:
                quitConfirmed = True

            if quitConfirmed:
                control.quit()
        elif key == ord("x") or key == ord("X"):
            # provides prompt to confirm that arm should issue a sighup
            msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
            confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD)

            if confirmationKey in (ord("x"), ord("X")):
                try:
                    torTools.getConn().reload()
                except IOError, exc:
                    log.error("Error detected when reloading tor: %s" % sysTools.getFileErrorMsg(exc))
        elif key == ord("h") or key == ord("H"):
            overrideKey = cli.popups.showHelpPopup()
Пример #15
0
def drawTorMonitor(stdscr, startTime):
    """
  Main draw loop context.
  
  Arguments:
    stdscr    - curses window
    startTime - unix time for when arm was started
  """

    initController(stdscr, startTime)
    control = getController()

    # provides notice about any unused config keys
    for key in conf.get_config("arm").unused_keys():
        log.notice("Unused configuration entry: %s" % key)

    # tells daemon panels to start
    for panelImpl in control.getDaemonPanels():
        panelImpl.start()

    # allows for background transparency
    try:
        curses.use_default_colors()
    except curses.error:
        pass

    # makes the cursor invisible
    try:
        curses.curs_set(0)
    except curses.error:
        pass

    # logs the initialization time
    log.info("arm started (initialization took %0.3f seconds)" %
             (time.time() - startTime))

    # main draw loop
    overrideKey = None  # uses this rather than waiting on user input
    isUnresponsive = False  # flag for heartbeat responsiveness check

    while not control.isDone():
        displayPanels = control.getDisplayPanels()
        isUnresponsive = heartbeatCheck(isUnresponsive)

        # sets panel visability
        for panelImpl in control.getAllPanels():
            panelImpl.setVisible(panelImpl in displayPanels)

        # redraws the interface if it's needed
        control.redraw(False)
        stdscr.refresh()

        # wait for user keyboard input until timeout, unless an override was set
        if overrideKey:
            key, overrideKey = overrideKey, None
        else:
            curses.halfdelay(CONFIG["features.redrawRate"] * 10)
            key = stdscr.getch()

        if key == curses.KEY_RIGHT:
            control.nextPage()
        elif key == curses.KEY_LEFT:
            control.prevPage()
        elif key == ord('p') or key == ord('P'):
            control.setPaused(not control.isPaused())
        elif key == ord('m') or key == ord('M'):
            cli.menu.menu.showMenu()
        elif key == ord('q') or key == ord('Q'):
            # provides prompt to confirm that arm should exit
            if CONFIG["features.confirmQuit"]:
                msg = "Are you sure (q again to confirm)?"
                confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD)
                quitConfirmed = confirmationKey in (ord('q'), ord('Q'))
            else:
                quitConfirmed = True

            if quitConfirmed: control.quit()
        elif key == ord('x') or key == ord('X'):
            # provides prompt to confirm that arm should issue a sighup
            msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
            confirmationKey = cli.popups.showMsg(msg, attr=curses.A_BOLD)

            if confirmationKey in (ord('x'), ord('X')):
                try:
                    torTools.getConn().reload()
                except IOError, exc:
                    log.error("Error detected when reloading tor: %s" %
                              sysTools.getFileErrorMsg(exc))
        elif key == ord('h') or key == ord('H'):
            overrideKey = cli.popups.showHelpPopup()
Пример #16
0
def start_nyx():
  """
  Main draw loop context.
  """

  global NYX_CONTROLLER

  NYX_CONTROLLER = Controller()
  control = get_controller()

  if not CONFIG['features.acsSupport']:
    nyx.curses.disable_acs()

  # provides notice about any unused config keys

  for key in sorted(conf.get_config('nyx').unused_keys()):
    if not key.startswith('msg.') and not key.startswith('dedup.'):
      log.notice('Unused configuration entry: %s' % key)

  # tells daemon panels to start

  for panel_impl in control.get_daemon_panels():
    panel_impl.start()

  # logs the initialization time

  log.info('nyx started (initialization took %0.3f seconds)' % (time.time() - CONFIG['start_time']))

  # main draw loop

  override_key = None      # uses this rather than waiting on user input

  while not control.quit_signal:
    display_panels = [control.header_panel()] + control.get_display_panels()

    # sets panel visability

    for panel_impl in control.get_all_panels():
      panel_impl.set_visible(panel_impl in display_panels)

    # redraws the interface if it's needed

    control.redraw(False)

    with nyx.curses.raw_screen() as stdscr:
      stdscr.refresh()

    # wait for user keyboard input until timeout, unless an override was set

    if override_key:
      key, override_key = override_key, None
    else:
      key = nyx.curses.key_input(CONFIG['features.redrawRate'])

    if key.match('right'):
      control.next_page()
    elif key.match('left'):
      control.prev_page()
    elif key.match('p'):
      control.set_paused(not control.is_paused())
    elif key.match('m'):
      nyx.menu.show_menu()
    elif key.match('q'):
      # provides prompt to confirm that nyx should exit

      if CONFIG['features.confirmQuit']:
        msg = 'Are you sure (q again to confirm)?'
        confirmation_key = show_message(msg, BOLD, max_wait = 30)
        quit_confirmed = confirmation_key.match('q')
      else:
        quit_confirmed = True

      if quit_confirmed:
        break
    elif key.match('x'):
      # provides prompt to confirm that nyx should issue a sighup

      msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
      confirmation_key = show_message(msg, BOLD, max_wait = 30)

      if confirmation_key in (ord('x'), ord('X')):
        try:
          tor_controller().signal(stem.Signal.RELOAD)
        except IOError as exc:
          log.error('Error detected when reloading tor: %s' % exc.strerror)
    elif key.match('h'):
      override_key = nyx.popups.show_help()
    elif key == ord('l') - 96:
      # force redraw when ctrl+l is pressed
      control.redraw(True)
    else:
      for panel_impl in display_panels:
        for keybinding in panel_impl.key_handlers():
          keybinding.handle(key)
Пример #17
0
  def draw(self, width, height):
    with self._vals_lock:
      # If true, we assume that the cached value in self._last_content_height is
      # still accurate, and stop drawing when there's nothing more to display.
      # Otherwise the self._last_content_height is suspect, and we'll process all
      # the content to check if it's right (and redraw again with the corrected
      # height if not).

      trust_last_content_height = self._last_content_height_args == (width, height)

      # restricts scroll location to valid bounds

      self.scroll = max(0, min(self.scroll, self._last_content_height - height + 1))

      rendered_contents, corrections, conf_location = None, {}, None

      if self.config_type == Config.TORRC:
        loaded_torrc = tor_config.get_torrc()

        with loaded_torrc.get_lock():
          conf_location = loaded_torrc.get_config_location()

          if not loaded_torrc.is_loaded():
            rendered_contents = ['### Unable to load the torrc ###']
          else:
            rendered_contents = loaded_torrc.get_display_contents(self.strip_comments)

            # constructs a mapping of line numbers to the issue on it

            corrections = dict((line_number, (issue, msg)) for line_number, issue, msg in loaded_torrc.get_corrections())
      else:
        loaded_nyxrc = conf.get_config('nyx')
        conf_location = loaded_nyxrc._path
        rendered_contents = list(loaded_nyxrc._raw_contents)

      # offset to make room for the line numbers

      line_number_offset = 0

      if self.show_line_num:
        if len(rendered_contents) == 0:
          line_number_offset = 2
        else:
          line_number_offset = int(math.log10(len(rendered_contents))) + 2

      # draws left-hand scroll bar if content's longer than the height

      scroll_offset = 0

      if CONFIG['features.config.file.showScrollbars'] and self._last_content_height > height - 1:
        scroll_offset = 3
        self.add_scroll_bar(self.scroll, self.scroll + height - 1, self._last_content_height, 1)

      display_line = -self.scroll + 1  # line we're drawing on

      # draws the top label

      if self.is_title_visible():
        source_label = 'Tor' if self.config_type == Config.TORRC else 'Nyx'
        location_label = ' (%s)' % conf_location if conf_location else ''
        self.addstr(0, 0, '%s Configuration File%s:' % (source_label, location_label), curses.A_STANDOUT)

      is_multiline = False  # true if we're in the middle of a multiline torrc entry

      for line_number in range(0, len(rendered_contents)):
        line_text = rendered_contents[line_number]
        line_text = line_text.rstrip()  # remove ending whitespace

        # blank lines are hidden when stripping comments

        if self.strip_comments and not line_text:
          continue

        # splits the line into its component (msg, format) tuples

        line_comp = {
          'option': ['', (curses.A_BOLD, 'green')],
          'argument': ['', (curses.A_BOLD, 'cyan')],
          'correction': ['', (curses.A_BOLD, 'cyan')],
          'comment': ['', ('white',)],
        }

        # parses the comment

        comment_index = line_text.find('#')

        if comment_index != -1:
          line_comp['comment'][0] = line_text[comment_index:]
          line_text = line_text[:comment_index]

        # splits the option and argument, preserving any whitespace around them

        stripped_line = line_text.strip()
        option_index = stripped_line.find(' ')

        if is_multiline:
          # part of a multiline entry started on a previous line so everything
          # is part of the argument
          line_comp['argument'][0] = line_text
        elif option_index == -1:
          # no argument provided
          line_comp['option'][0] = line_text
        else:
          option_text = stripped_line[:option_index]
          option_end = line_text.find(option_text) + len(option_text)
          line_comp['option'][0] = line_text[:option_end]
          line_comp['argument'][0] = line_text[option_end:]

        # flags following lines as belonging to this multiline entry if it ends
        # with a slash

        if stripped_line:
          is_multiline = stripped_line.endswith('\\')

        # gets the correction

        if line_number in corrections:
          line_issue, line_issue_msg = corrections[line_number]

          if line_issue in (tor_config.ValidationError.DUPLICATE, tor_config.ValidationError.IS_DEFAULT):
            line_comp['option'][1] = (curses.A_BOLD, 'blue')
            line_comp['argument'][1] = (curses.A_BOLD, 'blue')
          elif line_issue == tor_config.ValidationError.MISMATCH:
            line_comp['argument'][1] = (curses.A_BOLD, 'red')
            line_comp['correction'][0] = ' (%s)' % line_issue_msg
          else:
            # For some types of configs the correction field is simply used to
            # provide extra data (for instance, the type for tor state fields).

            line_comp['correction'][0] = ' (%s)' % line_issue_msg
            line_comp['correction'][1] = (curses.A_BOLD, 'magenta')

        # draws the line number

        if self.show_line_num and display_line < height and display_line >= 1:
          line_number_str = ('%%%ii' % (line_number_offset - 1)) % (line_number + 1)
          self.addstr(display_line, scroll_offset, line_number_str, curses.A_BOLD, 'yellow')

        # draws the rest of the components with line wrap

        cursor_location, line_offset = line_number_offset + scroll_offset, 0
        max_lines_per_entry = CONFIG['features.config.file.max_lines_per_entry']
        display_queue = [line_comp[entry] for entry in ('option', 'argument', 'correction', 'comment')]

        while display_queue:
          msg, format = display_queue.pop(0)

          max_msg_size, include_break = width - cursor_location, False

          if len(msg) >= max_msg_size:
            # message is too long - break it up

            if line_offset == max_lines_per_entry - 1:
              msg = str_tools.crop(msg, max_msg_size)
            else:
              include_break = True
              msg, remainder = str_tools.crop(msg, max_msg_size, 4, 4, str_tools.Ending.HYPHEN, True)
              display_queue.insert(0, (remainder.strip(), format))

          draw_line = display_line + line_offset

          if msg and draw_line < height and draw_line >= 1:
            self.addstr(draw_line, cursor_location, msg, *format)

          # If we're done, and have added content to this line, then start
          # further content on the next line.

          cursor_location += len(msg)
          include_break |= not display_queue and cursor_location != line_number_offset + scroll_offset

          if include_break:
            line_offset += 1
            cursor_location = line_number_offset + scroll_offset

        display_line += max(line_offset, 1)

        if trust_last_content_height and display_line >= height:
          break

      if not trust_last_content_height:
        self._last_content_height_args = (width, height)
        new_content_height = display_line + self.scroll - 1

        if self._last_content_height != new_content_height:
          self._last_content_height = new_content_height
          self.redraw(True)
Пример #18
0
  "torrc.label.time.hour": [],
  "torrc.label.time.day": [],
  "torrc.label.time.week": [],
}, conf_handler)

def general_conf_handler(config, key):
  value = config.get(key)
  
  if key.startswith("config.summary."):
    # we'll look for summary keys with a lowercase config name
    CONFIG[key.lower()] = value
  elif key.startswith("torrc.label.") and value:
    # all the torrc.label.* values are comma separated lists
    return [entry.strip() for entry in value[0].split(",")]

conf.get_config("arm").add_listener(general_conf_handler, backfill = True)

# enums and values for numeric torrc entries
ValueType = enum.Enum("UNRECOGNIZED", "SIZE", "TIME")
SIZE_MULT = {"b": 1, "kb": 1024, "mb": 1048576, "gb": 1073741824, "tb": 1099511627776}
TIME_MULT = {"sec": 1, "min": 60, "hour": 3600, "day": 86400, "week": 604800}

# enums for issues found during torrc validation:
# DUPLICATE  - entry is ignored due to being a duplicate
# MISMATCH   - the value doesn't match tor's current state
# MISSING    - value differs from its default but is missing from the torrc
# IS_DEFAULT - the configuration option matches tor's default
ValidationError = enum.Enum("DUPLICATE", "MISMATCH", "MISSING", "IS_DEFAULT")

# descriptions of tor's configuration options fetched from its man page
CONFIG_DESCRIPTIONS_LOCK = threading.RLock()
Пример #19
0
def start_nyx():
    """
  Main draw loop context.
  """

    global NYX_CONTROLLER

    NYX_CONTROLLER = Controller()
    control = get_controller()

    if not CONFIG['features.acsSupport']:
        nyx.curses.disable_acs()

    # provides notice about any unused config keys

    for key in sorted(conf.get_config('nyx').unused_keys()):
        if not key.startswith('msg.') and not key.startswith('dedup.'):
            log.notice('Unused configuration entry: %s' % key)

    # tells daemon panels to start

    for panel_impl in control.get_daemon_panels():
        panel_impl.start()

    # logs the initialization time

    log.info('nyx started (initialization took %0.3f seconds)' %
             (time.time() - CONFIG['start_time']))

    # main draw loop

    override_key = None  # uses this rather than waiting on user input

    while not control.quit_signal:
        display_panels = [control.header_panel()
                          ] + control.get_display_panels()

        # sets panel visability

        for panel_impl in control.get_all_panels():
            panel_impl.set_visible(panel_impl in display_panels)

        # redraws the interface if it's needed

        control.redraw(False)

        with nyx.curses.raw_screen() as stdscr:
            stdscr.refresh()

        # wait for user keyboard input until timeout, unless an override was set

        if override_key:
            key, override_key = override_key, None
        else:
            key = nyx.curses.key_input(CONFIG['features.redrawRate'])

        if key.match('right'):
            control.next_page()
        elif key.match('left'):
            control.prev_page()
        elif key.match('p'):
            control.set_paused(not control.is_paused())
        elif key.match('m'):
            nyx.menu.show_menu()
        elif key.match('q'):
            # provides prompt to confirm that nyx should exit

            if CONFIG['features.confirmQuit']:
                msg = 'Are you sure (q again to confirm)?'
                confirmation_key = show_message(msg, BOLD, max_wait=30)
                quit_confirmed = confirmation_key.match('q')
            else:
                quit_confirmed = True

            if quit_confirmed:
                break
        elif key.match('x'):
            # provides prompt to confirm that nyx should issue a sighup

            msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
            confirmation_key = show_message(msg, BOLD, max_wait=30)

            if confirmation_key in (ord('x'), ord('X')):
                try:
                    tor_controller().signal(stem.Signal.RELOAD)
                except IOError as exc:
                    log.error('Error detected when reloading tor: %s' %
                              exc.strerror)
        elif key.match('h'):
            override_key = nyx.popups.show_help()
        elif key == ord('l') - 96:
            # force redraw when ctrl+l is pressed
            control.redraw(True)
        else:
            for panel_impl in display_panels:
                for keybinding in panel_impl.key_handlers():
                    keybinding.handle(key)
Пример #20
0
    def __init__(self, stdscr):
        panel.Panel.__init__(self, stdscr, "connections", 0)
        threading.Thread.__init__(self)
        self.setDaemon(True)

        # defaults our listing selection to fingerprints if ip address
        # displaying is disabled
        #
        # TODO: This is a little sucky in that it won't work if showIps changes
        # while we're running (... but arm doesn't allow for that atm)

        if not CONFIG["features.connection.showIps"] and CONFIG[
                "features.connection.listingType"] == 0:
            armConf = conf.get_config("arm")
            armConf.set(
                "features.connection.listingType",
                enumeration.keys()[Listing.index_of(Listing.FINGERPRINT)])

        self._scroller = uiTools.Scroller(True)
        self._title = "Connections:"  # title line of the panel
        self._entries = []  # last fetched display entries
        self._entryLines = [
        ]  # individual lines rendered from the entries listing
        self._showDetails = False  # presents the details panel if true

        self._lastUpdate = -1  # time the content was last revised
        self._isTorRunning = True  # indicates if tor is currently running or not
        self._haltTime = None  # time when tor was stopped
        self._halt = False  # terminates thread if true
        self._cond = threading.Condition()  # used for pausing the thread
        self.valsLock = threading.RLock()

        # Tracks exiting port and client country statistics
        self._clientLocaleUsage = {}
        self._exitPortUsage = {}

        # If we're a bridge and been running over a day then prepopulates with the
        # last day's clients.

        conn = torTools.getConn()
        bridgeClients = conn.getInfo("status/clients-seen", None)

        if bridgeClients:
            # Response has a couple arguments...
            # TimeStarted="2011-08-17 15:50:49" CountrySummary=us=16,de=8,uk=8

            countrySummary = None
            for arg in bridgeClients.split():
                if arg.startswith("CountrySummary="):
                    countrySummary = arg[15:]
                    break

            if countrySummary:
                for entry in countrySummary.split(","):
                    if re.match("^..=[0-9]+$", entry):
                        locale, count = entry.split("=", 1)
                        self._clientLocaleUsage[locale] = int(count)

        # Last sampling received from the ConnectionResolver, used to detect when
        # it changes.
        self._lastResourceFetch = -1

        # resolver for the command/pid associated with SOCKS, HIDDEN, and CONTROL connections
        self._appResolver = connections.AppResolver("arm")

        # rate limits appResolver queries to once per update
        self.appResolveSinceUpdate = False

        # mark the initially exitsing connection uptimes as being estimates
        for entry in self._entries:
            if isinstance(entry, connEntry.ConnectionEntry):
                entry.getLines()[0].isInitialConnection = True

        # listens for when tor stops so we know to stop reflecting changes
        conn.addStatusListener(self.torStateListener)
Пример #21
0
 def __init__(self, stdscr):
   panel.Panel.__init__(self, stdscr, "connections", 0)
   threading.Thread.__init__(self)
   self.setDaemon(True)
   
   # defaults our listing selection to fingerprints if ip address
   # displaying is disabled
   #
   # TODO: This is a little sucky in that it won't work if showIps changes
   # while we're running (... but arm doesn't allow for that atm)
   
   if not CONFIG["features.connection.showIps"] and CONFIG["features.connection.listingType"] == 0:
     armConf = conf.get_config("arm")
     armConf.set("features.connection.listingType", enumeration.keys()[Listing.index_of(Listing.FINGERPRINT)])
   
   self._scroller = uiTools.Scroller(True)
   self._title = "Connections:" # title line of the panel
   self._entries = []          # last fetched display entries
   self._entryLines = []       # individual lines rendered from the entries listing
   self._showDetails = False   # presents the details panel if true
   
   self._lastUpdate = -1       # time the content was last revised
   self._isTorRunning = True   # indicates if tor is currently running or not
   self._haltTime = None       # time when tor was stopped
   self._halt = False          # terminates thread if true
   self._cond = threading.Condition()  # used for pausing the thread
   self.valsLock = threading.RLock()
   
   # Tracks exiting port and client country statistics
   self._clientLocaleUsage = {}
   self._exitPortUsage = {}
   
   # If we're a bridge and been running over a day then prepopulates with the
   # last day's clients.
   
   conn = torTools.getConn()
   bridgeClients = conn.getInfo("status/clients-seen", None)
   
   if bridgeClients:
     # Response has a couple arguments...
     # TimeStarted="2011-08-17 15:50:49" CountrySummary=us=16,de=8,uk=8
     
     countrySummary = None
     for arg in bridgeClients.split():
       if arg.startswith("CountrySummary="):
         countrySummary = arg[15:]
         break
     
     if countrySummary:
       for entry in countrySummary.split(","):
         if re.match("^..=[0-9]+$", entry):
           locale, count = entry.split("=", 1)
           self._clientLocaleUsage[locale] = int(count)
   
   # Last sampling received from the ConnectionResolver, used to detect when
   # it changes.
   self._lastResourceFetch = -1
   
   # resolver for the command/pid associated with SOCKS, HIDDEN, and CONTROL connections
   self._appResolver = connections.AppResolver("arm")
   
   # rate limits appResolver queries to once per update
   self.appResolveSinceUpdate = False
   
   # mark the initially exitsing connection uptimes as being estimates
   for entry in self._entries:
     if isinstance(entry, connEntry.ConnectionEntry):
       entry.getLines()[0].isInitialConnection = True
   
   # listens for when tor stops so we know to stop reflecting changes
   conn.addStatusListener(self.torStateListener)
Пример #22
0
    def draw(self, width, height):
        self.valsLock.acquire()

        # If true, we assume that the cached value in self._lastContentHeight is
        # still accurate, and stop drawing when there's nothing more to display.
        # Otherwise the self._lastContentHeight is suspect, and we'll process all
        # the content to check if it's right (and redraw again with the corrected
        # height if not).
        trustLastContentHeight = self._lastContentHeightArgs == (width, height)

        # restricts scroll location to valid bounds
        self.scroll = max(
            0, min(self.scroll, self._lastContentHeight - height + 1))

        renderedContents, corrections, confLocation = None, {}, None
        if self.configType == Config.TORRC:
            loadedTorrc = torConfig.getTorrc()
            loadedTorrc.getLock().acquire()
            confLocation = loadedTorrc.getConfigLocation()

            if not loadedTorrc.isLoaded():
                renderedContents = ["### Unable to load the torrc ###"]
            else:
                renderedContents = loadedTorrc.getDisplayContents(
                    self.stripComments)

                # constructs a mapping of line numbers to the issue on it
                corrections = dict(
                    (lineNum, (issue, msg))
                    for lineNum, issue, msg in loadedTorrc.getCorrections())

            loadedTorrc.getLock().release()
        else:
            loadedArmrc = conf.get_config("arm")
            confLocation = loadedArmrc._path
            renderedContents = list(loadedArmrc._raw_contents)

        # offset to make room for the line numbers
        lineNumOffset = 0
        if self.showLineNum:
            if len(renderedContents) == 0: lineNumOffset = 2
            else: lineNumOffset = int(math.log10(len(renderedContents))) + 2

        # draws left-hand scroll bar if content's longer than the height
        scrollOffset = 0
        if CONFIG[
                "features.config.file.showScrollbars"] and self._lastContentHeight > height - 1:
            scrollOffset = 3
            self.addScrollBar(self.scroll, self.scroll + height - 1,
                              self._lastContentHeight, 1)

        displayLine = -self.scroll + 1  # line we're drawing on

        # draws the top label
        if self.isTitleVisible():
            sourceLabel = "Tor" if self.configType == Config.TORRC else "Arm"
            locationLabel = " (%s)" % confLocation if confLocation else ""
            self.addstr(
                0, 0,
                "%s Configuration File%s:" % (sourceLabel, locationLabel),
                curses.A_STANDOUT)

        isMultiline = False  # true if we're in the middle of a multiline torrc entry
        for lineNumber in range(0, len(renderedContents)):
            lineText = renderedContents[lineNumber]
            lineText = lineText.rstrip()  # remove ending whitespace

            # blank lines are hidden when stripping comments
            if self.stripComments and not lineText: continue

            # splits the line into its component (msg, format) tuples
            lineComp = {
                "option": ["", curses.A_BOLD | uiTools.getColor("green")],
                "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                "comment": ["", uiTools.getColor("white")]
            }

            # parses the comment
            commentIndex = lineText.find("#")
            if commentIndex != -1:
                lineComp["comment"][0] = lineText[commentIndex:]
                lineText = lineText[:commentIndex]

            # splits the option and argument, preserving any whitespace around them
            strippedLine = lineText.strip()
            optionIndex = strippedLine.find(" ")
            if isMultiline:
                # part of a multiline entry started on a previous line so everything
                # is part of the argument
                lineComp["argument"][0] = lineText
            elif optionIndex == -1:
                # no argument provided
                lineComp["option"][0] = lineText
            else:
                optionText = strippedLine[:optionIndex]
                optionEnd = lineText.find(optionText) + len(optionText)
                lineComp["option"][0] = lineText[:optionEnd]
                lineComp["argument"][0] = lineText[optionEnd:]

            # flags following lines as belonging to this multiline entry if it ends
            # with a slash
            if strippedLine: isMultiline = strippedLine.endswith("\\")

            # gets the correction
            if lineNumber in corrections:
                lineIssue, lineIssueMsg = corrections[lineNumber]

                if lineIssue in (torConfig.ValidationError.DUPLICATE,
                                 torConfig.ValidationError.IS_DEFAULT):
                    lineComp["option"][1] = curses.A_BOLD | uiTools.getColor(
                        "blue")
                    lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor(
                        "blue")
                elif lineIssue == torConfig.ValidationError.MISMATCH:
                    lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor(
                        "red")
                    lineComp["correction"][0] = " (%s)" % lineIssueMsg
                else:
                    # For some types of configs the correction field is simply used to
                    # provide extra data (for instance, the type for tor state fields).
                    lineComp["correction"][0] = " (%s)" % lineIssueMsg
                    lineComp["correction"][
                        1] = curses.A_BOLD | uiTools.getColor("magenta")

            # draws the line number
            if self.showLineNum and displayLine < height and displayLine >= 1:
                lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1)
                self.addstr(displayLine, scrollOffset, lineNumStr,
                            curses.A_BOLD | uiTools.getColor("yellow"))

            # draws the rest of the components with line wrap
            cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0
            maxLinesPerEntry = CONFIG["features.config.file.maxLinesPerEntry"]
            displayQueue = [
                lineComp[entry]
                for entry in ("option", "argument", "correction", "comment")
            ]

            while displayQueue:
                msg, format = displayQueue.pop(0)

                maxMsgSize, includeBreak = width - cursorLoc, False
                if len(msg) >= maxMsgSize:
                    # message is too long - break it up
                    if lineOffset == maxLinesPerEntry - 1:
                        msg = uiTools.cropStr(msg, maxMsgSize)
                    else:
                        includeBreak = True
                        msg, remainder = uiTools.cropStr(
                            msg, maxMsgSize, 4, 4, uiTools.Ending.HYPHEN, True)
                        displayQueue.insert(0, (remainder.strip(), format))

                drawLine = displayLine + lineOffset
                if msg and drawLine < height and drawLine >= 1:
                    self.addstr(drawLine, cursorLoc, msg, format)

                # If we're done, and have added content to this line, then start
                # further content on the next line.
                cursorLoc += len(msg)
                includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset

                if includeBreak:
                    lineOffset += 1
                    cursorLoc = lineNumOffset + scrollOffset

            displayLine += max(lineOffset, 1)

            if trustLastContentHeight and displayLine >= height: break

        if not trustLastContentHeight:
            self._lastContentHeightArgs = (width, height)
            newContentHeight = displayLine + self.scroll - 1

            if self._lastContentHeight != newContentHeight:
                self._lastContentHeight = newContentHeight
                self.redraw(True)

        self.valsLock.release()
Пример #23
0
def main():
    last_notified_config = conf.get_config('last_notified')
    last_notified_path = util.get_path('data',
                                       'fingerprint_change_last_notified.cfg')

    if os.path.exists(last_notified_path):
        last_notified_config.load(last_notified_path)
    else:
        last_notified_config._path = last_notified_path

    fingerprint_changes = load_fingerprint_changes()
    downloader = DescriptorDownloader(timeout=15)
    alarm_for = {}

    for relay in downloader.get_consensus():
        prior_fingerprints = fingerprint_changes.setdefault(
            (relay.address, relay.or_port), {})

        if relay.fingerprint not in prior_fingerprints:
            log.debug("Registering a new fingerprint for %s:%s (%s)" %
                      (relay.address, relay.or_port, relay.fingerprint))
            prior_fingerprints[relay.fingerprint] = datetime_to_unix(
                relay.published)

            # drop fingerprint changes that are over thirty days old

            old_fingerprints = [
                fp for fp in prior_fingerprints
                if (time.time() - prior_fingerprints[fp] > TEN_DAYS)
            ]

            for fp in old_fingerprints:
                log.debug(
                    "Removing fingerprint for %s:%s (%s) which was published %i days ago"
                    % (relay.address, relay.or_port, fp,
                       prior_fingerprints[fp] / 60 / 60 / 24))
                del prior_fingerprints[fp]

            # if we've changed more than ten times in the last ten days then alarm

            if len(prior_fingerprints) >= 10:
                alarm_for['%s:%s' %
                          (relay.address, relay.or_port)] = (relay.address,
                                                             relay.or_port,
                                                             relay.fingerprint)

    if alarm_for and not is_notification_suppressed(alarm_for.values()):
        log.debug("Sending a notification for %i relays..." % len(alarm_for))
        body = EMAIL_BODY

        for address, or_port, fingerprint in alarm_for.values():
            try:
                desc = downloader.get_server_descriptors(fingerprint).run()[0]
            except:
                desc = None  # might not be available, just used for extra info

            fp_changes = fingerprint_changes[(address, or_port)]
            log.debug("* %s:%s has had %i fingerprints: %s" %
                      (address, or_port, len(fp_changes), ', '.join(
                          fp_changes.keys())))

            if desc:
                body += "* %s:%s (platform: %s, contact: %s)\n" % (
                    address, or_port, desc.platform.decode(
                        'utf-8', 'replace'), desc.contact)
            else:
                body += "* %s:%s\n" % (address, or_port)

            count = 0

            for fingerprint in sorted(fp_changes,
                                      reverse=True,
                                      key=lambda k: fp_changes[k]):
                body += "  %s at %s\n" % (
                    fingerprint,
                    datetime.datetime.fromtimestamp(
                        fp_changes[fingerprint]).strftime('%Y-%m-%d %H:%M:%S'))
                count += 1

                # Relays frequently cycling their fringerprint can have thousands of
                # entries. Enumerating them all is unimportant, so if too long then
                # just give the count.

                if count > 8:
                    oldest_timestamp = sorted(fp_changes.values())[0]
                    body += "  ... and %i more since %s\n" % (
                        len(fp_changes) - 8,
                        datetime.datetime.fromtimestamp(
                            oldest_timestamp).strftime('%Y-%m-%d %H:%M:%S'))
                    break

            body += "\n"

        subject = EMAIL_SUBJECT

        if len(alarm_for) == 1:
            subject += ' (%s:%s)' % alarm_for.values()[0][:2]

        util.send(subject,
                  body=body,
                  to=[
                      '*****@*****.**',
                      '*****@*****.**'
                  ])

        # register that we've notified for these

        current_time = str(int(time.time()))

        for address, or_port, _ in alarm_for.values():
            last_notified_config.set('%s:%s' % (address, or_port),
                                     current_time)

        last_notified_config.save()

    save_fingerprint_changes(fingerprint_changes)
Пример #24
0
        "torrc.label.time.week": [],
    }, conf_handler)


def general_conf_handler(config, key):
    value = config.get(key)

    if key.startswith("config.summary."):
        # we'll look for summary keys with a lowercase config name
        CONFIG[key.lower()] = value
    elif key.startswith("torrc.label.") and value:
        # all the torrc.label.* values are comma separated lists
        return [entry.strip() for entry in value[0].split(",")]


conf.get_config("arm").add_listener(general_conf_handler, backfill=True)

# enums and values for numeric torrc entries
ValueType = enum.Enum("UNRECOGNIZED", "SIZE", "TIME")
SIZE_MULT = {
    "b": 1,
    "kb": 1024,
    "mb": 1048576,
    "gb": 1073741824,
    "tb": 1099511627776
}
TIME_MULT = {"sec": 1, "min": 60, "hour": 3600, "day": 86400, "week": 604800}

# enums for issues found during torrc validation:
# DUPLICATE  - entry is ignored due to being a duplicate
# MISMATCH   - the value doesn't match tor's current state
Пример #25
0
def start_nyx(stdscr):
  """
  Main draw loop context.

  Arguments:
    stdscr    - curses window
  """

  init_controller(stdscr, CONFIG['start_time'])
  control = get_controller()

  if not CONFIG['features.acsSupport']:
    ui_tools.disable_acs()

  # provides notice about any unused config keys

  for key in conf.get_config('nyx').unused_keys():
    log.notice('Unused configuration entry: %s' % key)

  # tells daemon panels to start

  for panel_impl in control.get_daemon_panels():
    panel_impl.start()

  # allows for background transparency

  try:
    curses.use_default_colors()
  except curses.error:
    pass

  # makes the cursor invisible

  try:
    curses.curs_set(0)
  except curses.error:
    pass

  # logs the initialization time

  log.info('nyx started (initialization took %0.3f seconds)' % (time.time() - CONFIG['start_time']))

  # main draw loop

  override_key = None      # uses this rather than waiting on user input
  is_unresponsive = False  # flag for heartbeat responsiveness check

  while not control.quit_signal:
    display_panels = control.get_display_panels()
    is_unresponsive = heartbeat_check(is_unresponsive)

    # sets panel visability

    for panel_impl in control.get_all_panels():
      panel_impl.set_visible(panel_impl in display_panels)

    # redraws the interface if it's needed

    control.redraw(False)
    stdscr.refresh()

    # wait for user keyboard input until timeout, unless an override was set

    if override_key:
      key, override_key = override_key, None
    else:
      curses.halfdelay(CONFIG['features.redrawRate'] * 10)
      key = panel.KeyInput(stdscr.getch())

    if key.match('right'):
      control.next_page()
    elif key.match('left'):
      control.prev_page()
    elif key.match('p'):
      control.set_paused(not control.is_paused())
    elif key.match('m'):
      nyx.menu.menu.show_menu()
    elif key.match('q'):
      # provides prompt to confirm that nyx should exit

      if CONFIG['features.confirmQuit']:
        msg = 'Are you sure (q again to confirm)?'
        confirmation_key = nyx.popups.show_msg(msg, attr = curses.A_BOLD)
        quit_confirmed = confirmation_key.match('q')
      else:
        quit_confirmed = True

      if quit_confirmed:
        break
    elif key.match('x'):
      # provides prompt to confirm that nyx should issue a sighup

      msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
      confirmation_key = nyx.popups.show_msg(msg, attr = curses.A_BOLD)

      if confirmation_key in (ord('x'), ord('X')):
        try:
          tor_controller().signal(stem.Signal.RELOAD)
        except IOError as exc:
          log.error('Error detected when reloading tor: %s' % exc.strerror)
    elif key.match('h'):
      override_key = nyx.popups.show_help_popup()
    elif key == ord('l') - 96:
      # force redraw when ctrl+l is pressed
      control.redraw(True)
    else:
      for panel_impl in display_panels:
        is_keystroke_consumed = panel_impl.handle_key(key)

        if is_keystroke_consumed:
          break
Пример #26
0
    def draw(self, width, height):
        self.valsLock.acquire()

        # If true, we assume that the cached value in self._lastContentHeight is
        # still accurate, and stop drawing when there's nothing more to display.
        # Otherwise the self._lastContentHeight is suspect, and we'll process all
        # the content to check if it's right (and redraw again with the corrected
        # height if not).
        trustLastContentHeight = self._lastContentHeightArgs == (width, height)

        # restricts scroll location to valid bounds
        self.scroll = max(0, min(self.scroll, self._lastContentHeight - height + 1))

        renderedContents, corrections, confLocation = None, {}, None
        if self.configType == Config.TORRC:
            loadedTorrc = torConfig.getTorrc()
            loadedTorrc.getLock().acquire()
            confLocation = loadedTorrc.getConfigLocation()

            if not loadedTorrc.isLoaded():
                renderedContents = ["### Unable to load the torrc ###"]
            else:
                renderedContents = loadedTorrc.getDisplayContents(self.stripComments)

                # constructs a mapping of line numbers to the issue on it
                corrections = dict((lineNum, (issue, msg)) for lineNum, issue, msg in loadedTorrc.getCorrections())

            loadedTorrc.getLock().release()
        else:
            loadedArmrc = conf.get_config("arm")
            confLocation = loadedArmrc._path
            renderedContents = list(loadedArmrc._raw_contents)

        # offset to make room for the line numbers
        lineNumOffset = 0
        if self.showLineNum:
            if len(renderedContents) == 0:
                lineNumOffset = 2
            else:
                lineNumOffset = int(math.log10(len(renderedContents))) + 2

        # draws left-hand scroll bar if content's longer than the height
        scrollOffset = 0
        if CONFIG["features.config.file.showScrollbars"] and self._lastContentHeight > height - 1:
            scrollOffset = 3
            self.addScrollBar(self.scroll, self.scroll + height - 1, self._lastContentHeight, 1)

        displayLine = -self.scroll + 1  # line we're drawing on

        # draws the top label
        if self.isTitleVisible():
            sourceLabel = "Tor" if self.configType == Config.TORRC else "Arm"
            locationLabel = " (%s)" % confLocation if confLocation else ""
            self.addstr(0, 0, "%s Configuration File%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT)

        isMultiline = False  # true if we're in the middle of a multiline torrc entry
        for lineNumber in range(0, len(renderedContents)):
            lineText = renderedContents[lineNumber]
            lineText = lineText.rstrip()  # remove ending whitespace

            # blank lines are hidden when stripping comments
            if self.stripComments and not lineText:
                continue

            # splits the line into its component (msg, format) tuples
            lineComp = {
                "option": ["", curses.A_BOLD | uiTools.getColor("green")],
                "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                "comment": ["", uiTools.getColor("white")],
            }

            # parses the comment
            commentIndex = lineText.find("#")
            if commentIndex != -1:
                lineComp["comment"][0] = lineText[commentIndex:]
                lineText = lineText[:commentIndex]

            # splits the option and argument, preserving any whitespace around them
            strippedLine = lineText.strip()
            optionIndex = strippedLine.find(" ")
            if isMultiline:
                # part of a multiline entry started on a previous line so everything
                # is part of the argument
                lineComp["argument"][0] = lineText
            elif optionIndex == -1:
                # no argument provided
                lineComp["option"][0] = lineText
            else:
                optionText = strippedLine[:optionIndex]
                optionEnd = lineText.find(optionText) + len(optionText)
                lineComp["option"][0] = lineText[:optionEnd]
                lineComp["argument"][0] = lineText[optionEnd:]

            # flags following lines as belonging to this multiline entry if it ends
            # with a slash
            if strippedLine:
                isMultiline = strippedLine.endswith("\\")

            # gets the correction
            if lineNumber in corrections:
                lineIssue, lineIssueMsg = corrections[lineNumber]

                if lineIssue in (torConfig.ValidationError.DUPLICATE, torConfig.ValidationError.IS_DEFAULT):
                    lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
                    lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue")
                elif lineIssue == torConfig.ValidationError.MISMATCH:
                    lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red")
                    lineComp["correction"][0] = " (%s)" % lineIssueMsg
                else:
                    # For some types of configs the correction field is simply used to
                    # provide extra data (for instance, the type for tor state fields).
                    lineComp["correction"][0] = " (%s)" % lineIssueMsg
                    lineComp["correction"][1] = curses.A_BOLD | uiTools.getColor("magenta")

            # draws the line number
            if self.showLineNum and displayLine < height and displayLine >= 1:
                lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1)
                self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow"))

            # draws the rest of the components with line wrap
            cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0
            maxLinesPerEntry = CONFIG["features.config.file.maxLinesPerEntry"]
            displayQueue = [lineComp[entry] for entry in ("option", "argument", "correction", "comment")]

            while displayQueue:
                msg, format = displayQueue.pop(0)

                maxMsgSize, includeBreak = width - cursorLoc, False
                if len(msg) >= maxMsgSize:
                    # message is too long - break it up
                    if lineOffset == maxLinesPerEntry - 1:
                        msg = uiTools.cropStr(msg, maxMsgSize)
                    else:
                        includeBreak = True
                        msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.Ending.HYPHEN, True)
                        displayQueue.insert(0, (remainder.strip(), format))

                drawLine = displayLine + lineOffset
                if msg and drawLine < height and drawLine >= 1:
                    self.addstr(drawLine, cursorLoc, msg, format)

                # If we're done, and have added content to this line, then start
                # further content on the next line.
                cursorLoc += len(msg)
                includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset

                if includeBreak:
                    lineOffset += 1
                    cursorLoc = lineNumOffset + scrollOffset

            displayLine += max(lineOffset, 1)

            if trustLastContentHeight and displayLine >= height:
                break

        if not trustLastContentHeight:
            self._lastContentHeightArgs = (width, height)
            newContentHeight = displayLine + self.scroll - 1

            if self._lastContentHeight != newContentHeight:
                self._lastContentHeight = newContentHeight
                self.redraw(True)

        self.valsLock.release()