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()
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 {}
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
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
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
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
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))
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))
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()
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()
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
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()
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()
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)
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)
"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()
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)
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)
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)
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()
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)
"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
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
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()