예제 #1
0
  def __init__(self, clone = None):
    GraphCategory.__init__(self, clone)
    self._title_last_updated = None

    if not clone:
      # fill in past bandwidth information

      controller = tor_controller()
      bw_entries, is_successful = controller.get_info('bw-event-cache', None), True

      if bw_entries:
        for entry in bw_entries.split():
          entry_comp = entry.split(',')

          if len(entry_comp) != 2 or not entry_comp[0].isdigit() or not entry_comp[1].isdigit():
            log.warn("Tor's 'GETINFO bw-event-cache' provided malformed output: %s" % bw_entries)
            is_successful = False
            break

          self.primary.update(int(entry_comp[0]))
          self.secondary.update(int(entry_comp[1]))

        if is_successful:
          log.info('Bandwidth graph has information for the last %s' % str_tools.time_label(len(bw_entries.split()), is_long = True))

      read_total = controller.get_info('traffic/read', None)
      write_total = controller.get_info('traffic/written', None)
      start_time = system.start_time(controller.get_pid(None))

      if read_total and write_total and start_time:
        self.primary.total = int(read_total)
        self.secondary.total = int(write_total)
        self.start_time = start_time
예제 #2
0
파일: torConfig.py 프로젝트: gsathya/arm
 def getCorrections(self):
   """
   Performs validation on the loaded contents and provides back the
   corrections. If validation is disabled then this won't provide any
   results.
   """
   
   self.valsLock.acquire()
   
   if not self.isLoaded(): returnVal = None
   else:
     torVersion = torTools.getConn().getVersion()
     skipValidation = not CONFIG["features.torrc.validate"]
     skipValidation |= (torVersion is None or not torVersion.meets_requirements(stem.version.Requirement.GETINFO_CONFIG_TEXT))
     
     if skipValidation:
       log.info("Skipping torrc validation (requires tor 0.2.2.7-alpha)")
       returnVal = {}
     else:
       if self.corrections == None:
         self.corrections = validate(self.contents)
       
       returnVal = list(self.corrections)
   
   self.valsLock.release()
   return returnVal
예제 #3
0
    def getCorrections(self):
        """
    Performs validation on the loaded contents and provides back the
    corrections. If validation is disabled then this won't provide any
    results.
    """

        self.valsLock.acquire()

        if not self.isLoaded(): returnVal = None
        else:
            torVersion = torTools.getConn().getVersion()
            skipValidation = not CONFIG["features.torrc.validate"]
            skipValidation |= (
                torVersion is None or not torVersion.meets_requirements(
                    stem.version.Requirement.GETINFO_CONFIG_TEXT))

            if skipValidation:
                log.info(
                    "Skipping torrc validation (requires tor 0.2.2.7-alpha)")
                returnVal = {}
            else:
                if self.corrections == None:
                    self.corrections = validate(self.contents)

                returnVal = list(self.corrections)

        self.valsLock.release()
        return returnVal
예제 #4
0
def send_message(control_file, message, raw=False):
    """
  Sends a message to the control socket, adding the expected formatting for
  single verses multi-line messages. Neither message type should contain an
  ending newline (if so it'll be treated as a multi-line message with a blank
  line at the end). If the message doesn't contain a newline then it's sent
  as...

  ::

    <message>\\r\\n

  and if it does contain newlines then it's split on ``\\n`` and sent as...

  ::

    +<line 1>\\r\\n
    <line 2>\\r\\n
    <line 3>\\r\\n
    .\\r\\n

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)
  :param str message: message to be sent on the control socket
  :param bool raw: leaves the message formatting untouched, passing it to the
    socket as-is

  :raises:
    * :class:`stem.SocketError` if a problem arises in using the socket
    * :class:`stem.SocketClosed` if the socket is known to be shut down
  """

    if not raw:
        message = send_formatting(message)

    try:
        control_file.write(stem.util.str_tools._to_bytes(message))
        control_file.flush()

        if log.is_tracing():
            log_message = message.replace('\r\n', '\n').rstrip()
            msg_div = '\n' if '\n' in log_message else ' '
            log.trace('Sent to tor:%s%s' % (msg_div, log_message))
    except socket.error as exc:
        log.info('Failed to send message: %s' % exc)

        # When sending there doesn't seem to be a reliable method for
        # distinguishing between failures from a disconnect verses other things.
        # Just accounting for known disconnection responses.

        if str(exc) == '[Errno 32] Broken pipe':
            raise stem.SocketClosed(exc)
        else:
            raise stem.SocketError(exc)
    except AttributeError:
        # if the control_file has been closed then flush will receive:
        # AttributeError: 'NoneType' object has no attribute 'sendall'

        log.info('Failed to send message: file has been closed')
        raise stem.SocketClosed('file has been closed')
예제 #5
0
파일: graph.py 프로젝트: sammyshj/nyx
  def __init__(self, clone = None):
    GraphCategory.__init__(self, clone)

    if not clone:
      # fill in past bandwidth information

      controller = tor_controller()
      bw_entries, is_successful = controller.get_info('bw-event-cache', None), True

      if bw_entries:
        for entry in bw_entries.split():
          entry_comp = entry.split(',')

          if len(entry_comp) != 2 or not entry_comp[0].isdigit() or not entry_comp[1].isdigit():
            log.warn(msg('panel.graphing.bw_event_cache_malformed', response = bw_entries))
            is_successful = False
            break

          self.primary.update(int(entry_comp[0]))
          self.secondary.update(int(entry_comp[1]))

        if is_successful:
          log.info(msg('panel.graphing.prepopulation_successful', duration = str_tools.time_label(len(bw_entries.split()), is_long = True)))

      read_total = controller.get_info('traffic/read', None)
      write_total = controller.get_info('traffic/written', None)
      start_time = system.start_time(controller.get_pid(None))

      if read_total and write_total and start_time:
        self.primary.total = int(read_total)
        self.secondary.total = int(write_total)
        self.start_time = start_time
예제 #6
0
파일: socket.py 프로젝트: Fuzew/sharp-stem
def send_message(control_file, message, raw = False):
  """
  Sends a message to the control socket, adding the expected formatting for
  single verses multi-line messages. Neither message type should contain an
  ending newline (if so it'll be treated as a multi-line message with a blank
  line at the end). If the message doesn't contain a newline then it's sent
  as...

  ::

    <message>\\r\\n

  and if it does contain newlines then it's split on ``\\n`` and sent as...

  ::

    +<line 1>\\r\\n
    <line 2>\\r\\n
    <line 3>\\r\\n
    .\\r\\n

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)
  :param str message: message to be sent on the control socket
  :param bool raw: leaves the message formatting untouched, passing it to the
    socket as-is

  :raises:
    * :class:`stem.SocketError` if a problem arises in using the socket
    * :class:`stem.SocketClosed` if the socket is known to be shut down
  """

  if not raw:
    message = send_formatting(message)

  try:
    control_file.write(stem.util.str_tools._to_bytes(message))
    control_file.flush()

    log_message = message.replace('\r\n', '\n').rstrip()
    log.trace('Sent to tor:\n' + log_message)
  except socket.error as exc:
    log.info('Failed to send message: %s' % exc)

    # When sending there doesn't seem to be a reliable method for
    # distinguishing between failures from a disconnect verses other things.
    # Just accounting for known disconnection responses.

    if str(exc) == '[Errno 32] Broken pipe':
      raise stem.SocketClosed(exc)
    else:
      raise stem.SocketError(exc)
  except AttributeError:
    # if the control_file has been closed then flush will receive:
    # AttributeError: 'NoneType' object has no attribute 'sendall'

    log.info('Failed to send message: file has been closed')
    raise stem.SocketClosed('file has been closed')
예제 #7
0
파일: hostnames.py 프로젝트: gsathya/arm
 def _workerLoop(self):
   """
   Simple producer-consumer loop followed by worker threads. This takes
   addresses from the unresolvedQueue, attempts to look up its hostname, and
   adds its results or the error to the resolved cache. Resolver reference
   provides shared resources used by the thread pool.
   """
   
   while not self.halt:
     # if resolver is paused then put a hold on further resolutions
     if self.isPaused:
       self.cond.acquire()
       if not self.halt: self.cond.wait(1)
       self.cond.release()
       continue
     
     # snags next available ip, timeout is because queue can't be woken up
     # when 'halt' is set
     try: ipAddr = self.unresolvedQueue.get_nowait()
     except Queue.Empty:
       # no elements ready, wait a little while and try again
       self.cond.acquire()
       if not self.halt: self.cond.wait(1)
       self.cond.release()
       continue
     if self.halt: break
     
     try:
       if self.useSocketResolution: result = _resolveViaSocket(ipAddr)
       else: result = _resolveViaHost(ipAddr)
     except IOError, exc: result = exc # lookup failed
     except ValueError, exc: result = exc # dns error
     
     self.resolvedLock.acquire()
     self.resolvedCache[ipAddr] = (result, RESOLVER_COUNTER.next())
     
     # trim cache if excessively large (clearing out oldest entries)
     if len(self.resolvedCache) > CONFIG["cache.hostnames.size"]:
       # Providing for concurrent, non-blocking calls require that entries are
       # never removed from the cache, so this creates a new, trimmed version
       # instead.
       
       # determines minimum age of entries to be kept
       currentCount = RESOLVER_COUNTER.next()
       newCacheSize = CONFIG["cache.hostnames.size"] - CONFIG["cache.hostnames.trimSize"]
       threshold = currentCount - newCacheSize
       newCache = {}
       
       msg = "trimming hostname cache from %i entries to %i" % (len(self.resolvedCache), newCacheSize)
       log.info(msg)
       
       # checks age of each entry, adding to toDelete if too old
       for ipAddr, entry in self.resolvedCache.iteritems():
         if entry[1] >= threshold: newCache[ipAddr] = entry
       
       self.resolvedCache = newCache
     
     self.resolvedLock.release()
예제 #8
0
파일: starter.py 프로젝트: patacca/nyx
def _notify_of_unknown_events():
  """
  Provides a notice about any event types tor supports but nyx doesn't.
  """

  missing_events = nyx.arguments.missing_event_types()

  if missing_events:
    log.info('setup.unknown_event_types', event_types = ', '.join(missing_events))
예제 #9
0
def manual(option):
    result = stem.manual.query(
        'SELECT category, usage, summary, description, position FROM torrc WHERE key=?',
        option.upper()).fetchone()

    if result:
        return ManualEntry(*result)
    else:
        log.info("No manual information found for '%s'" % option)
        return None
예제 #10
0
파일: starter.py 프로젝트: patacca/nyx
def _setup_freebsd_chroot(controller, config):
  """
  If we're running under FreeBSD then check the system for a chroot path.
  """

  if not config.get('tor.chroot', None) and platform.system() == 'FreeBSD':
    jail_chroot = stem.util.system.bsd_jail_path(controller.get_pid(0))

    if jail_chroot and os.path.exists(jail_chroot):
      log.info('setup.set_freebsd_chroot', path = jail_chroot)
      config.set('tor.chroot', jail_chroot)
예제 #11
0
def _setup_freebsd_chroot(controller, config):
  """
  If we're running under FreeBSD then check the system for a chroot path.
  """

  if not config.get('tor.chroot', None) and platform.system() == 'FreeBSD':
    jail_chroot = stem.util.system.bsd_jail_path(controller.get_pid(0))

    if jail_chroot and os.path.exists(jail_chroot):
      log.info('setup.set_freebsd_chroot', path = jail_chroot)
      config.set('tor.chroot', jail_chroot)
예제 #12
0
 def __init__(self, processName, processPid = "", resolveRate = None, handle = None):
   """
   Initializes a new resolver daemon. When no longer needed it's suggested
   that this is stopped.
   
   Arguments:
     processName - name of the process being resolved
     processPid  - pid of the process being resolved
     resolveRate - time between resolving connections (in seconds, None if
                   chosen dynamically)
     handle      - name used to query this resolver, this is the processName
                   if undefined
   """
   
   threading.Thread.__init__(self)
   self.setDaemon(True)
   
   self.processName = processName
   self.processPid = processPid
   self.resolveRate = resolveRate
   self.handle = handle if handle else processName
   self.defaultRate = CONFIG["queries.connections.minRate"]
   self.lastLookup = -1
   self.overwriteResolver = None
   self.defaultResolver = Resolver.PROC
   
   osType = os.uname()[0]
   self.resolverOptions = getSystemResolvers(osType)
   
   log.info("Operating System: %s, Connection Resolvers: %s" % (osType, ", ".join(self.resolverOptions)))
   
   # sets the default resolver to be the first found in the system's PATH
   # (left as netstat if none are found)
   for resolver in self.resolverOptions:
     # Resolver strings correspond to their command with the exception of bsd
     # resolvers.
     resolverCmd = resolver.replace(" (bsd)", "")
     
     if resolver == Resolver.PROC or system.is_available(resolverCmd):
       self.defaultResolver = resolver
       break
   
   self._connections = []        # connection cache (latest results)
   self._resolutionCounter = 0   # number of successful connection resolutions
   self._isPaused = False
   self._halt = False            # terminates thread if true
   self._cond = threading.Condition()  # used for pausing the thread
   self._subsiquentFailures = 0  # number of failed resolutions with the default in a row
   self._resolverBlacklist = []  # resolvers that have failed to resolve
   
   # Number of sequential times the threshold rate's been too low. This is to
   # avoid having stray spikes up the rate.
   self._rateThresholdBroken = 0
