Esempio n. 1
0
    def getListingEntry(self, width, currentTime, listingType):
        """
    Provides the tuple list for this connection's listing. Lines are composed
    of the following components:
      <src>  -->  <dst>     <etc>     <uptime> (<type>)
    
    ListingType.IP_ADDRESS:
      src - <internal addr:port> --> <external addr:port>
      dst - <destination addr:port>
      etc - <fingerprint> <nickname>
    
    ListingType.HOSTNAME:
      src - localhost:<port>
      dst - <destination hostname:port>
      etc - <destination addr:port> <fingerprint> <nickname>
    
    ListingType.FINGERPRINT:
      src - localhost
      dst - <destination fingerprint>
      etc - <nickname> <destination addr:port>
    
    ListingType.NICKNAME:
      src - <source nickname>
      dst - <destination nickname>
      etc - <fingerprint> <destination addr:port>
    
    Arguments:
      width       - maximum length of the line
      currentTime - unix timestamp for what the results should consider to be
                    the current time
      listingType - primary attribute we're listing connections by
    """

        # fetch our (most likely cached) display entry for the listing
        myListing = entries.ConnectionPanelLine.getListingEntry(
            self, width, currentTime, listingType)

        # fill in the current uptime and return the results
        if CONFIG["features.connection.markInitialConnections"]:
            timePrefix = "+" if self.isInitialConnection else " "
        else:
            timePrefix = ""

        timeLabel = timePrefix + "%5s" % uiTools.getTimeLabel(
            currentTime - self.startTime, 1)
        myListing[2] = (timeLabel, myListing[2][1])

        return myListing
Esempio n. 2
0
    def get_listing_row(self, listingType):
        conn = torTools.getConn()
        myType = self.cliLine.getType()
        dstAddress = self.cliLine.getDestinationLabel(26, includeLocale=True)
        localPort = ":%s" % self.cliLine.local.getPort(
        ) if self.cliLine.includePort else ""

        src, dst, etc = "", "", ""

        if listingType == entries.ListingType.IP_ADDRESS:
            myExternalIpAddr = conn.getInfo("address",
                                            self.cliLine.local.getIpAddr())
            addrDiffer = myExternalIpAddr != self.cliLine.local.getIpAddr()

            isExpansionType = not myType in (Category.SOCKS, Category.HIDDEN,
                                             Category.CONTROL)

            if isExpansionType: srcAddress = myExternalIpAddr + localPort
            else: srcAddress = self.cliLine.local.getIpAddr() + localPort

            if myType in (Category.SOCKS, Category.CONTROL):
                src = dstAddress
                dst = srcAddress
            else:
                src = srcAddress
                dst = dstAddress

            if addrDiffer and isExpansionType and self.cliLine.includeExpandedIpAddr and CONFIG[
                    "features.connection.showColumn.expandedIp"]:
                internalAddress = self.cliLine.local.getIpAddr() + localPort

                if myType == Category.INBOUND:
                    (src, dst) = (src, internalAddress)
                else:
                    (src, dst) = (internalAddress, src)

            etc = self.cliLine.getEtcContent(100, listingType)
        else:
            src = "%s:%s" % (self.cliLine.local.ipAddr,
                             self.cliLine.local.port)
            dst = "%s:%s" % (self.cliLine.foreign.ipAddr,
                             self.cliLine.foreign.port)

        timeLabel = uiTools.getTimeLabel(time.time() - self.cliLine.startTime)
        theme = gtkTools.Theme()

        return (src, dst, timeLabel, self.cliLine.getType(),
                theme.colors['insensitive'], self)
Esempio n. 3
0
 def getListingEntry(self, width, currentTime, listingType):
   """
   Provides the tuple list for this connection's listing. Lines are composed
   of the following components:
     <src>  -->  <dst>     <etc>     <uptime> (<type>)
   
   ListingType.IP_ADDRESS:
     src - <internal addr:port> --> <external addr:port>
     dst - <destination addr:port>
     etc - <fingerprint> <nickname>
   
   ListingType.HOSTNAME:
     src - localhost:<port>
     dst - <destination hostname:port>
     etc - <destination addr:port> <fingerprint> <nickname>
   
   ListingType.FINGERPRINT:
     src - localhost
     dst - <destination fingerprint>
     etc - <nickname> <destination addr:port>
   
   ListingType.NICKNAME:
     src - <source nickname>
     dst - <destination nickname>
     etc - <fingerprint> <destination addr:port>
   
   Arguments:
     width       - maximum length of the line
     currentTime - unix timestamp for what the results should consider to be
                   the current time
     listingType - primary attribute we're listing connections by
   """
   
   # fetch our (most likely cached) display entry for the listing
   myListing = entries.ConnectionPanelLine.getListingEntry(self, width, currentTime, listingType)
   
   # fill in the current uptime and return the results
   if CONFIG["features.connection.markInitialConnections"]:
     timePrefix = "+" if self.isInitialConnection else " "
   else: timePrefix = ""
   
   timeLabel = timePrefix + "%5s" % uiTools.getTimeLabel(currentTime - self.startTime, 1)
   myListing[2] = (timeLabel, myListing[2][1])
   
   return myListing
Esempio n. 4
0
 def _getValue(self):
   """
   Provides the current value of the configuration entry, taking advantage of
   the torTools caching to effectively query the accurate value. This uses the
   value's type to provide a user friendly representation if able.
   """
   
   confValue = ", ".join(torTools.getConn().getOption(self.get(Field.OPTION), [], True))
   
   # provides nicer values for recognized types
   if not confValue: confValue = "<none>"
   elif self.get(Field.TYPE) == "Boolean" and confValue in ("0", "1"):
     confValue = "False" if confValue == "0" else "True"
   elif self.get(Field.TYPE) == "DataSize" and confValue.isdigit():
     confValue = uiTools.getSizeLabel(int(confValue))
   elif self.get(Field.TYPE) == "TimeInterval" and confValue.isdigit():
     confValue = uiTools.getTimeLabel(int(confValue), isLong = True)
   
   return confValue
Esempio n. 5
0
    def _getValue(self):
        """
    Provides the current value of the configuration entry, taking advantage of
    the torTools caching to effectively query the accurate value. This uses the
    value's type to provide a user friendly representation if able.
    """

        confValue = ", ".join(torTools.getConn().getOption(
            self.get(FIELD_OPTION), [], True))

        # provides nicer values for recognized types
        if not confValue: confValue = "<none>"
        elif self.get(FIELD_TYPE) == "Boolean" and confValue in ("0", "1"):
            confValue = "False" if confValue == "0" else "True"
        elif self.get(FIELD_TYPE) == "DataSize" and confValue.isdigit():
            confValue = uiTools.getSizeLabel(int(confValue))
        elif self.get(FIELD_TYPE) == "TimeInterval" and confValue.isdigit():
            confValue = uiTools.getTimeLabel(int(confValue), isLong=True)

        return confValue
Esempio n. 6
0
  def get_listing_row(self, listingType):
    conn = torTools.getConn()
    myType = self.cliLine.getType()
    dstAddress = self.cliLine.getDestinationLabel(26, includeLocale = True)
    localPort = ":%s" % self.cliLine.local.getPort() if self.cliLine.includePort else ""

    src, dst, etc = "", "", ""

    if listingType == entries.ListingType.IP_ADDRESS:
      myExternalIpAddr = conn.getInfo("address", self.cliLine.local.getIpAddr())
      addrDiffer = myExternalIpAddr != self.cliLine.local.getIpAddr()

      isExpansionType = not myType in (Category.SOCKS, Category.HIDDEN, Category.CONTROL)

      if isExpansionType: srcAddress = myExternalIpAddr + localPort
      else: srcAddress = self.cliLine.local.getIpAddr() + localPort

      if myType in (Category.SOCKS, Category.CONTROL):
        src = dstAddress
        dst = srcAddress
      else:
        src = srcAddress
        dst = dstAddress

      if addrDiffer and isExpansionType and self.cliLine.includeExpandedIpAddr and CONFIG["features.connection.showColumn.expandedIp"]:
        internalAddress = self.cliLine.local.getIpAddr() + localPort

        if myType == Category.INBOUND: (src, dst) =  (src, internalAddress)
        else: (src, dst) =  (internalAddress, src)

      etc = self.cliLine.getEtcContent(100, listingType)
    else:
      src = "%s:%s" % (self.cliLine.local.ipAddr, self.cliLine.local.port)
      dst = "%s:%s" % (self.cliLine.foreign.ipAddr, self.cliLine.foreign.port)

    timeLabel = uiTools.getTimeLabel(time.time() - self.cliLine.startTime)
    theme = gtkTools.Theme()

    return (src, dst, timeLabel, self.cliLine.getType(), theme.colors['insensitive'], self)
Esempio n. 7
0
def validate(contents = None):
  """
  Performs validation on the given torrc contents, providing back a listing of
  (line number, issue, msg) tuples for issues found. If the issue occures on a
  multiline torrc entry then the line number is for the last line of the entry.
  
  Arguments:
    contents - torrc contents
  """
  
  conn = torTools.getConn()
  customOptions = getCustomOptions()
  issuesFound, seenOptions = [], []
  
  # Strips comments and collapses multiline multi-line entries, for more
  # information see:
  # https://trac.torproject.org/projects/tor/ticket/1929
  strippedContents, multilineBuffer = [], ""
  for line in _stripComments(contents):
    if not line: strippedContents.append("")
    else:
      line = multilineBuffer + line
      multilineBuffer = ""
      
      if line.endswith("\\"):
        multilineBuffer = line[:-1]
        strippedContents.append("")
      else:
        strippedContents.append(line.strip())
  
  for lineNumber in range(len(strippedContents) - 1, -1, -1):
    lineText = strippedContents[lineNumber]
    if not lineText: continue
    
    lineComp = lineText.split(None, 1)
    if len(lineComp) == 2: option, value = lineComp
    else: option, value = lineText, ""
    
    # if an aliased option then use its real name
    if option in CONFIG["torrc.alias"]:
      option = CONFIG["torrc.alias"][option]
    
    # most parameters are overwritten if defined multiple times
    if option in seenOptions and not option in getMultilineParameters():
      issuesFound.append((lineNumber, VAL_DUPLICATE, option))
      continue
    else: seenOptions.append(option)
    
    # checks if the value isn't necessary due to matching the defaults
    if not option in customOptions:
      issuesFound.append((lineNumber, VAL_IS_DEFAULT, option))
    
    # replace aliases with their recognized representation
    if option in CONFIG["torrc.alias"]:
      option = CONFIG["torrc.alias"][option]
    
    # tor appears to replace tabs with a space, for instance:
    # "accept\t*:563" is read back as "accept *:563"
    value = value.replace("\t", " ")
    
    # parse value if it's a size or time, expanding the units
    value, valueType = _parseConfValue(value)
    
    # issues GETCONF to get the values tor's currently configured to use
    torValues = conn.getOption(option, [], True)
    
    # Some singleline entries are lists, in which case tor provides csv values
    # without spaces, such as:
    # lolcat1,lolcat2,cutebunny,extracutebunny,birthdaynode
    # so we need to strip spaces in comma separated values.
    
    if "," in value:
      value = ",".join([val.strip() for val in value.split(",")])
    
    # multiline entries can be comma separated values (for both tor and conf)
    valueList = [value]
    if option in getMultilineParameters():
      valueList = [val.strip() for val in value.split(",")]
      
      fetchedValues, torValues = torValues, []
      for fetchedValue in fetchedValues:
        for fetchedEntry in fetchedValue.split(","):
          fetchedEntry = fetchedEntry.strip()
          if not fetchedEntry in torValues:
            torValues.append(fetchedEntry)
    
    for val in valueList:
      # checks if both the argument and tor's value are empty
      isBlankMatch = not val and not torValues
      
      if not isBlankMatch and not val in torValues:
        # converts corrections to reader friedly size values
        displayValues = torValues
        if valueType == SIZE_VALUE:
          displayValues = [uiTools.getSizeLabel(int(val)) for val in torValues]
        elif valueType == TIME_VALUE:
          displayValues = [uiTools.getTimeLabel(int(val)) for val in torValues]
        
        issuesFound.append((lineNumber, VAL_MISMATCH, ", ".join(displayValues)))
  
  # checks if any custom options are missing from the torrc
  for option in customOptions:
    if not option in seenOptions:
      issuesFound.append((None, VAL_MISSING, option))
  
  return issuesFound