예제 #13
0
def send_message(control_file, message, raw=False):
    """
  Sends a message to the control socket, adding the expected formatting for
  single verses multi-line messages. Neither message type should contain an
  ending newline (if so it'll be treated as a multi-line message with a blank
  line at the end). If the message doesn't contain a newline then it's sent
  as...

  ::

    <message>\\r\\n

  and if it does contain newlines then it's split on ``\\n`` and sent as...

  ::

    +<line 1>\\r\\n
    <line 2>\\r\\n
    <line 3>\\r\\n
    .\\r\\n

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)
  :param str message: message to be sent on the control socket
  :param bool raw: leaves the message formatting untouched, passing it to the
    socket as-is

  :raises:
    * :class:`stem.SocketError` if a problem arises in using the socket
    * :class:`stem.SocketClosed` if the socket is known to be shut down
  """

    if not raw:
        message = send_formatting(message)

    try:
        control_file.write(message)
        control_file.flush()

        log_message = message.replace("\r\n", "\n").rstrip()
        log.trace("Sent to tor:\n" + log_message)
    except socket.error, exc:
        log.info("Failed to send message: %s" % exc)

        # When sending there doesn't seem to be a reliable method for
        # distinguishing between failures from a disconnect verses other things.
        # Just accounting for known disconnection responses.

        if str(exc) == "[Errno 32] Broken pipe":
            raise stem.SocketClosed(exc)
        else:
            raise stem.SocketError(exc)
예제 #14
0
파일: uiTools.py 프로젝트: lnrsoft/arm
def _initColors():
    """
  Initializes color mappings usable by curses. This can only be done after
  calling curses.initscr().
  """

    global COLOR_ATTR_INITIALIZED, COLOR_IS_SUPPORTED
    if not COLOR_ATTR_INITIALIZED:
        # hack to replace all ACS characters with '+' if ACS support has been
        # manually disabled
        if not CONFIG["features.acsSupport"]:
            for item in curses.__dict__:
                if item.startswith("ACS_"):
                    curses.__dict__[item] = ord('+')

            # replace a few common border pipes that are better rendered as '|' or
            # '-' instead

            curses.ACS_SBSB = ord('|')
            curses.ACS_VLINE = ord('|')
            curses.ACS_BSBS = ord('-')
            curses.ACS_HLINE = ord('-')

        COLOR_ATTR_INITIALIZED = True
        COLOR_IS_SUPPORTED = False
        if not CONFIG["features.colorInterface"]: return

        try:
            COLOR_IS_SUPPORTED = curses.has_colors()
        except curses.error:
            return  # initscr hasn't been called yet

        # initializes color mappings if color support is available
        if COLOR_IS_SUPPORTED:
            colorpair = 0
            log.info("Terminal color support detected and enabled")

            for colorName in COLOR_LIST:
                fgColor = COLOR_LIST[colorName]
                bgColor = -1  # allows for default (possibly transparent) background
                colorpair += 1
                curses.init_pair(colorpair, fgColor, bgColor)
                COLOR_ATTR[colorName] = curses.color_pair(colorpair)
        else:
            log.info("Terminal color support unavailable")
예제 #15
0
파일: uiTools.py 프로젝트: gsathya/arm
def _initColors():
  """
  Initializes color mappings usable by curses. This can only be done after
  calling curses.initscr().
  """
  
  global COLOR_ATTR_INITIALIZED, COLOR_IS_SUPPORTED
  if not COLOR_ATTR_INITIALIZED:
    # hack to replace all ACS characters with '+' if ACS support has been
    # manually disabled
    if not CONFIG["features.acsSupport"]:
      for item in curses.__dict__:
        if item.startswith("ACS_"):
          curses.__dict__[item] = ord('+')
      
      # replace a few common border pipes that are better rendered as '|' or
      # '-' instead
      
      curses.ACS_SBSB = ord('|')
      curses.ACS_VLINE = ord('|')
      curses.ACS_BSBS = ord('-')
      curses.ACS_HLINE = ord('-')
    
    COLOR_ATTR_INITIALIZED = True
    COLOR_IS_SUPPORTED = False
    if not CONFIG["features.colorInterface"]: return
    
    try: COLOR_IS_SUPPORTED = curses.has_colors()
    except curses.error: return # initscr hasn't been called yet
    
    # initializes color mappings if color support is available
    if COLOR_IS_SUPPORTED:
      colorpair = 0
      log.info("Terminal color support detected and enabled")
      
      for colorName in COLOR_LIST:
        fgColor = COLOR_LIST[colorName]
        bgColor = -1 # allows for default (possibly transparent) background
        colorpair += 1
        curses.init_pair(colorpair, fgColor, bgColor)
        COLOR_ATTR[colorName] = curses.color_pair(colorpair)
    else:
      log.info("Terminal color support unavailable")
예제 #16
0
def _write_to_socket(socket_file, message):
  try:
    socket_file.write(stem.util.str_tools._to_bytes(message))
    socket_file.flush()
  except socket.error as exc:
    log.info('Failed to send: %s' % exc)

    # When sending there doesn't seem to be a reliable method for
    # distinguishing between failures from a disconnect verses other things.
    # Just accounting for known disconnection responses.

    if str(exc) == '[Errno 32] Broken pipe':
      raise stem.SocketClosed(exc)
    else:
      raise stem.SocketError(exc)
  except AttributeError:
    # if the control_file has been closed then flush will receive:
    # AttributeError: 'NoneType' object has no attribute 'sendall'

    log.info('Failed to send: file has been closed')
    raise stem.SocketClosed('file has been closed')
예제 #17
0
    def __init__(self, clone=None):
        GraphCategory.__init__(self, clone)

        if not clone:
            # fill in past bandwidth information

            controller = tor_controller()
            bw_entries, is_successful = controller.get_info(
                'bw-event-cache', None), True

            if bw_entries:
                for entry in bw_entries.split():
                    entry_comp = entry.split(',')

                    if len(entry_comp) != 2 or not entry_comp[0].isdigit(
                    ) or not entry_comp[1].isdigit():
                        log.warn(
                            msg('panel.graphing.bw_event_cache_malformed',
                                response=bw_entries))
                        is_successful = False
                        break

                    self.primary.update(int(entry_comp[0]))
                    self.secondary.update(int(entry_comp[1]))

                if is_successful:
                    log.info(
                        msg('panel.graphing.prepopulation_successful',
                            duration=str_tools.time_label(len(
                                bw_entries.split()),
                                                          is_long=True)))

            read_total = controller.get_info('traffic/read', None)
            write_total = controller.get_info('traffic/written', None)
            start_time = system.start_time(controller.get_pid(None))

            if read_total and write_total and start_time:
                self.primary.total = int(read_total)
                self.secondary.total = int(write_total)
                self.start_time = start_time
예제 #18
0
async def _write_to_socket(writer: asyncio.StreamWriter,
                           message: Union[str, bytes]) -> None:
    try:
        writer.write(stem.util.str_tools._to_bytes(message))
        await writer.drain()
    except socket.error as exc:
        log.info('Failed to send: %s' % exc)

        # When sending there doesn't seem to be a reliable method for
        # distinguishing between failures from a disconnect verses other things.
        # Just accounting for known disconnection responses.

        if str(exc) == 'Connection lost':
            raise stem.SocketClosed(exc)
        else:
            raise stem.SocketError(exc)
    except AttributeError:
        # if the control_file has been closed then flush will receive:
        # AttributeError: 'NoneType' object has no attribute 'sendall'

        log.info('Failed to send: file has been closed')
        raise stem.SocketClosed('file has been closed')
예제 #19
0
  def get_corrections(self):
    """
    Performs validation on the loaded contents and provides back the
    corrections. If validation is disabled then this won't provide any
    results.
    """

    with self._vals_lock:
      if not self.is_loaded():
        return None
      else:
        tor_version = tor_controller().get_version(None)
        skip_validation = not CONFIG['features.torrc.validate']
        skip_validation |= (tor_version is None or not tor_version >= stem.version.Requirement.GETINFO_CONFIG_TEXT)

        if skip_validation:
          log.info('Skipping torrc validation (requires tor 0.2.2.7-alpha)')
          return {}
        else:
          if self.corrections is None:
            self.corrections = validate(self.contents)

          return list(self.corrections)
예제 #20
0
 def run(self):
   while not self._halt:
     minWait = self.resolveRate if self.resolveRate else self.defaultRate
     timeSinceReset = time.time() - self.lastLookup
     
     if self._isPaused or timeSinceReset < minWait:
       sleepTime = max(0.2, minWait - timeSinceReset)
       
       self._cond.acquire()
       if not self._halt: self._cond.wait(sleepTime)
       self._cond.release()
       
       continue # done waiting, try again
     
     isDefault = self.overwriteResolver == None
     resolver = self.defaultResolver if isDefault else self.overwriteResolver
     
     # checks if there's nothing to resolve with
     if not resolver:
       self.lastLookup = time.time() # avoids a busy wait in this case
       continue
     
     try:
       resolveStart = time.time()
       connResults = getConnections(resolver, self.processName, self.processPid)
       lookupTime = time.time() - resolveStart
       
       self._connections = connResults
       self._resolutionCounter += 1
       
       newMinDefaultRate = 100 * lookupTime
       if self.defaultRate < newMinDefaultRate:
         if self._rateThresholdBroken >= 3:
           # adding extra to keep the rate from frequently changing
           self.defaultRate = newMinDefaultRate + 0.5
           
           log.trace("connection lookup time increasing to %0.1f seconds per call" % self.defaultRate)
         else: self._rateThresholdBroken += 1
       else: self._rateThresholdBroken = 0
       
       if isDefault: self._subsiquentFailures = 0
     except (ValueError, IOError), exc:
       # this logs in a couple of cases:
       # - special failures noted by getConnections (most cases are already
       # logged via system)
       # - note fail-overs for default resolution methods
       if str(exc).startswith("No results found using:"):
         log.info(exc)
       
       if isDefault:
         self._subsiquentFailures += 1
         
         if self._subsiquentFailures >= RESOLVER_FAILURE_TOLERANCE:
           # failed several times in a row - abandon resolver and move on to another
           self._resolverBlacklist.append(resolver)
           self._subsiquentFailures = 0
           
           # pick another (non-blacklisted) resolver
           newResolver = None
           for r in self.resolverOptions:
             if not r in self._resolverBlacklist:
               newResolver = r
               break
           
           if newResolver:
             # provide notice that failures have occurred and resolver is changing
             log.notice(RESOLVER_SERIAL_FAILURE_MSG % (resolver, newResolver))
           else:
             # exhausted all resolvers, give warning
             log.notice(RESOLVER_FINAL_FAILURE_MSG)
           
           self.defaultResolver = newResolver
     finally:
예제 #21
0
def recv_message(control_file):
    """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
    * :class:`stem.SocketClosed` if the socket closes before we receive
      a complete message
  """

    parsed_content, raw_content = [], b""
    logging_prefix = "Error while receiving a control message (%s): "

    while True:
        try:
            # From a real socket readline() would always provide bytes, but during
            # tests we might be given a StringIO in which case it's unicode under
            # python 3.x.

            line = stem.util.str_tools._to_bytes(control_file.readline())
        except AttributeError:
            # if the control_file has been closed then we will receive:
            # AttributeError: 'NoneType' object has no attribute 'recv'

            prefix = logging_prefix % "SocketClosed"
            log.info(prefix + "socket file has been closed")
            raise stem.SocketClosed("socket file has been closed")
        except (socket.error, ValueError) as exc:
            # When disconnected we get...
            #
            # Python 2:
            #   socket.error: [Errno 107] Transport endpoint is not connected
            #
            # Python 3:
            #   ValueError: I/O operation on closed file.

            prefix = logging_prefix % "SocketClosed"
            log.info(prefix + "received exception \"%s\"" % exc)
            raise stem.SocketClosed(exc)

        raw_content += line

        # Parses the tor control lines. These are of the form...
        # <status code><divider><content>\r\n

        if len(line) == 0:
            # if the socket is disconnected then the readline() method will provide
            # empty content

            prefix = logging_prefix % "SocketClosed"
            log.info(prefix + "empty socket content")
            raise stem.SocketClosed("Received empty socket content.")
        elif len(line) < 4:
            prefix = logging_prefix % "ProtocolError"
            log.info(prefix + "line too short, \"%s\"" % log.escape(line))
            raise stem.ProtocolError("Badly formatted reply line: too short")
        elif not re.match(b'^[a-zA-Z0-9]{3}[-+ ]', line):
            prefix = logging_prefix % "ProtocolError"
            log.info(prefix + "malformed status code/divider, \"%s\"" %
                     log.escape(line))
            raise stem.ProtocolError(
                "Badly formatted reply line: beginning is malformed")
        elif not line.endswith(b"\r\n"):
            prefix = logging_prefix % "ProtocolError"
            log.info(prefix + "no CRLF linebreak, \"%s\"" % log.escape(line))
            raise stem.ProtocolError("All lines should end with CRLF")

        line = line[:-2]  # strips off the CRLF
        status_code, divider, content = line[:3], line[3:4], line[4:]

        if stem.prereq.is_python_3():
            status_code = stem.util.str_tools._to_unicode(status_code)
            divider = stem.util.str_tools._to_unicode(divider)

        if divider == "-":
            # mid-reply line, keep pulling for more content
            parsed_content.append((status_code, divider, content))
        elif divider == " ":
            # end of the message, return the message
            parsed_content.append((status_code, divider, content))

            log_message = raw_content.replace(b"\r\n", b"\n").rstrip()
            log.trace("Received from tor:\n" +
                      stem.util.str_tools._to_unicode(log_message))

            return stem.response.ControlMessage(parsed_content, raw_content)
        elif divider == "+":
            # data entry, all of the following lines belong to the content until we
            # get a line with just a period

            while True:
                try:
                    line = stem.util.str_tools._to_bytes(
                        control_file.readline())
                except socket.error as exc:
                    prefix = logging_prefix % "SocketClosed"
                    log.info(
                        prefix +
                        "received an exception while mid-way through a data reply (exception: \"%s\", read content: \"%s\")"
                        % (exc, log.escape(raw_content)))
                    raise stem.SocketClosed(exc)

                raw_content += line

                if not line.endswith(b"\r\n"):
                    prefix = logging_prefix % "ProtocolError"
                    log.info(
                        prefix +
                        "CRLF linebreaks missing from a data reply, \"%s\"" %
                        log.escape(raw_content))
                    raise stem.ProtocolError("All lines should end with CRLF")
                elif line == b".\r\n":
                    break  # data block termination

                line = line[:-2]  # strips off the CRLF

                # lines starting with a period are escaped by a second period (as per
                # section 2.4 of the control-spec)

                if line.startswith(b".."):
                    line = line[1:]

                # appends to previous content, using a newline rather than CRLF
                # separator (more conventional for multi-line string content outside
                # the windows world)

                content += b"\n" + line

            parsed_content.append((status_code, divider, content))
        else:
            # this should never be reached due to the prefix regex, but might as well
            # be safe...
            prefix = logging_prefix % "ProtocolError"
            log.warn(prefix +
                     "\"%s\" isn't a recognized divider type" % divider)
            raise stem.ProtocolError(
                "Unrecognized divider type '%s': %s" %
                (divider, stem.util.str_tools._to_unicode(line)))