Esempio n. 8
0
 def prepopulateFromState(self):
   """
   Attempts to use tor's state file to prepopulate values for the 15 minute
   interval via the BWHistoryReadValues/BWHistoryWriteValues values. This
   returns True if successful and False otherwise.
   """
   
   # checks that this is a relay (if ORPort is unset, then skip)
   conn = torTools.getConn()
   orPort = conn.getOption("ORPort", None)
   if orPort == "0": return
   
   # gets the uptime (using the same parameters as the header panel to take
   # advantage of caching
   uptime = None
   queryPid = conn.getMyPid()
   if queryPid:
     queryParam = ["%cpu", "rss", "%mem", "etime"]
     queryCmd = "ps -p %s -o %s" % (queryPid, ",".join(queryParam))
     psCall = sysTools.call(queryCmd, 3600, True)
     
     if psCall and len(psCall) == 2:
       stats = psCall[1].strip().split()
       if len(stats) == 4: uptime = stats[3]
   
   # checks if tor has been running for at least a day, the reason being that
   # the state tracks a day's worth of data and this should only prepopulate
   # results associated with this tor instance
   if not uptime or not "-" in uptime:
     msg = PREPOPULATE_FAILURE_MSG % "insufficient uptime"
     log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
     return False
   
   # get the user's data directory (usually '~/.tor')
   dataDir = conn.getOption("DataDirectory", None)
   if not dataDir:
     msg = PREPOPULATE_FAILURE_MSG % "data directory not found"
     log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
     return False
   
   # attempt to open the state file
   try: stateFile = open("%s%s/state" % (conn.getPathPrefix(), dataDir), "r")
   except IOError:
     msg = PREPOPULATE_FAILURE_MSG % "unable to read the state file"
     log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
     return False
   
   # get the BWHistory entries (ordered oldest to newest) and number of
   # intervals since last recorded
   bwReadEntries, bwWriteEntries = None, None
   missingReadEntries, missingWriteEntries = None, None
   
   # converts from gmt to local with respect to DST
   tz_offset = time.altzone if time.localtime()[8] else time.timezone
   
   for line in stateFile:
     line = line.strip()
     
     # According to the rep_hist_update_state() function the BWHistory*Ends
     # correspond to the start of the following sampling period. Also, the
     # most recent values of BWHistory*Values appear to be an incremental
     # counter for the current sampling period. Hence, offsets are added to
     # account for both.
     
     if line.startswith("BWHistoryReadValues"):
       bwReadEntries = line[20:].split(",")
       bwReadEntries = [int(entry) / 1024.0 / 900 for entry in bwReadEntries]
       bwReadEntries.pop()
     elif line.startswith("BWHistoryWriteValues"):
       bwWriteEntries = line[21:].split(",")
       bwWriteEntries = [int(entry) / 1024.0 / 900 for entry in bwWriteEntries]
       bwWriteEntries.pop()
     elif line.startswith("BWHistoryReadEnds"):
       lastReadTime = time.mktime(time.strptime(line[18:], "%Y-%m-%d %H:%M:%S")) - tz_offset
       lastReadTime -= 900
       missingReadEntries = int((time.time() - lastReadTime) / 900)
     elif line.startswith("BWHistoryWriteEnds"):
       lastWriteTime = time.mktime(time.strptime(line[19:], "%Y-%m-%d %H:%M:%S")) - tz_offset
       lastWriteTime -= 900
       missingWriteEntries = int((time.time() - lastWriteTime) / 900)
   
   if not bwReadEntries or not bwWriteEntries or not lastReadTime or not lastWriteTime:
     msg = PREPOPULATE_FAILURE_MSG % "bandwidth stats missing from state file"
     log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
     return False
   
   # fills missing entries with the last value
   bwReadEntries += [bwReadEntries[-1]] * missingReadEntries
   bwWriteEntries += [bwWriteEntries[-1]] * missingWriteEntries
   
   # crops starting entries so they're the same size
   entryCount = min(len(bwReadEntries), len(bwWriteEntries), self.maxCol)
   bwReadEntries = bwReadEntries[len(bwReadEntries) - entryCount:]
   bwWriteEntries = bwWriteEntries[len(bwWriteEntries) - entryCount:]
   
   # gets index for 15-minute interval
   intervalIndex = 0
   for indexEntry in graphPanel.UPDATE_INTERVALS:
     if indexEntry[1] == 900: break
     else: intervalIndex += 1
   
   # fills the graphing parameters with state information
   for i in range(entryCount):
     readVal, writeVal = bwReadEntries[i], bwWriteEntries[i]
     
     self.lastPrimary, self.lastSecondary = readVal, writeVal
     
     self.prepopulatePrimaryTotal += readVal * 900
     self.prepopulateSecondaryTotal += writeVal * 900
     self.prepopulateTicks += 900
     
     self.primaryCounts[intervalIndex].insert(0, readVal)
     self.secondaryCounts[intervalIndex].insert(0, writeVal)
   
   self.maxPrimary[intervalIndex] = max(self.primaryCounts)
   self.maxSecondary[intervalIndex] = max(self.secondaryCounts)
   del self.primaryCounts[intervalIndex][self.maxCol + 1:]
   del self.secondaryCounts[intervalIndex][self.maxCol + 1:]
   
   msg = PREPOPULATE_SUCCESS_MSG
   missingSec = time.time() - min(lastReadTime, lastWriteTime)
   if missingSec: msg += " (%s is missing)" % uiTools.getTimeLabel(missingSec, 0, True)
   log.log(self._config["log.graph.bw.prepopulateSuccess"], msg)
   
   return True
Esempio n. 9
0
def validate(contents = None):
  """
  Performs validation on the given torrc contents, providing back a listing of
  (line number, issue, msg) tuples for issues found. If the issue occures on a
  multiline torrc entry then the line number is for the last line of the entry.
  
  Arguments:
    contents - torrc contents
  """
  
  conn = torTools.getConn()
  customOptions = getCustomOptions()
  issuesFound, seenOptions = [], []
  
  # Strips comments and collapses multiline multi-line entries, for more
  # information see:
  # https://trac.torproject.org/projects/tor/ticket/1929
  strippedContents, multilineBuffer = [], ""
  for line in _stripComments(contents):
    if not line: strippedContents.append("")
    else:
      line = multilineBuffer + line
      multilineBuffer = ""
      
      if line.endswith("\\"):
        multilineBuffer = line[:-1]
        strippedContents.append("")
      else:
        strippedContents.append(line.strip())
  
  for lineNumber in range(len(strippedContents) - 1, -1, -1):
    lineText = strippedContents[lineNumber]
    if not lineText: continue
    
    lineComp = lineText.split(None, 1)
    if len(lineComp) == 2: option, value = lineComp
    else: option, value = lineText, ""
    
    # Tor is case insensetive when parsing its torrc. This poses a bit of an
    # issue for us because we want all of our checks to be case insensetive
    # too but also want messages to match the normal camel-case conventions.
    #
    # Using the customOptions to account for this. It contains the tor reported
    # options (camel case) and is either a matching set or the following defaut
    # value check will fail. Hence using that hash to correct the case.
    #
    # TODO: when refactoring for stem make this less confusing...
    
    for customOpt in customOptions:
      if customOpt.lower() == option.lower():
        option = customOpt
        break
    
    # if an aliased option then use its real name
    if option in CONFIG["torrc.alias"]:
      option = CONFIG["torrc.alias"][option]
    
    # most parameters are overwritten if defined multiple times
    if option in seenOptions and not option in getMultilineParameters():
      issuesFound.append((lineNumber, ValidationError.DUPLICATE, option))
      continue
    else: seenOptions.append(option)
    
    # checks if the value isn't necessary due to matching the defaults
    if not option in customOptions:
      issuesFound.append((lineNumber, ValidationError.IS_DEFAULT, option))
    
    # replace aliases with their recognized representation
    if option in CONFIG["torrc.alias"]:
      option = CONFIG["torrc.alias"][option]
    
    # tor appears to replace tabs with a space, for instance:
    # "accept\t*:563" is read back as "accept *:563"
    value = value.replace("\t", " ")
    
    # parse value if it's a size or time, expanding the units
    value, valueType = _parseConfValue(value)
    
    # issues GETCONF to get the values tor's currently configured to use
    torValues = conn.getOption(option, [], True)
    
    # multiline entries can be comma separated values (for both tor and conf)
    valueList = [value]
    if option in getMultilineParameters():
      valueList = [val.strip() for val in value.split(",")]
      
      fetchedValues, torValues = torValues, []
      for fetchedValue in fetchedValues:
        for fetchedEntry in fetchedValue.split(","):
          fetchedEntry = fetchedEntry.strip()
          if not fetchedEntry in torValues:
            torValues.append(fetchedEntry)
    
    for val in valueList:
      # checks if both the argument and tor's value are empty
      isBlankMatch = not val and not torValues
      
      if not isBlankMatch and not val in torValues:
        # converts corrections to reader friedly size values
        displayValues = torValues
        if valueType == ValueType.SIZE:
          displayValues = [uiTools.getSizeLabel(int(val)) for val in torValues]
        elif valueType == ValueType.TIME:
          displayValues = [uiTools.getTimeLabel(int(val)) for val in torValues]
        
        issuesFound.append((lineNumber, ValidationError.MISMATCH, ", ".join(displayValues)))
  
  # checks if any custom options are missing from the torrc
  for option in customOptions:
    # In new versions the 'DirReqStatistics' option is true by default and
    # disabled on startup if geoip lookups are unavailable. If this option is
    # missing then that's most likely the reason.
    #
    # https://trac.torproject.org/projects/tor/ticket/4237
    
    if option == "DirReqStatistics": continue
    
    if not option in seenOptions:
      issuesFound.append((None, ValidationError.MISSING, option))
  
  return issuesFound