예제 #22
0
            return  # success!
        except OpenAuthRejected, exc:
            auth_exceptions.append(exc)
        except IncorrectPassword, exc:
            auth_exceptions.append(exc)
        except PasswordAuthRejected, exc:
            # Since the PROTOCOLINFO says password auth is available we can assume
            # that if PasswordAuthRejected is raised it's being raised in error.
            log.debug(
                "The authenticate_password method raised a PasswordAuthRejected when password auth should be available. Stem may need to be corrected to recognize this response: %s"
                % exc)
            auth_exceptions.append(IncorrectPassword(str(exc)))
        except AuthSecurityFailure, exc:
            log.info(
                "Tor failed to provide the nonce expected for safecookie authentication. (%s)"
                % exc)
            auth_exceptions.append(exc)
        except (InvalidClientNonce, UnrecognizedAuthChallengeMethod,
                AuthChallengeFailed), exc:
            auth_exceptions.append(exc)
        except (IncorrectCookieSize, UnreadableCookieFile,
                IncorrectCookieValue), exc:
            auth_exceptions.append(exc)
        except CookieAuthRejected, exc:
            auth_func = "authenticate_safecookie" if exc.is_safecookie else "authenticate_cookie"

            log.debug(
                "The %s method raised a CookieAuthRejected when cookie auth should be available. Stem may need to be corrected to recognize this response: %s"
                % (auth_func, exc))
            auth_exceptions.append(
예제 #23
0
def drawTorMonitor(stdscr, startTime):
    """
  Main draw loop context.
  
  Arguments:
    stdscr    - curses window
    startTime - unix time for when arm was started
  """

    initController(stdscr, startTime)
    control = getController()

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

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

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

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

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

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

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

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

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

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

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

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

            if confirmationKey in (ord('x'), ord('X')):
                try:
                    torTools.getConn().reload()
                except IOError, exc:
                    log.error("Error detected when reloading tor: %s" %
                              sysTools.getFileErrorMsg(exc))
        elif key == ord('h') or key == ord('H'):
            overrideKey = cli.popups.showHelpPopup()
예제 #24
0
def startTorMonitor(startTime):
    """
  Initializes the interface and starts the main draw loop.
  
  Arguments:
    startTime - unix time for when arm was started
  """

    # attempts to fetch the tor pid, warning if unsuccessful (this is needed for
    # checking its resource usage, among other things)
    conn = torTools.getConn()
    torPid = conn.getMyPid()

    if not torPid and conn.isAlive():
        log.warn(
            "Unable to determine Tor's pid. Some information, like its resource usage will be unavailable."
        )

    # adds events needed for arm functionality to the torTools REQ_EVENTS
    # mapping (they're then included with any setControllerEvents call, and log
    # a more helpful error if unavailable)

    torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function"

    if not CONFIG["startup.blindModeEnabled"]:
        # The DisableDebuggerAttachment will prevent our connection panel from really
        # functioning. It'll have circuits, but little else. If this is the case then
        # notify the user and tell them what they can do to fix it.

        if conn.getOption("DisableDebuggerAttachment", None) == "1":
            log.notice(
                "Tor is preventing system utilities like netstat and lsof from working. This means that arm can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313"
            )
            connections.getResolver("tor").setPaused(True)
        else:
            torTools.REQ_EVENTS[
                "CIRC"] = "may cause issues in identifying client connections"

            # Configures connection resoultions. This is paused/unpaused according to
            # if Tor's connected or not.
            conn.addStatusListener(connResetListener)

            if torPid:
                # use the tor pid to help narrow connection results
                torCmdName = sysTools.getProcessName(torPid, "tor")
                connections.getResolver(torCmdName, torPid, "tor")
            else:
                # constructs singleton resolver and, if tor isn't connected, initizes
                # it to be paused
                connections.getResolver("tor").setPaused(not conn.isAlive())

            # hack to display a better (arm specific) notice if all resolvers fail
            connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")."

    # provides a notice about any event types tor supports but arm doesn't
    missingEventTypes = cli.logPanel.getMissingEventTypes()

    if missingEventTypes:
        pluralLabel = "s" if len(missingEventTypes) > 1 else ""
        log.info(
            "arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)"
            % (pluralLabel, ", ".join(missingEventTypes)))

    try:
        curses.wrapper(drawTorMonitor, startTime)
    except KeyboardInterrupt:
        # Skip printing stack trace in case of keyboard interrupt. The
        # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw
        # (which would leave the user's terminal in a screwed up state). There is
        # still a tiny timing issue here (after the exception but before the flag
        # is set) but I've never seen it happen in practice.

        panel.HALT_ACTIVITY = True
        shutdownDaemons()
예제 #25
0
class _Resolver():
    """
  Performs reverse DNS resolutions. Lookups are a network bound operation so
  this spawns a pool of worker threads to do several at a time in parallel.
  """
    def __init__(self):
        # IP Address => (hostname/error, age), resolution failures result in a
        # ValueError with the lookup's status
        self.resolvedCache = {}

        self.resolvedLock = threading.RLock(
        )  # governs concurrent access when modifying resolvedCache
        self.unresolvedQueue = Queue.Queue()  # unprocessed lookup requests
        self.recentQueries = [
        ]  # recent resolution requests to prevent duplicate requests
        self.threadPool = []  # worker threads that process requests
        self.totalResolves = 0  # counter for the total number of addresses queried to be resolved
        self.isPaused = False  # prevents further resolutions if true
        self.halt = False  # if true, tells workers to stop
        self.cond = threading.Condition()  # used for pausing threads

        # Determines if resolutions are made using os 'host' calls or python's
        # 'socket.gethostbyaddr'. The following checks if the system has the
        # gethostbyname_r function, which determines if python resolutions can be
        # done in parallel or not. If so, this is preferable.
        isSocketResolutionParallel = distutils.sysconfig.get_config_var(
            "HAVE_GETHOSTBYNAME_R")
        self.useSocketResolution = CONFIG[
            "queries.hostnames.useSocketModule"] and isSocketResolutionParallel

        for _ in range(CONFIG["queries.hostnames.poolSize"]):
            t = threading.Thread(target=self._workerLoop)
            t.setDaemon(True)
            t.start()
            self.threadPool.append(t)

    def getHostname(self, ipAddr, timeout, flushCache=False):
        """
    Provides the hostname, queuing the request and returning None if the
    timeout is reached before resolution. If a problem's encountered then this
    either raises an IOError (for os and network issues) or ValueError (for DNS
    resolution errors).
    
    Arguments:
      ipAddr     - ip address to be resolved
      timeout    - maximum duration to wait for a resolution (blocks to
                   completion if None)
      flushCache - if true the cache is skipped and address re-resolved
    """

        # if outstanding requests are done then clear recentQueries to allow
        # entries removed from the cache to be re-run
        if self.unresolvedQueue.empty(): self.recentQueries = []

        # copies reference cache (this is important in case the cache is trimmed
        # during this call)
        cacheRef = self.resolvedCache

        if not flushCache and ipAddr in cacheRef:
            # cached response is available - raise if an error, return if a hostname
            response = cacheRef[ipAddr][0]
            if isinstance(response, Exception): raise response
            else: return response
        elif flushCache or ipAddr not in self.recentQueries:
            # new request - queue for resolution
            self.totalResolves += 1
            self.recentQueries.append(ipAddr)
            self.unresolvedQueue.put(ipAddr)

        # periodically check cache if requester is willing to wait
        if timeout == None or timeout > 0:
            startTime = time.time()

            while timeout == None or time.time() - startTime < timeout:
                if ipAddr in cacheRef:
                    # address was resolved - raise if an error, return if a hostname
                    response = cacheRef[ipAddr][0]
                    if isinstance(response, Exception): raise response
                    else: return response
                else: time.sleep(0.1)

        return None  # timeout reached without resolution

    def stop(self):
        """
    Halts further resolutions and terminates the thread.
    """

        self.cond.acquire()
        self.halt = True
        self.cond.notifyAll()
        self.cond.release()

    def _workerLoop(self):
        """
    Simple producer-consumer loop followed by worker threads. This takes
    addresses from the unresolvedQueue, attempts to look up its hostname, and
    adds its results or the error to the resolved cache. Resolver reference
    provides shared resources used by the thread pool.
    """

        while not self.halt:
            # if resolver is paused then put a hold on further resolutions
            if self.isPaused:
                self.cond.acquire()
                if not self.halt: self.cond.wait(1)
                self.cond.release()
                continue

            # snags next available ip, timeout is because queue can't be woken up
            # when 'halt' is set
            try:
                ipAddr = self.unresolvedQueue.get_nowait()
            except Queue.Empty:
                # no elements ready, wait a little while and try again
                self.cond.acquire()
                if not self.halt: self.cond.wait(1)
                self.cond.release()
                continue
            if self.halt: break

            try:
                if self.useSocketResolution: result = _resolveViaSocket(ipAddr)
                else: result = _resolveViaHost(ipAddr)
            except IOError, exc:
                result = exc  # lookup failed
            except ValueError, exc:
                result = exc  # dns error

            self.resolvedLock.acquire()
            self.resolvedCache[ipAddr] = (result, RESOLVER_COUNTER.next())

            # trim cache if excessively large (clearing out oldest entries)
            if len(self.resolvedCache) > CONFIG["cache.hostnames.size"]:
                # Providing for concurrent, non-blocking calls require that entries are
                # never removed from the cache, so this creates a new, trimmed version
                # instead.

                # determines minimum age of entries to be kept
                currentCount = RESOLVER_COUNTER.next()
                newCacheSize = CONFIG["cache.hostnames.size"] - CONFIG[
                    "cache.hostnames.trimSize"]
                threshold = currentCount - newCacheSize
                newCache = {}

                msg = "trimming hostname cache from %i entries to %i" % (len(
                    self.resolvedCache), newCacheSize)
                log.info(msg)

                # checks age of each entry, adding to toDelete if too old
                for ipAddr, entry in self.resolvedCache.iteritems():
                    if entry[1] >= threshold: newCache[ipAddr] = entry

                self.resolvedCache = newCache

            self.resolvedLock.release()
예제 #26
0
파일: controller.py 프로젝트: gsathya/arm
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()
예제 #27
0
def start_nyx(stdscr):
  """
  Main draw loop context.

  Arguments:
    stdscr    - curses window
  """

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

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

  # provides notice about any unused config keys

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

  # tells daemon panels to start

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

  # allows for background transparency

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

  # makes the cursor invisible

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

  # logs the initialization time

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

  # main draw loop

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

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

    # sets panel visability

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

    # redraws the interface if it's needed

    control.redraw(False)
    stdscr.refresh()

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

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

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

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

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

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

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

        if is_keystroke_consumed:
          break
예제 #28
0
def init_controller(stdscr, start_time):
  """
  Spawns the controller, and related panels for it.

  Arguments:
    stdscr - curses window
  """

  global NYX_CONTROLLER

  # initializes the panels

  sticky_panels = [
    nyx.header_panel.HeaderPanel(stdscr, start_time),
    LabelPanel(stdscr),
  ]

  page_panels, first_page_panels = [], []

  # first page: graph and log
  if CONFIG['features.panels.show.graph']:
    first_page_panels.append(nyx.graph_panel.GraphPanel(stdscr))

  if CONFIG['features.panels.show.log']:
    expanded_events = nyx.arguments.expand_events(CONFIG['startup.events'])
    first_page_panels.append(nyx.log_panel.LogPanel(stdscr, expanded_events))

  if first_page_panels:
    page_panels.append(first_page_panels)

  # second page: connections
  if CONFIG['features.panels.show.connection']:
    page_panels.append([nyx.connection_panel.ConnectionPanel(stdscr)])

    # The DisableDebuggerAttachment will prevent our connection panel from really
    # functioning. It'll have circuits, but little else. If this is the case then
    # notify the user and tell them what they can do to fix it.

    controller = tor_controller()

    if controller.get_conf('DisableDebuggerAttachment', None) == '1':
      log.notice("Tor is preventing system utilities like netstat and lsof from working. This means that nyx can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313")
      nyx.util.tracker.get_connection_tracker().set_paused(True)
    else:
      # Configures connection resoultions. This is paused/unpaused according to
      # if Tor's connected or not.

      controller.add_status_listener(conn_reset_listener)

      tor_pid = controller.get_pid(None)

      if tor_pid:
        # use the tor pid to help narrow connection results
        tor_cmd = system.name_by_pid(tor_pid)

        if tor_cmd is None:
          tor_cmd = 'tor'

        resolver = nyx.util.tracker.get_connection_tracker()
        log.info('Operating System: %s, Connection Resolvers: %s' % (os.uname()[0], ', '.join(resolver._resolvers)))
      else:
        # constructs singleton resolver and, if tor isn't connected, initizes
        # it to be paused

        nyx.util.tracker.get_connection_tracker().set_paused(not controller.is_alive())

  # third page: config

  if CONFIG['features.panels.show.config']:
    page_panels.append([nyx.config_panel.ConfigPanel(stdscr, nyx.config_panel.State.TOR)])

  # fourth page: torrc

  if CONFIG['features.panels.show.torrc']:
    page_panels.append([nyx.torrc_panel.TorrcPanel(stdscr, nyx.torrc_panel.Config.TORRC)])

  # initializes the controller

  NYX_CONTROLLER = Controller(stdscr, sticky_panels, page_panels)
예제 #29
0
def recv_message(control_file, arrived_at = None):
  """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
    * :class:`stem.SocketClosed` if the socket closes before we receive
      a complete message
  """

  parsed_content, raw_content, first_line = None, None, True

  while True:
    try:
      line = control_file.readline()
    except AttributeError:
      # if the control_file has been closed then we will receive:
      # AttributeError: 'NoneType' object has no attribute 'recv'

      log.info(ERROR_MSG % ('SocketClosed', 'socket file has been closed'))
      raise stem.SocketClosed('socket file has been closed')
    except (OSError, ValueError) as exc:
      # when disconnected this errors with...
      #
      #   * ValueError: I/O operation on closed file
      #   * OSError: [Errno 107] Transport endpoint is not connected
      #   * OSError: [Errno 9] Bad file descriptor

      log.info(ERROR_MSG % ('SocketClosed', 'received exception "%s"' % exc))
      raise stem.SocketClosed(exc)

    # Parses the tor control lines. These are of the form...
    # <status code><divider><content>\r\n

    if not line:
      # if the socket is disconnected then the readline() method will provide
      # empty content

      log.info(ERROR_MSG % ('SocketClosed', 'empty socket content'))
      raise stem.SocketClosed('Received empty socket content.')
    elif not MESSAGE_PREFIX.match(line):
      log.info(ERROR_MSG % ('ProtocolError', 'malformed status code/divider, "%s"' % log.escape(line)))
      raise stem.ProtocolError('Badly formatted reply line: beginning is malformed')
    elif not line.endswith(b'\r\n'):
      log.info(ERROR_MSG % ('ProtocolError', 'no CRLF linebreak, "%s"' % log.escape(line)))
      raise stem.ProtocolError('All lines should end with CRLF')

    status_code, divider, content = line[:3], line[3:4], line[4:-2]  # strip CRLF off content

    status_code = stem.util.str_tools._to_unicode(status_code)
    divider = stem.util.str_tools._to_unicode(divider)

    # Most controller responses are single lines, in which case we don't need
    # so much overhead.

    if first_line:
      if divider == ' ':
        _log_trace(line)
        return stem.response.ControlMessage([(status_code, divider, content)], line, arrived_at = arrived_at)
      else:
        parsed_content, raw_content, first_line = [], bytearray(), False

    raw_content += line

    if divider == '-':
      # mid-reply line, keep pulling for more content
      parsed_content.append((status_code, divider, content))
    elif divider == ' ':
      # end of the message, return the message
      parsed_content.append((status_code, divider, content))
      _log_trace(bytes(raw_content))
      return stem.response.ControlMessage(parsed_content, bytes(raw_content), arrived_at = arrived_at)
    elif divider == '+':
      # data entry, all of the following lines belong to the content until we
      # get a line with just a period

      content_block = bytearray(content)

      while True:
        try:
          line = control_file.readline()
          raw_content += line
        except socket.error as exc:
          log.info(ERROR_MSG % ('SocketClosed', 'received an exception while mid-way through a data reply (exception: "%s", read content: "%s")' % (exc, log.escape(bytes(raw_content)))))
          raise stem.SocketClosed(exc)

        if not line.endswith(b'\r\n'):
          log.info(ERROR_MSG % ('ProtocolError', 'CRLF linebreaks missing from a data reply, "%s"' % log.escape(bytes(raw_content))))
          raise stem.ProtocolError('All lines should end with CRLF')
        elif line == b'.\r\n':
          break  # data block termination

        line = line[:-2]  # strips off the CRLF

        # lines starting with a period are escaped by a second period (as per
        # section 2.4 of the control-spec)

        if line.startswith(b'..'):
          line = line[1:]

        content_block += b'\n' + line

      # joins the content using a newline rather than CRLF separator (more
      # conventional for multi-line string content outside the windows world)

      parsed_content.append((status_code, divider, bytes(content_block)))
    else:
      # this should never be reached due to the prefix regex, but might as well
      # be safe...

      log.warn(ERROR_MSG % ('ProtocolError', "\"%s\" isn't a recognized divider type" % divider))
      raise stem.ProtocolError("Unrecognized divider type '%s': %s" % (divider, stem.util.str_tools._to_unicode(line)))
예제 #30
0
def recv_message(control_file):
  """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
    * :class:`stem.SocketClosed` if the socket closes before we receive
      a complete message
  """

  parsed_content, raw_content = [], ""
  logging_prefix = "Error while receiving a control message (%s): "

  while True:
    try:
      line = control_file.readline()
    except AttributeError:
      # if the control_file has been closed then we will receive:
      # AttributeError: 'NoneType' object has no attribute 'recv'

      prefix = logging_prefix % "SocketClosed"
      log.info(prefix + "socket file has been closed")
      raise stem.SocketClosed("socket file has been closed")
    except socket.error, exc:
      # when disconnected we get...
      # socket.error: [Errno 107] Transport endpoint is not connected

      prefix = logging_prefix % "SocketClosed"
      log.info(prefix + "received exception \"%s\"" % exc)
      raise stem.SocketClosed(exc)

    raw_content += line

    # Parses the tor control lines. These are of the form...
    # <status code><divider><content>\r\n

    if len(line) == 0:
      # if the socket is disconnected then the readline() method will provide
      # empty content

      prefix = logging_prefix % "SocketClosed"
      log.info(prefix + "empty socket content")
      raise stem.SocketClosed("Received empty socket content.")
    elif len(line) < 4:
      prefix = logging_prefix % "ProtocolError"
      log.info(prefix + "line too short, \"%s\"" % log.escape(line))
      raise stem.ProtocolError("Badly formatted reply line: too short")
    elif not re.match(r'^[a-zA-Z0-9]{3}[-+ ]', line):
      prefix = logging_prefix % "ProtocolError"
      log.info(prefix + "malformed status code/divider, \"%s\"" % log.escape(line))
      raise stem.ProtocolError("Badly formatted reply line: beginning is malformed")
    elif not line.endswith("\r\n"):
      prefix = logging_prefix % "ProtocolError"
      log.info(prefix + "no CRLF linebreak, \"%s\"" % log.escape(line))
      raise stem.ProtocolError("All lines should end with CRLF")

    line = line[:-2]  # strips off the CRLF
    status_code, divider, content = line[:3], line[3], line[4:]

    if divider == "-":
      # mid-reply line, keep pulling for more content
      parsed_content.append((status_code, divider, content))
    elif divider == " ":
      # end of the message, return the message
      parsed_content.append((status_code, divider, content))

      log_message = raw_content.replace("\r\n", "\n").rstrip()
      log.trace("Received from tor:\n" + log_message)

      return stem.response.ControlMessage(parsed_content, raw_content)
    elif divider == "+":
      # data entry, all of the following lines belong to the content until we
      # get a line with just a period

      while True:
        try:
          line = control_file.readline()
        except socket.error, exc:
          prefix = logging_prefix % "SocketClosed"
          log.info(prefix + "received an exception while mid-way through a data reply (exception: \"%s\", read content: \"%s\")" % (exc, log.escape(raw_content)))
          raise stem.SocketClosed(exc)

        raw_content += line

        if not line.endswith("\r\n"):
          prefix = logging_prefix % "ProtocolError"
          log.info(prefix + "CRLF linebreaks missing from a data reply, \"%s\"" % log.escape(raw_content))
          raise stem.ProtocolError("All lines should end with CRLF")
        elif line == ".\r\n":
          break  # data block termination

        line = line[:-2]  # strips off the CRLF

        # lines starting with a period are escaped by a second period (as per
        # section 2.4 of the control-spec)

        if line.startswith(".."):
          line = line[1:]

        # appends to previous content, using a newline rather than CRLF
        # separator (more conventional for multi-line string content outside
        # the windows world)

        content += "\n" + line

      parsed_content.append((status_code, divider, content))
예제 #31
0
파일: torConfig.py 프로젝트: gsathya/arm
def renderTorrc(template, options, commentIndent = 30):
  """
  Uses the given template to generate a nicely formatted torrc with the given
  options. The tempating language this recognizes is a simple one, recognizing
  the following options:
    [IF <option>]         # if <option> maps to true or a non-empty string
    [IF NOT <option>]     # logical inverse
    [IF <opt1> | <opt2>]  # logical or of the options
    [ELSE]          # if the prior conditional evaluated to false
    [END IF]        # ends the control block
    
    [<option>]      # inputs the option value, omitting the line if it maps
                    # to a boolean or empty string
    [NEWLINE]       # empty line, otherwise templating white space is ignored
  
  Arguments:
    template      - torrc template lines used to generate the results
    options       - mapping of keywords to their given values, with values
                    being booleans or strings (possibly multi-line)
    commentIndent - minimum column that comments align on
  """
  
  results = []
  templateIter = iter(template)
  commentLineFormat = "%%-%is%%s" % commentIndent
  
  try:
    while True:
      line = templateIter.next().strip()
      
      if line.startswith("[IF ") and line.endswith("]"):
        # checks if any of the conditional options are true or a non-empty string
        evaluatesTrue = False
        for cond in line[4:-1].split("|"):
          isInverse = False
          if cond.startswith("NOT "):
            isInverse = True
            cond = cond[4:]
          
          if isInverse != bool(options.get(cond.strip())):
            evaluatesTrue = True
            break
        
        if evaluatesTrue:
          continue
        else:
          # skips lines until we come to an else or the end of the block
          depth = 0
          
          while depth != -1:
            line = templateIter.next().strip()
            
            if line.startswith("[IF ") and line.endswith("]"): depth += 1
            elif line == "[END IF]": depth -= 1
            elif depth == 0 and line == "[ELSE]": depth -= 1
      elif line == "[ELSE]":
        # an else block we aren't using - skip to the end of it
        depth = 0
        
        while depth != -1:
          line = templateIter.next().strip()
          
          if line.startswith("[IF "): depth += 1
          elif line == "[END IF]": depth -= 1
      elif line == "[NEWLINE]":
        # explicit newline
        results.append("")
      elif line.startswith("#"):
        # comment only
        results.append(line)
      elif line.startswith("[") and line.endswith("]"):
        # completely dynamic entry
        optValue = options.get(line[1:-1])
        if optValue: results.append(optValue)
      else:
        # torrc option line
        option, arg, comment = "", "", ""
        parsedLine = line
        
        if "#" in parsedLine:
          parsedLine, comment = parsedLine.split("#", 1)
          parsedLine = parsedLine.strip()
          comment = "# %s" % comment.strip()
        
        # parses the argument from the option
        if " " in parsedLine.strip():
          option, arg = parsedLine.split(" ", 1)
          option = option.strip()
        else:
          log.info("torrc template option lacks an argument: '%s'" % line)
          continue
        
        # inputs dynamic arguments
        if arg.startswith("[") and arg.endswith("]"):
          arg = options.get(arg[1:-1])
        
        # skips argument if it's false or an empty string
        if not arg: continue
        
        torrcEntry = "%s %s" % (option, arg)
        if comment: results.append(commentLineFormat % (torrcEntry + " ", comment))
        else: results.append(torrcEntry)
  except StopIteration: pass
  
  return "\n".join(results)
예제 #32
0
 def msg(self, message):
   """
   Sends a message to our control socket and provides back its reply.
   
   :param str message: message to be formatted and sent to tor
   
   :returns: :class:`stem.response.ControlMessage` with the response
   
   :raises:
     * :class:`stem.socket.ProtocolError` the content from the socket is malformed
     * :class:`stem.socket.SocketError` if a problem arises in using the socket
     * :class:`stem.socket.SocketClosed` if the socket is shut down
   """
   
   with self._msg_lock:
     # If our _reply_queue isn't empty then one of a few things happened...
     #
     # - Our connection was closed and probably re-restablished. This was
     #   in reply to pulling for an asynchronous event and getting this is
     #   expected - ignore it.
     #
     # - Pulling for asynchronous events produced an error. If this was a
     #   ProtocolError then it's a tor bug, and if a non-closure SocketError
     #   then it was probably a socket glitch. Deserves an INFO level log
     #   message.
     #
     # - This is a leftover response for a msg() call. We can't tell who an
     #   exception was airmarked for, so we only know that this was the case
     #   if it's a ControlMessage. This should not be possable and indicates
     #   a stem bug. This deserves a NOTICE level log message since it
     #   indicates that one of our callers didn't get their reply.
     
     while not self._reply_queue.empty():
       try:
         response = self._reply_queue.get_nowait()
         
         if isinstance(response, stem.socket.SocketClosed):
           pass # this is fine
         elif isinstance(response, stem.socket.ProtocolError):
           log.info("Tor provided a malformed message (%s)" % response)
         elif isinstance(response, stem.socket.ControllerError):
           log.info("Socket experienced a problem (%s)" % response)
         elif isinstance(response, stem.response.ControlMessage):
           log.notice("BUG: the msg() function failed to deliver a response: %s" % response)
       except Queue.Empty:
         # the empty() method is documented to not be fully reliable so this
         # isn't entirely surprising
         
         break
     
     try:
       self._socket.send(message)
       response = self._reply_queue.get()
       
       # If the message we received back had an exception then re-raise it to the
       # caller. Otherwise return the response.
       
       if isinstance(response, stem.socket.ControllerError):
         raise response
       else:
         return response
     except stem.socket.SocketClosed, exc:
       # If the recv() thread caused the SocketClosed then we could still be
       # in the process of closing. Calling close() here so that we can
       # provide an assurance to the caller that when we raise a SocketClosed
       # exception we are shut down afterward for realz.
       
       self.close()
       raise exc
예제 #33
0
파일: controller.py 프로젝트: gsathya/arm
def startTorMonitor(startTime):
    """
  Initializes the interface and starts the main draw loop.
  
  Arguments:
    startTime - unix time for when arm was started
  """

    # attempts to fetch the tor pid, warning if unsuccessful (this is needed for
    # checking its resource usage, among other things)
    conn = torTools.getConn()
    torPid = conn.getMyPid()

    if not torPid and conn.isAlive():
        log.warn("Unable to determine Tor's pid. Some information, like its resource usage will be unavailable.")

    # adds events needed for arm functionality to the torTools REQ_EVENTS
    # mapping (they're then included with any setControllerEvents call, and log
    # a more helpful error if unavailable)

    torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function"

    if not CONFIG["startup.blindModeEnabled"]:
        # The DisableDebuggerAttachment will prevent our connection panel from really
        # functioning. It'll have circuits, but little else. If this is the case then
        # notify the user and tell them what they can do to fix it.

        if conn.getOption("DisableDebuggerAttachment", None) == "1":
            log.notice(
                "Tor is preventing system utilities like netstat and lsof from working. This means that arm can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313"
            )
            connections.getResolver("tor").setPaused(True)
        else:
            torTools.REQ_EVENTS["CIRC"] = "may cause issues in identifying client connections"

            # Configures connection resoultions. This is paused/unpaused according to
            # if Tor's connected or not.
            conn.addStatusListener(connResetListener)

            if torPid:
                # use the tor pid to help narrow connection results
                torCmdName = sysTools.getProcessName(torPid, "tor")
                connections.getResolver(torCmdName, torPid, "tor")
            else:
                # constructs singleton resolver and, if tor isn't connected, initizes
                # it to be paused
                connections.getResolver("tor").setPaused(not conn.isAlive())

            # hack to display a better (arm specific) notice if all resolvers fail
            connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")."

    # provides a notice about any event types tor supports but arm doesn't
    missingEventTypes = cli.logPanel.getMissingEventTypes()

    if missingEventTypes:
        pluralLabel = "s" if len(missingEventTypes) > 1 else ""
        log.info(
            "arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)"
            % (pluralLabel, ", ".join(missingEventTypes))
        )

    try:
        curses.wrapper(drawTorMonitor, startTime)
    except KeyboardInterrupt:
        # Skip printing stack trace in case of keyboard interrupt. The
        # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw
        # (which would leave the user's terminal in a screwed up state). There is
        # still a tiny timing issue here (after the exception but before the flag
        # is set) but I've never seen it happen in practice.

        panel.HALT_ACTIVITY = True
        shutdownDaemons()
예제 #34
0
def recv_message(control_file):
    """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
    * :class:`stem.SocketClosed` if the socket closes before we receive
      a complete message
  """

    parsed_content, raw_content = [], ""
    logging_prefix = "Error while receiving a control message (%s): "

    while True:
        try:
            line = control_file.readline()
        except AttributeError:
            # if the control_file has been closed then we will receive:
            # AttributeError: 'NoneType' object has no attribute 'recv'

            prefix = logging_prefix % "SocketClosed"
            log.info(prefix + "socket file has been closed")
            raise stem.SocketClosed("socket file has been closed")
        except socket.error, exc:
            # when disconnected we get...
            # socket.error: [Errno 107] Transport endpoint is not connected

            prefix = logging_prefix % "SocketClosed"
            log.info(prefix + "received exception \"%s\"" % exc)
            raise stem.SocketClosed(exc)

        raw_content += line

        # Parses the tor control lines. These are of the form...
        # <status code><divider><content>\r\n

        if len(line) == 0:
            # if the socket is disconnected then the readline() method will provide
            # empty content

            prefix = logging_prefix % "SocketClosed"
            log.info(prefix + "empty socket content")
            raise stem.SocketClosed("Received empty socket content.")
        elif len(line) < 4:
            prefix = logging_prefix % "ProtocolError"
            log.info(prefix + "line too short, \"%s\"" % log.escape(line))
            raise stem.ProtocolError("Badly formatted reply line: too short")
        elif not re.match(r'^[a-zA-Z0-9]{3}[-+ ]', line):
            prefix = logging_prefix % "ProtocolError"
            log.info(prefix + "malformed status code/divider, \"%s\"" %
                     log.escape(line))
            raise stem.ProtocolError(
                "Badly formatted reply line: beginning is malformed")
        elif not line.endswith("\r\n"):
            prefix = logging_prefix % "ProtocolError"
            log.info(prefix + "no CRLF linebreak, \"%s\"" % log.escape(line))
            raise stem.ProtocolError("All lines should end with CRLF")

        line = line[:-2]  # strips off the CRLF
        status_code, divider, content = line[:3], line[3], line[4:]

        if divider == "-":
            # mid-reply line, keep pulling for more content
            parsed_content.append((status_code, divider, content))
        elif divider == " ":
            # end of the message, return the message
            parsed_content.append((status_code, divider, content))

            log_message = raw_content.replace("\r\n", "\n").rstrip()
            log.trace("Received from tor:\n" + log_message)

            return stem.response.ControlMessage(parsed_content, raw_content)
        elif divider == "+":
            # data entry, all of the following lines belong to the content until we
            # get a line with just a period

            while True:
                try:
                    line = control_file.readline()
                except socket.error, exc:
                    prefix = logging_prefix % "SocketClosed"
                    log.info(
                        prefix +
                        "received an exception while mid-way through a data reply (exception: \"%s\", read content: \"%s\")"
                        % (exc, log.escape(raw_content)))
                    raise stem.SocketClosed(exc)

                raw_content += line

                if not line.endswith("\r\n"):
                    prefix = logging_prefix % "ProtocolError"
                    log.info(
                        prefix +
                        "CRLF linebreaks missing from a data reply, \"%s\"" %
                        log.escape(raw_content))
                    raise stem.ProtocolError("All lines should end with CRLF")
                elif line == ".\r\n":
                    break  # data block termination

                line = line[:-2]  # strips off the CRLF

                # lines starting with a period are escaped by a second period (as per
                # section 2.4 of the control-spec)

                if line.startswith(".."):
                    line = line[1:]

                # appends to previous content, using a newline rather than CRLF
                # separator (more conventional for multi-line string content outside
                # the windows world)

                content += "\n" + line

            parsed_content.append((status_code, divider, content))
예제 #35
0
파일: socket.py 프로젝트: rl1987/stem
def recv_message(control_file):
    """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
    * :class:`stem.SocketClosed` if the socket closes before we receive
      a complete message
  """

    parsed_content, raw_content = [], []
    logging_prefix = 'Error while receiving a control message (%s): '

    while True:
        try:
            # From a real socket readline() would always provide bytes, but during
            # tests we might be given a StringIO in which case it's unicode under
            # python 3.x.

            line = stem.util.str_tools._to_bytes(control_file.readline())
        except AttributeError:
            # if the control_file has been closed then we will receive:
            # AttributeError: 'NoneType' object has no attribute 'recv'

            prefix = logging_prefix % 'SocketClosed'
            log.info(prefix + 'socket file has been closed')
            raise stem.SocketClosed('socket file has been closed')
        except (socket.error, ValueError) as exc:
            # When disconnected we get...
            #
            # Python 2:
            #   socket.error: [Errno 107] Transport endpoint is not connected
            #
            # Python 3:
            #   ValueError: I/O operation on closed file.

            prefix = logging_prefix % 'SocketClosed'
            log.info(prefix + 'received exception "%s"' % exc)
            raise stem.SocketClosed(exc)

        raw_content.append(line)

        # Parses the tor control lines. These are of the form...
        # <status code><divider><content>\r\n

        if len(line) == 0:
            # if the socket is disconnected then the readline() method will provide
            # empty content

            prefix = logging_prefix % 'SocketClosed'
            log.info(prefix + 'empty socket content')
            raise stem.SocketClosed('Received empty socket content.')
        elif len(line) < 4:
            prefix = logging_prefix % 'ProtocolError'
            log.info(prefix + 'line too short, "%s"' % log.escape(line))
            raise stem.ProtocolError('Badly formatted reply line: too short')
        elif not re.match(b'^[a-zA-Z0-9]{3}[-+ ]', line):
            prefix = logging_prefix % 'ProtocolError'
            log.info(prefix +
                     'malformed status code/divider, "%s"' % log.escape(line))
            raise stem.ProtocolError(
                'Badly formatted reply line: beginning is malformed')
        elif not line.endswith(b'\r\n'):
            prefix = logging_prefix % 'ProtocolError'
            log.info(prefix + 'no CRLF linebreak, "%s"' % log.escape(line))
            raise stem.ProtocolError('All lines should end with CRLF')

        line = line[:-2]  # strips off the CRLF
        status_code, divider, content = line[:3], line[3:4], line[4:]
        content_lines = [content]

        if stem.prereq.is_python_3():
            status_code = stem.util.str_tools._to_unicode(status_code)
            divider = stem.util.str_tools._to_unicode(divider)

        if divider == '-':
            # mid-reply line, keep pulling for more content
            parsed_content.append((status_code, divider, content))
        elif divider == ' ':
            # end of the message, return the message
            parsed_content.append((status_code, divider, content))

            raw_content_str = b''.join(raw_content)
            log_message = stem.util.str_tools._to_unicode(
                raw_content_str.replace(b'\r\n', b'\n').rstrip())
            log_message_lines = log_message.split('\n')

            if TRUNCATE_LOGS and len(log_message_lines) > TRUNCATE_LOGS:
                log_message = '\n'.join(log_message_lines[:TRUNCATE_LOGS] + [
                    '... %i more lines...' %
                    (len(log_message_lines) - TRUNCATE_LOGS)
                ])

            if len(log_message_lines) > 2:
                log.trace('Received from tor:\n%s' % log_message)
            else:
                log.trace('Received from tor: %s' %
                          log_message.replace('\n', '\\n'))

            return stem.response.ControlMessage(parsed_content,
                                                raw_content_str)
        elif divider == '+':
            # data entry, all of the following lines belong to the content until we
            # get a line with just a period

            while True:
                try:
                    line = stem.util.str_tools._to_bytes(
                        control_file.readline())
                except socket.error as exc:
                    prefix = logging_prefix % 'SocketClosed'
                    log.info(
                        prefix +
                        'received an exception while mid-way through a data reply (exception: "%s", read content: "%s")'
                        % (exc, log.escape(b''.join(raw_content))))
                    raise stem.SocketClosed(exc)

                raw_content.append(line)

                if not line.endswith(b'\r\n'):
                    prefix = logging_prefix % 'ProtocolError'
                    log.info(
                        prefix +
                        'CRLF linebreaks missing from a data reply, "%s"' %
                        log.escape(b''.join(raw_content)))
                    raise stem.ProtocolError('All lines should end with CRLF')
                elif line == b'.\r\n':
                    break  # data block termination

                line = line[:-2]  # strips off the CRLF

                # lines starting with a period are escaped by a second period (as per
                # section 2.4 of the control-spec)

                if line.startswith(b'..'):
                    line = line[1:]

                content_lines.append(line)

            # joins the content using a newline rather than CRLF separator (more
            # conventional for multi-line string content outside the windows world)

            parsed_content.append(
                (status_code, divider, b'\n'.join(content_lines)))
        else:
            # this should never be reached due to the prefix regex, but might as well
            # be safe...
            prefix = logging_prefix % 'ProtocolError'
            log.warn(prefix +
                     "\"%s\" isn't a recognized divider type" % divider)
            raise stem.ProtocolError(
                "Unrecognized divider type '%s': %s" %
                (divider, stem.util.str_tools._to_unicode(line)))
예제 #36
0
파일: sysTools.py 프로젝트: gsathya/arm
 def run(self):
   while not self._halt:
     timeSinceReset = time.time() - self.lastLookup
     
     if self.resolveRate == 0:
       self._cond.acquire()
       if not self._halt: self._cond.wait(0.2)
       self._cond.release()
       
       continue
     elif timeSinceReset < self.resolveRate:
       sleepTime = max(0.2, self.resolveRate - timeSinceReset)
       
       self._cond.acquire()
       if not self._halt: self._cond.wait(sleepTime)
       self._cond.release()
       
       continue # done waiting, try again
     
     newValues = {}
     try:
       if self._useProc:
         utime, stime, startTime = proc.get_stats(self.processPid, proc.Stat.CPU_UTIME, proc.Stat.CPU_STIME, proc.Stat.START_TIME)
         totalCpuTime = float(utime) + float(stime)
         cpuDelta = totalCpuTime - self._lastCpuTotal
         newValues["cpuSampling"] = cpuDelta / timeSinceReset
         newValues["cpuAvg"] = totalCpuTime / (time.time() - float(startTime))
         newValues["_lastCpuTotal"] = totalCpuTime
         
         memUsage = int(proc.get_memory_usage(self.processPid)[0])
         totalMemory = proc.get_physical_memory()
         newValues["memUsage"] = memUsage
         newValues["memUsagePercentage"] = float(memUsage) / totalMemory
       else:
         # the ps call formats results as:
         # 
         #     TIME     ELAPSED   RSS %MEM
         # 3-08:06:32 21-00:00:12 121844 23.5
         # 
         # or if Tor has only recently been started:
         # 
         #     TIME      ELAPSED    RSS %MEM
         #  0:04.40        37:57  18772  0.9
         
         psCall = system.call("ps -p %s -o cputime,etime,rss,%%mem" % self.processPid)
         
         isSuccessful = False
         if psCall and len(psCall) >= 2:
           stats = psCall[1].strip().split()
           
           if len(stats) == 4:
             try:
               totalCpuTime = str_tools.parse_short_time_label(stats[0])
               uptime = str_tools.parse_short_time_label(stats[1])
               cpuDelta = totalCpuTime - self._lastCpuTotal
               newValues["cpuSampling"] = cpuDelta / timeSinceReset
               newValues["cpuAvg"] = totalCpuTime / uptime
               newValues["_lastCpuTotal"] = totalCpuTime
               
               newValues["memUsage"] = int(stats[2]) * 1024 # ps size is in kb
               newValues["memUsagePercentage"] = float(stats[3]) / 100.0
               isSuccessful = True
             except ValueError, exc: pass
         
         if not isSuccessful:
           raise IOError("unrecognized output from ps: %s" % psCall)
     except IOError, exc:
       newValues = {}
       self._failureCount += 1
       
       if self._useProc:
         if self._failureCount >= 3:
           # We've failed three times resolving via proc. Warn, and fall back
           # to ps resolutions.
           log.info("Failed three attempts to get process resource usage from proc, falling back to ps (%s)" % exc)
           
           self._useProc = False
           self._failureCount = 1 # prevents lastQueryFailed() from thinking that we succeeded
         else:
           # wait a bit and try again
           log.debug("Unable to query process resource usage from proc (%s)" % exc)
           self._cond.acquire()
           if not self._halt: self._cond.wait(0.5)
           self._cond.release()
       else:
         # exponential backoff on making failed ps calls
         sleepTime = 0.01 * (2 ** self._failureCount) + self._failureCount
         log.debug("Unable to query process resource usage from ps, waiting %0.2f seconds (%s)" % (sleepTime, exc))
         self._cond.acquire()
         if not self._halt: self._cond.wait(sleepTime)
         self._cond.release()
     
     # sets the new values
     if newValues:
       # If this is the first run then the cpuSampling stat is meaningless
       # (there isn't a previous tick to sample from so it's zero at this
       # point). Setting it to the average, which is a fairer estimate.
       if self.lastLookup == -1:
         newValues["cpuSampling"] = newValues["cpuAvg"]
       
       self._valLock.acquire()
       self.cpuSampling = newValues["cpuSampling"]
       self.cpuAvg = newValues["cpuAvg"]
       self.memUsage = newValues["memUsage"]
       self.memUsagePercentage = newValues["memUsagePercentage"]
       self._lastCpuTotal = newValues["_lastCpuTotal"]
       self.lastLookup = time.time()
       self._runCount += 1
       self._failureCount = 0
       self._valLock.release()
예제 #37
0
  except socket.error, exc:
    log.info("Failed to send message: %s" % exc)

    # When sending there doesn't seem to be a reliable method for
    # distinguishing between failures from a disconnect verses other things.
    # Just accounting for known disconnection responses.

    if str(exc) == "[Errno 32] Broken pipe":
      raise stem.SocketClosed(exc)
    else:
      raise stem.SocketError(exc)
  except AttributeError:
    # if the control_file has been closed then flush will receive:
    # AttributeError: 'NoneType' object has no attribute 'sendall'

    log.info("Failed to send message: file has been closed")
    raise stem.SocketClosed("file has been closed")


def recv_message(control_file):
  """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
예제 #38
0
  def _parse_message(self):
    # Example:
    #   250-PROTOCOLINFO 1
    #   250-AUTH METHODS=COOKIE COOKIEFILE="/home/atagar/.tor/control_auth_cookie"
    #   250-VERSION Tor="0.2.1.30"
    #   250 OK

    self.protocol_version = None
    self.tor_version = None
    self.auth_methods = ()
    self.unknown_auth_methods = ()
    self.cookie_path = None

    auth_methods, unknown_auth_methods = [], []
    remaining_lines = list(self)

    if not self.is_ok() or not remaining_lines.pop() == "OK":
      raise stem.ProtocolError("PROTOCOLINFO response didn't have an OK status:\n%s" % self)

    # sanity check that we're a PROTOCOLINFO response
    if not remaining_lines[0].startswith("PROTOCOLINFO"):
      raise stem.ProtocolError("Message is not a PROTOCOLINFO response:\n%s" % self)

    while remaining_lines:
      line = remaining_lines.pop(0)
      line_type = line.pop()

      if line_type == "PROTOCOLINFO":
        # Line format:
        #   FirstLine = "PROTOCOLINFO" SP PIVERSION CRLF
        #   PIVERSION = 1*DIGIT

        if line.is_empty():
          raise stem.ProtocolError("PROTOCOLINFO response's initial line is missing the protocol version: %s" % line)

        try:
          self.protocol_version = int(line.pop())
        except ValueError:
          raise stem.ProtocolError("PROTOCOLINFO response version is non-numeric: %s" % line)

        # The piversion really should be "1" but, according to the spec, tor
        # does not necessarily need to provide the PROTOCOLINFO version that we
        # requested. Log if it's something we aren't expecting but still make
        # an effort to parse like a v1 response.

        if self.protocol_version != 1:
          log.info("We made a PROTOCOLINFO version 1 query but got a version %i response instead. We'll still try to use it, but this may cause problems." % self.protocol_version)
      elif line_type == "AUTH":
        # Line format:
        #   AuthLine = "250-AUTH" SP "METHODS=" AuthMethod *("," AuthMethod)
        #              *(SP "COOKIEFILE=" AuthCookieFile) CRLF
        #   AuthMethod = "NULL" / "HASHEDPASSWORD" / "COOKIE"
        #   AuthCookieFile = QuotedString

        # parse AuthMethod mapping
        if not line.is_next_mapping("METHODS"):
          raise stem.ProtocolError("PROTOCOLINFO response's AUTH line is missing its mandatory 'METHODS' mapping: %s" % line)

        for method in line.pop_mapping()[1].split(","):
          if method == "NULL":
            auth_methods.append(AuthMethod.NONE)
          elif method == "HASHEDPASSWORD":
            auth_methods.append(AuthMethod.PASSWORD)
          elif method == "COOKIE":
            auth_methods.append(AuthMethod.COOKIE)
          elif method == "SAFECOOKIE":
            auth_methods.append(AuthMethod.SAFECOOKIE)
          else:
            unknown_auth_methods.append(method)
            message_id = "stem.response.protocolinfo.unknown_auth_%s" % method
            log.log_once(message_id, log.INFO, "PROTOCOLINFO response included a type of authentication that we don't recognize: %s" % method)

            # our auth_methods should have a single AuthMethod.UNKNOWN entry if
            # any unknown authentication methods exist
            if not AuthMethod.UNKNOWN in auth_methods:
              auth_methods.append(AuthMethod.UNKNOWN)

        # parse optional COOKIEFILE mapping (quoted and can have escapes)
        if line.is_next_mapping("COOKIEFILE", True, True):
          self.cookie_path = line.pop_mapping(True, True)[1]
      elif line_type == "VERSION":
        # Line format:
        #   VersionLine = "250-VERSION" SP "Tor=" TorVersion OptArguments CRLF
        #   TorVersion = QuotedString

        if not line.is_next_mapping("Tor", True):
          raise stem.ProtocolError("PROTOCOLINFO response's VERSION line is missing its mandatory tor version mapping: %s" % line)

        try:
          self.tor_version = stem.version.Version(line.pop_mapping(True)[1])
        except ValueError as exc:
          raise stem.ProtocolError(exc)
      else:
        log.debug("Unrecognized PROTOCOLINFO line type '%s', ignoring it: %s" % (line_type, line))

    self.auth_methods = tuple(auth_methods)
    self.unknown_auth_methods = tuple(unknown_auth_methods)
예제 #39
0
def renderTorrc(template, options, commentIndent=30):
    """
  Uses the given template to generate a nicely formatted torrc with the given
  options. The tempating language this recognizes is a simple one, recognizing
  the following options:
    [IF <option>]         # if <option> maps to true or a non-empty string
    [IF NOT <option>]     # logical inverse
    [IF <opt1> | <opt2>]  # logical or of the options
    [ELSE]          # if the prior conditional evaluated to false
    [END IF]        # ends the control block
    
    [<option>]      # inputs the option value, omitting the line if it maps
                    # to a boolean or empty string
    [NEWLINE]       # empty line, otherwise templating white space is ignored
  
  Arguments:
    template      - torrc template lines used to generate the results
    options       - mapping of keywords to their given values, with values
                    being booleans or strings (possibly multi-line)
    commentIndent - minimum column that comments align on
  """

    results = []
    templateIter = iter(template)
    commentLineFormat = "%%-%is%%s" % commentIndent

    try:
        while True:
            line = templateIter.next().strip()

            if line.startswith("[IF ") and line.endswith("]"):
                # checks if any of the conditional options are true or a non-empty string
                evaluatesTrue = False
                for cond in line[4:-1].split("|"):
                    isInverse = False
                    if cond.startswith("NOT "):
                        isInverse = True
                        cond = cond[4:]

                    if isInverse != bool(options.get(cond.strip())):
                        evaluatesTrue = True
                        break

                if evaluatesTrue:
                    continue
                else:
                    # skips lines until we come to an else or the end of the block
                    depth = 0

                    while depth != -1:
                        line = templateIter.next().strip()

                        if line.startswith("[IF ") and line.endswith("]"):
                            depth += 1
                        elif line == "[END IF]":
                            depth -= 1
                        elif depth == 0 and line == "[ELSE]":
                            depth -= 1
            elif line == "[ELSE]":
                # an else block we aren't using - skip to the end of it
                depth = 0

                while depth != -1:
                    line = templateIter.next().strip()

                    if line.startswith("[IF "): depth += 1
                    elif line == "[END IF]": depth -= 1
            elif line == "[NEWLINE]":
                # explicit newline
                results.append("")
            elif line.startswith("#"):
                # comment only
                results.append(line)
            elif line.startswith("[") and line.endswith("]"):
                # completely dynamic entry
                optValue = options.get(line[1:-1])
                if optValue: results.append(optValue)
            else:
                # torrc option line
                option, arg, comment = "", "", ""
                parsedLine = line

                if "#" in parsedLine:
                    parsedLine, comment = parsedLine.split("#", 1)
                    parsedLine = parsedLine.strip()
                    comment = "# %s" % comment.strip()

                # parses the argument from the option
                if " " in parsedLine.strip():
                    option, arg = parsedLine.split(" ", 1)
                    option = option.strip()
                else:
                    log.info("torrc template option lacks an argument: '%s'" %
                             line)
                    continue

                # inputs dynamic arguments
                if arg.startswith("[") and arg.endswith("]"):
                    arg = options.get(arg[1:-1])

                # skips argument if it's false or an empty string
                if not arg: continue

                torrcEntry = "%s %s" % (option, arg)
                if comment:
                    results.append(commentLineFormat %
                                   (torrcEntry + " ", comment))
                else:
                    results.append(torrcEntry)
    except StopIteration:
        pass

    return "\n".join(results)
예제 #40
0
파일: socket.py 프로젝트: Fuzew/sharp-stem
def recv_message(control_file):
  """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
    * :class:`stem.SocketClosed` if the socket closes before we receive
      a complete message
  """

  parsed_content, raw_content = [], []
  logging_prefix = 'Error while receiving a control message (%s): '

  while True:
    try:
      # From a real socket readline() would always provide bytes, but during
      # tests we might be given a StringIO in which case it's unicode under
      # python 3.x.

      line = stem.util.str_tools._to_bytes(control_file.readline())
    except AttributeError:
      # if the control_file has been closed then we will receive:
      # AttributeError: 'NoneType' object has no attribute 'recv'

      prefix = logging_prefix % 'SocketClosed'
      log.info(prefix + 'socket file has been closed')
      raise stem.SocketClosed('socket file has been closed')
    except (socket.error, ValueError) as exc:
      # When disconnected we get...
      #
      # Python 2:
      #   socket.error: [Errno 107] Transport endpoint is not connected
      #
      # Python 3:
      #   ValueError: I/O operation on closed file.

      prefix = logging_prefix % 'SocketClosed'
      log.info(prefix + 'received exception "%s"' % exc)
      raise stem.SocketClosed(exc)

    raw_content.append(line)

    # Parses the tor control lines. These are of the form...
    # <status code><divider><content>\r\n

    if len(line) == 0:
      # if the socket is disconnected then the readline() method will provide
      # empty content

      prefix = logging_prefix % 'SocketClosed'
      log.info(prefix + 'empty socket content')
      raise stem.SocketClosed('Received empty socket content.')
    elif len(line) < 4:
      prefix = logging_prefix % 'ProtocolError'
      log.info(prefix + 'line too short, "%s"' % log.escape(line))
      raise stem.ProtocolError('Badly formatted reply line: too short')
    elif not re.match(b'^[a-zA-Z0-9]{3}[-+ ]', line):
      prefix = logging_prefix % 'ProtocolError'
      log.info(prefix + 'malformed status code/divider, "%s"' % log.escape(line))
      raise stem.ProtocolError('Badly formatted reply line: beginning is malformed')
    elif not line.endswith(b'\r\n'):
      prefix = logging_prefix % 'ProtocolError'
      log.info(prefix + 'no CRLF linebreak, "%s"' % log.escape(line))
      raise stem.ProtocolError('All lines should end with CRLF')

    line = line[:-2]  # strips off the CRLF
    status_code, divider, content = line[:3], line[3:4], line[4:]
    content_lines = [content]

    if stem.prereq.is_python_3():
      status_code = stem.util.str_tools._to_unicode(status_code)
      divider = stem.util.str_tools._to_unicode(divider)

    if divider == '-':
      # mid-reply line, keep pulling for more content
      parsed_content.append((status_code, divider, content))
    elif divider == ' ':
      # end of the message, return the message
      parsed_content.append((status_code, divider, content))

      raw_content_str = b''.join(raw_content)
      log_message = raw_content_str.replace(b'\r\n', b'\n').rstrip()
      log.trace('Received from tor:\n' + stem.util.str_tools._to_unicode(log_message))

      return stem.response.ControlMessage(parsed_content, raw_content_str)
    elif divider == '+':
      # data entry, all of the following lines belong to the content until we
      # get a line with just a period

      while True:
        try:
          line = stem.util.str_tools._to_bytes(control_file.readline())
        except socket.error as exc:
          prefix = logging_prefix % 'SocketClosed'
          log.info(prefix + 'received an exception while mid-way through a data reply (exception: "%s", read content: "%s")' % (exc, log.escape(b''.join(raw_content))))
          raise stem.SocketClosed(exc)

        raw_content.append(line)

        if not line.endswith(b'\r\n'):
          prefix = logging_prefix % 'ProtocolError'
          log.info(prefix + 'CRLF linebreaks missing from a data reply, "%s"' % log.escape(b''.join(raw_content)))
          raise stem.ProtocolError('All lines should end with CRLF')
        elif line == b'.\r\n':
          break  # data block termination

        line = line[:-2]  # strips off the CRLF

        # lines starting with a period are escaped by a second period (as per
        # section 2.4 of the control-spec)

        if line.startswith(b'..'):
          line = line[1:]

        content_lines.append(line)

      # joins the content using a newline rather than CRLF separator (more
      # conventional for multi-line string content outside the windows world)

      parsed_content.append((status_code, divider, b'\n'.join(content_lines)))
    else:
      # this should never be reached due to the prefix regex, but might as well
      # be safe...
      prefix = logging_prefix % 'ProtocolError'
      log.warn(prefix + "\"%s\" isn't a recognized divider type" % divider)
      raise stem.ProtocolError("Unrecognized divider type '%s': %s" % (divider, stem.util.str_tools._to_unicode(line)))
예제 #41
0
import base64
import hashlib
import datetime

import stem.descriptor
import stem.descriptor.extrainfo_descriptor
import stem.version
import stem.util.log as log
import stem.util.connection
import stem.util.tor_tools

try:
  import rsa
  IS_RSA_AVAILABLE = True
except ImportError:
  log.info("Unable to import the rsa module. Because of this we'll be unable to verify descriptor signature integrity.")
  IS_RSA_AVAILABLE = False

# relay descriptors must have exactly one of the following
REQUIRED_FIELDS = (
  "router",
  "bandwidth",
  "published",
  "onion-key",
  "signing-key",
  "router-signature",
)

# optional entries that can appear at most once
SINGLE_FIELDS = (
  "platform",
예제 #42
0
파일: connection.py 프로젝트: gsathya/stem
          authenticate_safecookie(controller, cookie_path, False)
        else:
          authenticate_cookie(controller, cookie_path, False)

      return  # success!
    except OpenAuthRejected, exc:
      auth_exceptions.append(exc)
    except IncorrectPassword, exc:
      auth_exceptions.append(exc)
    except PasswordAuthRejected, exc:
      # Since the PROTOCOLINFO says password auth is available we can assume
      # that if PasswordAuthRejected is raised it's being raised in error.
      log.debug("The authenticate_password method raised a PasswordAuthRejected when password auth should be available. Stem may need to be corrected to recognize this response: %s" % exc)
      auth_exceptions.append(IncorrectPassword(str(exc)))
    except AuthSecurityFailure, exc:
      log.info("Tor failed to provide the nonce expected for safecookie authentication. (%s)" % exc)
      auth_exceptions.append(exc)
    except (InvalidClientNonce, UnrecognizedAuthChallengeMethod, AuthChallengeFailed), exc:
      auth_exceptions.append(exc)
    except (IncorrectCookieSize, UnreadableCookieFile, IncorrectCookieValue), exc:
      auth_exceptions.append(exc)
    except CookieAuthRejected, exc:
      auth_func = "authenticate_safecookie" if exc.is_safecookie else "authenticate_cookie"

      log.debug("The %s method raised a CookieAuthRejected when cookie auth should be available. Stem may need to be corrected to recognize this response: %s" % (auth_func, exc))
      auth_exceptions.append(IncorrectCookieValue(str(exc), exc.cookie_path, exc.is_safecookie))
    except stem.ControllerError, exc:
      auth_exceptions.append(AuthenticationFailure(str(exc)))

  # All authentication attempts failed. Raise the exception that takes priority
  # according to our pydocs.
예제 #43
0
파일: sysTools.py 프로젝트: lnrsoft/arm
    def run(self):
        while not self._halt:
            timeSinceReset = time.time() - self.lastLookup

            if self.resolveRate == 0:
                self._cond.acquire()
                if not self._halt: self._cond.wait(0.2)
                self._cond.release()

                continue
            elif timeSinceReset < self.resolveRate:
                sleepTime = max(0.2, self.resolveRate - timeSinceReset)

                self._cond.acquire()
                if not self._halt: self._cond.wait(sleepTime)
                self._cond.release()

                continue  # done waiting, try again

            newValues = {}
            try:
                if self._useProc:
                    utime, stime, startTime = proc.get_stats(
                        self.processPid, proc.Stat.CPU_UTIME,
                        proc.Stat.CPU_STIME, proc.Stat.START_TIME)
                    totalCpuTime = float(utime) + float(stime)
                    cpuDelta = totalCpuTime - self._lastCpuTotal
                    newValues["cpuSampling"] = cpuDelta / timeSinceReset
                    newValues["cpuAvg"] = totalCpuTime / (time.time() -
                                                          float(startTime))
                    newValues["_lastCpuTotal"] = totalCpuTime

                    memUsage = int(proc.get_memory_usage(self.processPid)[0])
                    totalMemory = proc.get_physical_memory()
                    newValues["memUsage"] = memUsage
                    newValues["memUsagePercentage"] = float(
                        memUsage) / totalMemory
                else:
                    # the ps call formats results as:
                    #
                    #     TIME     ELAPSED   RSS %MEM
                    # 3-08:06:32 21-00:00:12 121844 23.5
                    #
                    # or if Tor has only recently been started:
                    #
                    #     TIME      ELAPSED    RSS %MEM
                    #  0:04.40        37:57  18772  0.9

                    psCall = system.call(
                        "ps -p %s -o cputime,etime,rss,%%mem" %
                        self.processPid)

                    isSuccessful = False
                    if psCall and len(psCall) >= 2:
                        stats = psCall[1].strip().split()

                        if len(stats) == 4:
                            try:
                                totalCpuTime = str_tools.parse_short_time_label(
                                    stats[0])
                                uptime = str_tools.parse_short_time_label(
                                    stats[1])
                                cpuDelta = totalCpuTime - self._lastCpuTotal
                                newValues[
                                    "cpuSampling"] = cpuDelta / timeSinceReset
                                newValues["cpuAvg"] = totalCpuTime / uptime
                                newValues["_lastCpuTotal"] = totalCpuTime

                                newValues["memUsage"] = int(
                                    stats[2]) * 1024  # ps size is in kb
                                newValues["memUsagePercentage"] = float(
                                    stats[3]) / 100.0
                                isSuccessful = True
                            except ValueError, exc:
                                pass

                    if not isSuccessful:
                        raise IOError("unrecognized output from ps: %s" %
                                      psCall)
            except IOError, exc:
                newValues = {}
                self._failureCount += 1

                if self._useProc:
                    if self._failureCount >= 3:
                        # We've failed three times resolving via proc. Warn, and fall back
                        # to ps resolutions.
                        log.info(
                            "Failed three attempts to get process resource usage from proc, falling back to ps (%s)"
                            % exc)

                        self._useProc = False
                        self._failureCount = 1  # prevents lastQueryFailed() from thinking that we succeeded
                    else:
                        # wait a bit and try again
                        log.debug(
                            "Unable to query process resource usage from proc (%s)"
                            % exc)
                        self._cond.acquire()
                        if not self._halt: self._cond.wait(0.5)
                        self._cond.release()
                else:
                    # exponential backoff on making failed ps calls
                    sleepTime = 0.01 * (
                        2**self._failureCount) + self._failureCount
                    log.debug(
                        "Unable to query process resource usage from ps, waiting %0.2f seconds (%s)"
                        % (sleepTime, exc))
                    self._cond.acquire()
                    if not self._halt: self._cond.wait(sleepTime)
                    self._cond.release()

            # sets the new values
            if newValues:
                # If this is the first run then the cpuSampling stat is meaningless
                # (there isn't a previous tick to sample from so it's zero at this
                # point). Setting it to the average, which is a fairer estimate.
                if self.lastLookup == -1:
                    newValues["cpuSampling"] = newValues["cpuAvg"]

                self._valLock.acquire()
                self.cpuSampling = newValues["cpuSampling"]
                self.cpuAvg = newValues["cpuAvg"]
                self.memUsage = newValues["memUsage"]
                self.memUsagePercentage = newValues["memUsagePercentage"]
                self._lastCpuTotal = newValues["_lastCpuTotal"]
                self.lastLookup = time.time()
                self._runCount += 1
                self._failureCount = 0
                self._valLock.release()
예제 #44
0
    except socket.error, exc:
        log.info("Failed to send message: %s" % exc)

        # When sending there doesn't seem to be a reliable method for
        # distinguishing between failures from a disconnect verses other things.
        # Just accounting for known disconnection responses.

        if str(exc) == "[Errno 32] Broken pipe":
            raise stem.SocketClosed(exc)
        else:
            raise stem.SocketError(exc)
    except AttributeError:
        # if the control_file has been closed then flush will receive:
        # AttributeError: 'NoneType' object has no attribute 'sendall'

        log.info("Failed to send message: file has been closed")
        raise stem.SocketClosed("file has been closed")


def recv_message(control_file):
    """
  Pulls from a control socket until we either have a complete message or
  encounter a problem.

  :param file control_file: file derived from the control socket (see the
    socket's makefile() method for more information)

  :returns: :class:`~stem.response.ControlMessage` read from the socket

  :raises:
    * :class:`stem.ProtocolError` the content from the socket is malformed
예제 #45
0
def authenticate(controller, password = None, chroot_path = None, protocolinfo_response = None):
  """
  Authenticates to a control socket using the information provided by a
  PROTOCOLINFO response. In practice this will often be all we need to
  authenticate, raising an exception if all attempts to authenticate fail.

  All exceptions are subclasses of AuthenticationFailure so, in practice,
  callers should catch the types of authentication failure that they care
  about, then have a :class:`~stem.connection.AuthenticationFailure` catch-all
  at the end.

  This can authenticate to either a :class:`~stem.control.BaseController` or
  :class:`~stem.socket.ControlSocket`.

  :param controller: tor controller or socket to be authenticated
  :param str password: passphrase to present to the socket if it uses password
    authentication (skips password auth if **None**)
  :param str chroot_path: path prefix if in a chroot environment
  :param stem.response.protocolinfo.ProtocolInfoResponse protocolinfo_response:
    tor protocolinfo response, this is retrieved on our own if **None**

  :raises: If all attempts to authenticate fails then this will raise a
    :class:`~stem.connection.AuthenticationFailure` subclass. Since this may
    try multiple authentication methods it may encounter multiple exceptions.
    If so then the exception this raises is prioritized as follows...

    * :class:`stem.connection.IncorrectSocketType`

      The controller does not speak the tor control protocol. Most often this
      happened because the user confused the SocksPort or ORPort with the
      ControlPort.

    * :class:`stem.connection.UnrecognizedAuthMethods`

      All of the authentication methods tor will accept are new and
      unrecognized. Please upgrade stem and, if that doesn't work, file a
      ticket on 'trac.torproject.org' and I'd be happy to add support.

    * :class:`stem.connection.MissingPassword`

      We were unable to authenticate but didn't attempt password authentication
      because none was provided. You should prompt the user for a password and
      try again via 'authenticate_password'.

    * :class:`stem.connection.IncorrectPassword`

      We were provided with a password but it was incorrect.

    * :class:`stem.connection.IncorrectCookieSize`

      Tor allows for authentication by reading it a cookie file, but that file
      is the wrong size to be an authentication cookie.

    * :class:`stem.connection.UnreadableCookieFile`

      Tor allows for authentication by reading it a cookie file, but we can't
      read that file (probably due to permissions).

    * **\***:class:`stem.connection.IncorrectCookieValue`

      Tor allows for authentication by reading it a cookie file, but rejected
      the contents of that file.

    * **\***:class:`stem.connection.AuthChallengeUnsupported`

      Tor doesn't recognize the AUTHCHALLENGE command. This is probably a Tor
      version prior to SAFECOOKIE being implement, but this exception shouldn't
      arise because we won't attempt SAFECOOKIE auth unless Tor claims to
      support it.

    * **\***:class:`stem.connection.UnrecognizedAuthChallengeMethod`

      Tor couldn't recognize the AUTHCHALLENGE method Stem sent to it. This
      shouldn't happen at all.

    * **\***:class:`stem.connection.InvalidClientNonce`

      Tor says that the client nonce provided by Stem during the AUTHCHALLENGE
      process is invalid.

    * **\***:class:`stem.connection.AuthSecurityFailure`

      Nonce value provided by the server was invalid.

    * **\***:class:`stem.connection.OpenAuthRejected`

      Tor says that it allows for authentication without any credentials, but
      then rejected our authentication attempt.

    * **\***:class:`stem.connection.MissingAuthInfo`

      Tor provided us with a PROTOCOLINFO reply that is technically valid, but
      missing the information we need to authenticate.

    * **\***:class:`stem.connection.AuthenticationFailure`

      There are numerous other ways that authentication could have failed
      including socket failures, malformed controller responses, etc. These
      mostly constitute transient failures or bugs.

    **\*** In practice it is highly unusual for this to occur, being more of a
    theoretical possibility rather than something you should expect. It's fine
    to treat these as errors. If you have a use case where this commonly
    happens, please file a ticket on 'trac.torproject.org'.

    In the future new :class:`~stem.connection.AuthenticationFailure`
    subclasses may be added to allow for better error handling.
  """

  if not protocolinfo_response:
    try:
      protocolinfo_response = get_protocolinfo(controller)
    except stem.ProtocolError:
      raise IncorrectSocketType('unable to use the control socket')
    except stem.SocketError as exc:
      raise AuthenticationFailure('socket connection failed (%s)' % exc)

  auth_methods = list(protocolinfo_response.auth_methods)
  auth_exceptions = []

  if len(auth_methods) == 0:
    raise NoAuthMethods('our PROTOCOLINFO response did not have any methods for authenticating')

  # remove authentication methods that are either unknown or for which we don't
  # have an input
  if AuthMethod.UNKNOWN in auth_methods:
    auth_methods.remove(AuthMethod.UNKNOWN)

    unknown_methods = protocolinfo_response.unknown_auth_methods
    plural_label = 's' if len(unknown_methods) > 1 else ''
    methods_label = ', '.join(unknown_methods)

    # we... er, can't do anything with only unrecognized auth types
    if not auth_methods:
      exc_msg = 'unrecognized authentication method%s (%s)' % (plural_label, methods_label)
      auth_exceptions.append(UnrecognizedAuthMethods(exc_msg, unknown_methods))
    else:
      log.debug('Authenticating to a socket with unrecognized auth method%s, ignoring them: %s' % (plural_label, methods_label))

  if protocolinfo_response.cookie_path is None:
    for cookie_auth_method in (AuthMethod.COOKIE, AuthMethod.SAFECOOKIE):
      if cookie_auth_method in auth_methods:
        auth_methods.remove(cookie_auth_method)

        exc_msg = 'our PROTOCOLINFO response did not have the location of our authentication cookie'
        auth_exceptions.append(NoAuthCookie(exc_msg, cookie_auth_method == AuthMethod.SAFECOOKIE))

  if AuthMethod.PASSWORD in auth_methods and password is None:
    auth_methods.remove(AuthMethod.PASSWORD)
    auth_exceptions.append(MissingPassword('no passphrase provided'))

  # iterating over AuthMethods so we can try them in this order
  for auth_type in (AuthMethod.NONE, AuthMethod.PASSWORD, AuthMethod.SAFECOOKIE, AuthMethod.COOKIE):
    if auth_type not in auth_methods:
      continue

    try:
      if auth_type == AuthMethod.NONE:
        authenticate_none(controller, False)
      elif auth_type == AuthMethod.PASSWORD:
        authenticate_password(controller, password, False)
      elif auth_type in (AuthMethod.COOKIE, AuthMethod.SAFECOOKIE):
        cookie_path = protocolinfo_response.cookie_path

        if chroot_path:
          cookie_path = os.path.join(chroot_path, cookie_path.lstrip(os.path.sep))

        if auth_type == AuthMethod.SAFECOOKIE:
          authenticate_safecookie(controller, cookie_path, False)
        else:
          authenticate_cookie(controller, cookie_path, False)

      return  # success!
    except OpenAuthRejected as exc:
      auth_exceptions.append(exc)
    except IncorrectPassword as exc:
      auth_exceptions.append(exc)
    except PasswordAuthRejected as exc:
      # Since the PROTOCOLINFO says password auth is available we can assume
      # that if PasswordAuthRejected is raised it's being raised in error.
      log.debug('The authenticate_password method raised a PasswordAuthRejected when password auth should be available. Stem may need to be corrected to recognize this response: %s' % exc)
      auth_exceptions.append(IncorrectPassword(str(exc)))
    except AuthSecurityFailure as exc:
      log.info('Tor failed to provide the nonce expected for safecookie authentication. (%s)' % exc)
      auth_exceptions.append(exc)
    except (InvalidClientNonce, UnrecognizedAuthChallengeMethod, AuthChallengeFailed) as exc:
      auth_exceptions.append(exc)
    except (IncorrectCookieSize, UnreadableCookieFile, IncorrectCookieValue) as exc:
      auth_exceptions.append(exc)
    except CookieAuthRejected as exc:
      auth_func = 'authenticate_safecookie' if exc.is_safecookie else 'authenticate_cookie'

      log.debug('The %s method raised a CookieAuthRejected when cookie auth should be available. Stem may need to be corrected to recognize this response: %s' % (auth_func, exc))
      auth_exceptions.append(IncorrectCookieValue(str(exc), exc.cookie_path, exc.is_safecookie))
    except stem.ControllerError as exc:
      auth_exceptions.append(AuthenticationFailure(str(exc)))

  # All authentication attempts failed. Raise the exception that takes priority
  # according to our pydocs.

  for exc_type in AUTHENTICATE_EXCEPTIONS:
    for auth_exc in auth_exceptions:
      if isinstance(auth_exc, exc_type):
        raise auth_exc

  # We really, really shouldn't get here. It means that auth_exceptions is
  # either empty or contains something that isn't an AuthenticationFailure.

  raise AssertionError('BUG: Authentication failed without providing a recognized exception: %s' % str(auth_exceptions))
예제 #46
0
def load_configuration_descriptions(path_prefix):
  """
  Attempts to load descriptions for tor's configuration options, fetching them
  from the man page and persisting them to a file to speed future startups.
  """

  # It is important that this is loaded before entering the curses context,
  # otherwise the man call pegs the cpu for around a minute (I'm not sure
  # why... curses must mess the terminal in a way that's important to man).

  if CONFIG['features.config.descriptions.enabled']:
    is_config_descriptions_loaded = False

    # determines the path where cached descriptions should be persisted (left
    # undefined if caching is disabled)

    descriptor_path = None

    if CONFIG['features.config.descriptions.persist']:
      data_dir = CONFIG['startup.data_directory']

      if not data_dir.endswith('/'):
        data_dir += '/'

      descriptor_path = os.path.expanduser(data_dir + 'cache/') + CONFIG_DESC_FILENAME

    # attempts to load configuration descriptions cached in the data directory

    if descriptor_path:
      try:
        load_start_time = time.time()
        load_option_descriptions(descriptor_path)
        is_config_descriptions_loaded = True

        log.info(DESC_LOAD_SUCCESS_MSG % (descriptor_path, time.time() - load_start_time))
      except IOError as exc:
        log.info(DESC_LOAD_FAILED_MSG % exc.strerror)

    # fetches configuration options from the man page

    if not is_config_descriptions_loaded:
      try:
        load_start_time = time.time()
        load_option_descriptions()
        is_config_descriptions_loaded = True

        log.info(DESC_READ_MAN_SUCCESS_MSG % (time.time() - load_start_time))
      except IOError as exc:
        log.notice(DESC_READ_MAN_FAILED_MSG % exc.strerror)

      # persists configuration descriptions

      if is_config_descriptions_loaded and descriptor_path:
        try:
          load_start_time = time.time()
          save_option_descriptions(descriptor_path)
          log.info(DESC_SAVE_SUCCESS_MSG % (descriptor_path, time.time() - load_start_time))
        except IOError as exc:
          log.notice(DESC_SAVE_FAILED_MSG % exc.strerror)
        except OSError as exc:
          log.notice(DESC_SAVE_FAILED_MSG % exc)

    # finally fall back to the cached descriptors provided with nyx (this is
    # often the case for tbb and manual builds)

    if not is_config_descriptions_loaded:
      try:
        load_start_time = time.time()
        loaded_version = load_option_descriptions('%sresources/%s' % (path_prefix, CONFIG_DESC_FILENAME), False)
        is_config_descriptions_loaded = True
        log.notice(DESC_INTERNAL_LOAD_SUCCESS_MSG % loaded_version)
      except IOError as exc:
        log.error(DESC_INTERNAL_LOAD_FAILED_MSG % exc.strerror)