Esempio n. 10
0
    def draw(self, subwindow, width, height):
        """ Redraws graph panel """

        if self.currentDisplay:
            param = self.stats[self.currentDisplay]
            graphCol = min((width - 10) / 2, param.maxCol)

            primaryColor = uiTools.getColor(param.getColor(True))
            secondaryColor = uiTools.getColor(param.getColor(False))

            if self.showLabel:
                self.addstr(0, 0, param.getTitle(width), curses.A_STANDOUT)

            # top labels
            left, right = param.getHeaderLabel(width / 2,
                                               True), param.getHeaderLabel(
                                                   width / 2, False)
            if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor)
            if right:
                self.addstr(1, graphCol + 5, right,
                            curses.A_BOLD | secondaryColor)

            # determines max/min value on the graph
            if self.bounds == BOUNDS_GLOBAL_MAX:
                primaryMaxBound = int(param.maxPrimary[self.updateInterval])
                secondaryMaxBound = int(
                    param.maxSecondary[self.updateInterval])
            else:
                # both BOUNDS_LOCAL_MAX and BOUNDS_TIGHT use local maxima
                if graphCol < 2:
                    # nothing being displayed
                    primaryMaxBound, secondaryMaxBound = 0, 0
                else:
                    primaryMaxBound = int(
                        max(param.primaryCounts[self.updateInterval]
                            [1:graphCol + 1]))
                    secondaryMaxBound = int(
                        max(param.secondaryCounts[self.updateInterval]
                            [1:graphCol + 1]))

            primaryMinBound = secondaryMinBound = 0
            if self.bounds == BOUNDS_TIGHT:
                primaryMinBound = int(
                    min(param.primaryCounts[self.updateInterval][1:graphCol +
                                                                 1]))
                secondaryMinBound = int(
                    min(param.secondaryCounts[self.updateInterval][1:graphCol +
                                                                   1]))

                # if the max = min (ie, all values are the same) then use zero lower
                # bound so a graph is still displayed
                if primaryMinBound == primaryMaxBound: primaryMinBound = 0
                if secondaryMinBound == secondaryMaxBound:
                    secondaryMinBound = 0

            # displays upper and lower bounds
            self.addstr(2, 0, "%4i" % primaryMaxBound, primaryColor)
            self.addstr(self.graphHeight + 1, 0, "%4i" % primaryMinBound,
                        primaryColor)

            self.addstr(2, graphCol + 5, "%4i" % secondaryMaxBound,
                        secondaryColor)
            self.addstr(self.graphHeight + 1, graphCol + 5,
                        "%4i" % secondaryMinBound, secondaryColor)

            # displays intermediate bounds on every other row
            if CONFIG["features.graph.showIntermediateBounds"]:
                ticks = (self.graphHeight - 3) / 2
                for i in range(ticks):
                    row = self.graphHeight - (2 * i) - 3
                    if self.graphHeight % 2 == 0 and i >= (ticks / 2): row -= 1

                    if primaryMinBound != primaryMaxBound:
                        primaryVal = (primaryMaxBound - primaryMinBound) / (
                            self.graphHeight - 1) * (self.graphHeight - row -
                                                     1)
                        if not primaryVal in (primaryMinBound,
                                              primaryMaxBound):
                            self.addstr(row + 2, 0, "%4i" % primaryVal,
                                        primaryColor)

                    if secondaryMinBound != secondaryMaxBound:
                        secondaryVal = (secondaryMaxBound - secondaryMinBound
                                        ) / (self.graphHeight -
                                             1) * (self.graphHeight - row - 1)
                        if not secondaryVal in (secondaryMinBound,
                                                secondaryMaxBound):
                            self.addstr(row + 2, graphCol + 5,
                                        "%4i" % secondaryVal, secondaryColor)

            # creates bar graph (both primary and secondary)
            for col in range(graphCol):
                colCount = int(param.primaryCounts[self.updateInterval][
                    col + 1]) - primaryMinBound
                colHeight = min(
                    self.graphHeight, self.graphHeight * colCount /
                    (max(1, primaryMaxBound) - primaryMinBound))
                for row in range(colHeight):
                    self.addstr(self.graphHeight + 1 - row, col + 5, " ",
                                curses.A_STANDOUT | primaryColor)

                colCount = int(param.secondaryCounts[self.updateInterval][
                    col + 1]) - secondaryMinBound
                colHeight = min(
                    self.graphHeight, self.graphHeight * colCount /
                    (max(1, secondaryMaxBound) - secondaryMinBound))
                for row in range(colHeight):
                    self.addstr(self.graphHeight + 1 - row,
                                col + graphCol + 10, " ",
                                curses.A_STANDOUT | secondaryColor)

            # bottom labeling of x-axis
            intervalSec = 1  # seconds per labeling
            for i in range(len(UPDATE_INTERVALS)):
                if i == self.updateInterval:
                    intervalSec = UPDATE_INTERVALS[i][1]

            intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5
            unitsLabel, decimalPrecision = None, 0
            for i in range((graphCol - 4) / intervalSpacing):
                loc = (i + 1) * intervalSpacing
                timeLabel = uiTools.getTimeLabel(loc * intervalSec,
                                                 decimalPrecision)

                if not unitsLabel: unitsLabel = timeLabel[-1]
                elif unitsLabel != timeLabel[-1]:
                    # upped scale so also up precision of future measurements
                    unitsLabel = timeLabel[-1]
                    decimalPrecision += 1
                else:
                    # if constrained on space then strips labeling since already provided
                    timeLabel = timeLabel[:-1]

                self.addstr(self.graphHeight + 2, 4 + loc, timeLabel,
                            primaryColor)
                self.addstr(self.graphHeight + 2, graphCol + 10 + loc,
                            timeLabel, secondaryColor)

            param.draw(self, width,
                       height)  # allows current stats to modify the display
Esempio n. 11
0
 def draw(self, width, height):
   """ Redraws graph panel """
   
   if self.currentDisplay:
     param = self.getAttr("stats")[self.currentDisplay]
     graphCol = min((width - 10) / 2, param.maxCol)
     
     primaryColor = uiTools.getColor(param.getColor(True))
     secondaryColor = uiTools.getColor(param.getColor(False))
     
     if self.isTitleVisible(): self.addstr(0, 0, param.getTitle(width), curses.A_STANDOUT)
     
     # top labels
     left, right = param.getHeaderLabel(width / 2, True), param.getHeaderLabel(width / 2, False)
     if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor)
     if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor)
     
     # determines max/min value on the graph
     if self.bounds == Bounds.GLOBAL_MAX:
       primaryMaxBound = int(param.maxPrimary[self.updateInterval])
       secondaryMaxBound = int(param.maxSecondary[self.updateInterval])
     else:
       # both Bounds.LOCAL_MAX and Bounds.TIGHT use local maxima
       if graphCol < 2:
         # nothing being displayed
         primaryMaxBound, secondaryMaxBound = 0, 0
       else:
         primaryMaxBound = int(max(param.primaryCounts[self.updateInterval][1:graphCol + 1]))
         secondaryMaxBound = int(max(param.secondaryCounts[self.updateInterval][1:graphCol + 1]))
     
     primaryMinBound = secondaryMinBound = 0
     if self.bounds == Bounds.TIGHT:
       primaryMinBound = int(min(param.primaryCounts[self.updateInterval][1:graphCol + 1]))
       secondaryMinBound = int(min(param.secondaryCounts[self.updateInterval][1:graphCol + 1]))
       
       # if the max = min (ie, all values are the same) then use zero lower
       # bound so a graph is still displayed
       if primaryMinBound == primaryMaxBound: primaryMinBound = 0
       if secondaryMinBound == secondaryMaxBound: secondaryMinBound = 0
     
     # displays upper and lower bounds
     self.addstr(2, 0, "%4i" % primaryMaxBound, primaryColor)
     self.addstr(self.graphHeight + 1, 0, "%4i" % primaryMinBound, primaryColor)
     
     self.addstr(2, graphCol + 5, "%4i" % secondaryMaxBound, secondaryColor)
     self.addstr(self.graphHeight + 1, graphCol + 5, "%4i" % secondaryMinBound, secondaryColor)
     
     # displays intermediate bounds on every other row
     if CONFIG["features.graph.showIntermediateBounds"]:
       ticks = (self.graphHeight - 3) / 2
       for i in range(ticks):
         row = self.graphHeight - (2 * i) - 3
         if self.graphHeight % 2 == 0 and i >= (ticks / 2): row -= 1
         
         if primaryMinBound != primaryMaxBound:
           primaryVal = (primaryMaxBound - primaryMinBound) * (self.graphHeight - row - 1) / (self.graphHeight - 1)
           if not primaryVal in (primaryMinBound, primaryMaxBound): self.addstr(row + 2, 0, "%4i" % primaryVal, primaryColor)
         
         if secondaryMinBound != secondaryMaxBound:
           secondaryVal = (secondaryMaxBound - secondaryMinBound) * (self.graphHeight - row - 1) / (self.graphHeight - 1)
           if not secondaryVal in (secondaryMinBound, secondaryMaxBound): self.addstr(row + 2, graphCol + 5, "%4i" % secondaryVal, secondaryColor)
     
     # creates bar graph (both primary and secondary)
     for col in range(graphCol):
       colCount = int(param.primaryCounts[self.updateInterval][col + 1]) - primaryMinBound
       colHeight = min(self.graphHeight, self.graphHeight * colCount / (max(1, primaryMaxBound) - primaryMinBound))
       for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + 5, " ", curses.A_STANDOUT | primaryColor)
       
       colCount = int(param.secondaryCounts[self.updateInterval][col + 1]) - secondaryMinBound
       colHeight = min(self.graphHeight, self.graphHeight * colCount / (max(1, secondaryMaxBound) - secondaryMinBound))
       for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor)
     
     # bottom labeling of x-axis
     intervalSec = 1 # seconds per labeling
     for i in range(len(UPDATE_INTERVALS)):
       if i == self.updateInterval: intervalSec = UPDATE_INTERVALS[i][1]
     
     intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5
     unitsLabel, decimalPrecision = None, 0
     for i in range((graphCol - 4) / intervalSpacing):
       loc = (i + 1) * intervalSpacing
       timeLabel = uiTools.getTimeLabel(loc * intervalSec, decimalPrecision)
       
       if not unitsLabel: unitsLabel = timeLabel[-1]
       elif unitsLabel != timeLabel[-1]:
         # upped scale so also up precision of future measurements
         unitsLabel = timeLabel[-1]
         decimalPrecision += 1
       else:
         # if constrained on space then strips labeling since already provided
         timeLabel = timeLabel[:-1]
       
       self.addstr(self.graphHeight + 2, 4 + loc, timeLabel, primaryColor)
       self.addstr(self.graphHeight + 2, graphCol + 10 + loc, timeLabel, secondaryColor)
       
     param.draw(self, width, height) # allows current stats to modify the display
    def prepopulateFromState(self):
        """
    Attempts to use tor's state file to prepopulate values for the 15 minute
    interval via the BWHistoryReadValues/BWHistoryWriteValues values. This
    returns True if successful and False otherwise.
    """

        # checks that this is a relay (if ORPort is unset, then skip)
        conn = torTools.getConn()
        orPort = conn.getOption("ORPort")
        if orPort == "0": return

        # gets the uptime (using the same parameters as the header panel to take
        # advantage of caching
        uptime = None
        queryPid = conn.getMyPid()
        if queryPid:
            queryParam = ["%cpu", "rss", "%mem", "etime"]
            queryCmd = "ps -p %s -o %s" % (queryPid, ",".join(queryParam))
            psCall = sysTools.call(queryCmd, 3600, True)

            if psCall and len(psCall) == 2:
                stats = psCall[1].strip().split()
                if len(stats) == 4: uptime = stats[3]

        # checks if tor has been running for at least a day, the reason being that
        # the state tracks a day's worth of data and this should only prepopulate
        # results associated with this tor instance
        if not uptime or not "-" in uptime:
            msg = PREPOPULATE_FAILURE_MSG % "insufficient uptime"
            log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
            return False

        # get the user's data directory (usually '~/.tor')
        dataDir = conn.getOption("DataDirectory")
        if not dataDir:
            msg = PREPOPULATE_FAILURE_MSG % "data directory not found"
            log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
            return False

        # attempt to open the state file
        try:
            stateFile = open("%s%s/state" % (conn.getPathPrefix(), dataDir),
                             "r")
        except IOError:
            msg = PREPOPULATE_FAILURE_MSG % "unable to read the state file"
            log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
            return False

        # get the BWHistory entries (ordered oldest to newest) and number of
        # intervals since last recorded
        bwReadEntries, bwWriteEntries = None, None
        missingReadEntries, missingWriteEntries = None, None

        # converts from gmt to local with respect to DST
        tz_offset = time.altzone if time.localtime()[8] else time.timezone

        for line in stateFile:
            line = line.strip()

            # According to the rep_hist_update_state() function the BWHistory*Ends
            # correspond to the start of the following sampling period. Also, the
            # most recent values of BWHistory*Values appear to be an incremental
            # counter for the current sampling period. Hence, offsets are added to
            # account for both.

            if line.startswith("BWHistoryReadValues"):
                bwReadEntries = line[20:].split(",")
                bwReadEntries = [
                    int(entry) / 1024.0 / 900 for entry in bwReadEntries
                ]
                bwReadEntries.pop()
            elif line.startswith("BWHistoryWriteValues"):
                bwWriteEntries = line[21:].split(",")
                bwWriteEntries = [
                    int(entry) / 1024.0 / 900 for entry in bwWriteEntries
                ]
                bwWriteEntries.pop()
            elif line.startswith("BWHistoryReadEnds"):
                lastReadTime = time.mktime(
                    time.strptime(line[18:], "%Y-%m-%d %H:%M:%S")) - tz_offset
                lastReadTime -= 900
                missingReadEntries = int((time.time() - lastReadTime) / 900)
            elif line.startswith("BWHistoryWriteEnds"):
                lastWriteTime = time.mktime(
                    time.strptime(line[19:], "%Y-%m-%d %H:%M:%S")) - tz_offset
                lastWriteTime -= 900
                missingWriteEntries = int((time.time() - lastWriteTime) / 900)

        if not bwReadEntries or not bwWriteEntries or not lastReadTime or not lastWriteTime:
            msg = PREPOPULATE_FAILURE_MSG % "bandwidth stats missing from state file"
            log.log(self._config["log.graph.bw.prepopulateFailure"], msg)
            return False

        # fills missing entries with the last value
        bwReadEntries += [bwReadEntries[-1]] * missingReadEntries
        bwWriteEntries += [bwWriteEntries[-1]] * missingWriteEntries

        # crops starting entries so they're the same size
        entryCount = min(len(bwReadEntries), len(bwWriteEntries), self.maxCol)
        bwReadEntries = bwReadEntries[len(bwReadEntries) - entryCount:]
        bwWriteEntries = bwWriteEntries[len(bwWriteEntries) - entryCount:]

        # gets index for 15-minute interval
        intervalIndex = 0
        for indexEntry in graphPanel.UPDATE_INTERVALS:
            if indexEntry[1] == 900: break
            else: intervalIndex += 1

        # fills the graphing parameters with state information
        for i in range(entryCount):
            readVal, writeVal = bwReadEntries[i], bwWriteEntries[i]

            self.lastPrimary, self.lastSecondary = readVal, writeVal

            self.prepopulatePrimaryTotal += readVal * 900
            self.prepopulateSecondaryTotal += writeVal * 900
            self.prepopulateTicks += 900

            self.primaryCounts[intervalIndex].insert(0, readVal)
            self.secondaryCounts[intervalIndex].insert(0, writeVal)

        self.maxPrimary[intervalIndex] = max(self.primaryCounts)
        self.maxSecondary[intervalIndex] = max(self.secondaryCounts)
        del self.primaryCounts[intervalIndex][self.maxCol + 1:]
        del self.secondaryCounts[intervalIndex][self.maxCol + 1:]

        msg = PREPOPULATE_SUCCESS_MSG
        missingSec = time.time() - min(lastReadTime, lastWriteTime)
        if missingSec:
            msg += " (%s is missing)" % uiTools.getTimeLabel(
                missingSec, 0, True)
        log.log(self._config["log.graph.bw.prepopulateSuccess"], msg)

        return True
Esempio n. 13
0
 def draw(self, subwindow, width, height):
   self.connectionsLock.acquire()
   try:
     # hostnames frequently get updated so frequent sorting needed
     if self.listingType == LIST_HOSTNAME: self.sortConnections()
     
     if self.showLabel:
       # notes the number of connections for each type if above zero
       countLabel = ""
       for i in range(len(self.connectionCount)):
         if self.connectionCount[i] > 0: countLabel += "%i %s, " % (self.connectionCount[i], CONN_COUNT_LABELS[i])
       if countLabel: countLabel = " (%s)" % countLabel[:-2] # strips ending ", " and encases in parentheses
       self.addstr(0, 0, "Connections%s:" % countLabel, curses.A_STANDOUT)
     
     if self.connections:
       listingHeight = height - 1
       currentTime = time.time() if not self.isPaused else self.pauseTime
       
       if self.showingDetails:
         listingHeight -= 8
         isScrollBarVisible = len(self.connections) > height - 9
         if width > 80: subwindow.hline(8, 80, curses.ACS_HLINE, width - 81)
       else:
         isScrollBarVisible = len(self.connections) > height - 1
       xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
       
       # ensure cursor location and scroll top are within bounds
       self.cursorLoc = max(min(self.cursorLoc, len(self.connections) - 1), 0)
       self.scroll = max(min(self.scroll, len(self.connections) - listingHeight), 0)
       
       if self.isCursorEnabled:
         # update cursorLoc with selection (or vice versa if selection not found)
         if self.cursorSelection not in self.connections:
           self.cursorSelection = self.connections[self.cursorLoc]
         else: self.cursorLoc = self.connections.index(self.cursorSelection)
         
         # shift scroll if necessary for cursor to be visible
         if self.cursorLoc < self.scroll: self.scroll = self.cursorLoc
         elif self.cursorLoc - listingHeight + 1 > self.scroll: self.scroll = self.cursorLoc - listingHeight + 1
       
       lineNum = (-1 * self.scroll) + 1
       for entry in self.connections:
         if lineNum >= 1:
           type = entry[CONN_TYPE]
           isPrivate = entry[CONN_PRIVATE]
           color = TYPE_COLORS[type]
           
           # adjustments to measurements for 'xOffset' are to account for scroll bar
           if self.listingType == LIST_IP:
             # base data requires 73 characters
             src = "%s:%s" % (entry[CONN_L_IP], entry[CONN_L_PORT])
             dst = "%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
             
             if isPrivate: dst = "<scrubbed>"
             
             src, dst = "%-21s" % src, "%-26s" % dst
             
             etc = ""
             if width > 115 + xOffset:
               # show fingerprint (column width: 42 characters)
               etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
               
             if width > 127 + xOffset:
               # show nickname (column width: remainder)
               nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
               nicknameSpace = width - 118 - xOffset
               
               # truncates if too long
               if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
               
               etc += ("%%-%is  " % nicknameSpace) % nickname
           elif self.listingType == LIST_HOSTNAME:
             # base data requires 80 characters
             src = "localhost:%-5s" % entry[CONN_L_PORT]
             
             # space available for foreign hostname (stretched to claim any free space)
             foreignHostnameSpace = width - 42 - xOffset
             
             etc = ""
             if width > 102 + xOffset:
               # shows ip/locale (column width: 22 characters)
               foreignHostnameSpace -= 22
               
               if isPrivate: ipEntry = "<scrubbed>"
               else: ipEntry = "%s %s" % (entry[CONN_F_IP], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
               etc += "%-20s  " % ipEntry
             
             if width > 134 + xOffset:
               # show fingerprint (column width: 42 characters)
               foreignHostnameSpace -= 42
               etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
             
             if width > 151 + xOffset:
               # show nickname (column width: min 17 characters, uses half of the remainder)
               nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
               nicknameSpace = 15 + (width - xOffset - 151) / 2
               foreignHostnameSpace -= (nicknameSpace + 2)
               
               if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
               etc += ("%%-%is  " % nicknameSpace) % nickname
             
             if isPrivate: dst = "<scrubbed>"
             else:
               try: hostname = hostnames.resolve(entry[CONN_F_IP])
               except ValueError: hostname = None
               
               # truncates long hostnames
               portDigits = len(str(entry[CONN_F_PORT]))
               if hostname and (len(hostname) + portDigits) > foreignHostnameSpace - 1:
                 hostname = hostname[:(foreignHostnameSpace - portDigits - 4)] + "..."
               
               dst = "%s:%s" % (hostname if hostname else entry[CONN_F_IP], entry[CONN_F_PORT])
             
             dst = ("%%-%is" % foreignHostnameSpace) % dst
           elif self.listingType == LIST_FINGERPRINT:
             # base data requires 75 characters
             src = "localhost"
             if entry[CONN_TYPE] == "control": dst = "localhost"
             else: dst = self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
             dst = "%-40s" % dst
             
             etc = ""
             if width > 92 + xOffset:
               # show nickname (column width: min 17 characters, uses remainder if extra room's available)
               nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
               nicknameSpace = width - 78 - xOffset if width < 126 else width - 106 - xOffset
               if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
               etc += ("%%-%is  " % nicknameSpace) % nickname
             
             if width > 125 + xOffset:
               # shows ip/port/locale (column width: 28 characters)
               if isPrivate: ipEntry = "<scrubbed>"
               else: ipEntry = "%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
               etc += "%-26s  " % ipEntry
           else:
             # base data uses whatever extra room's available (using minimun of 50 characters)
             src = self.nickname
             if entry[CONN_TYPE] == "control": dst = self.nickname
             else: dst = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
             
             # space available for foreign nickname
             foreignNicknameSpace = width - len(self.nickname) - 27 - xOffset
             
             etc = ""
             if width > 92 + xOffset:
               # show fingerprint (column width: 42 characters)
               foreignNicknameSpace -= 42
               etc += "%-40s  " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
             
             if width > 120 + xOffset:
               # shows ip/port/locale (column width: 28 characters)
               foreignNicknameSpace -= 28
               
               if isPrivate: ipEntry = "<scrubbed>"
               else: ipEntry = "%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
               etc += "%-26s  " % ipEntry
             
             dst = ("%%-%is" % foreignNicknameSpace) % dst
           
           timeLabel = uiTools.getTimeLabel(currentTime - entry[CONN_TIME], 1)
           if type == "inbound": src, dst = dst, src
           elif type == "family" and int(entry[CONN_L_PORT]) > 65535:
             # this belongs to an unresolved family entry - replaces invalid data with "UNKNOWN"
             timeLabel = "---"
             
             if self.listingType == LIST_IP:
               src = "%-21s" % "UNKNOWN"
               dst = "%-26s" % "UNKNOWN"
             elif self.listingType == LIST_HOSTNAME:
               src = "%-15s" % "UNKNOWN"
               dst = ("%%-%is" % len(dst)) % "UNKNOWN"
               if len(etc) > 0: etc = etc.replace("256.255.255.255 (??)", "UNKNOWN" + " " * 13)
             else:
               ipStart = etc.find("256")
               if ipStart > -1: etc = etc[:ipStart] + ("%%-%is" % len(etc[ipStart:])) % "UNKNOWN"
           
           padding = width - (len(src) + len(dst) + len(etc) + 27) - xOffset # padding needed to fill full line
           lineEntry = "<%s>%s  -->  %s  %s%s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, " " * padding, timeLabel, type.upper(), " " * (9 - len(type)), color)
           
           if self.isCursorEnabled and entry == self.cursorSelection:
             lineEntry = "<h>%s</h>" % lineEntry
           
           yOffset = 0 if not self.showingDetails else 8
           self.addfstr(lineNum + yOffset, xOffset, lineEntry)
         lineNum += 1
       
       if isScrollBarVisible:
         topY = 9 if self.showingDetails else 1
         bottomEntry = self.scroll + height - 9 if self.showingDetails else self.scroll + height - 1
         self.addScrollBar(self.scroll, bottomEntry, len(self.connections), topY)
   finally:
     self.connectionsLock.release()