Stem Docs

stem.version

Source code for stem.version

# Copyright 2011-2019, Damian Johnson and The Tor Project
# See LICENSE for licensing information

"""
Tor versioning information and requirements for its features. These can be
easily parsed and compared, for instance...

::

  >>> from stem.version import get_system_tor_version, Requirement
  >>> my_version = get_system_tor_version()
  >>> print(my_version)
  0.2.1.30
  >>> my_version >= Requirement.TORRC_CONTROL_SOCKET
  True

**Module Overview:**

::

  get_system_tor_version - gets the version of our system's tor installation

  Version - Tor versioning information

.. data:: Requirement (enum)

  Enumerations for the version requirements of features.

  .. deprecated:: 1.6.0
     Requirement entries belonging to tor versions which have been obsolete for
     at least six months will be removed when we break backward compatibility
     in the 2.x stem release.

  ===================================== ===========
  Requirement                           Description
  ===================================== ===========
  **AUTH_SAFECOOKIE**                   SAFECOOKIE authentication method
  **DESCRIPTOR_COMPRESSION**            `Expanded compression support for ZSTD and LZMA <https://gitweb.torproject.org/torspec.git/commit/?id=1cb56afdc1e55e303e3e6b69e90d983ee217d93f>`_
  **DORMANT_MODE**                      **DORMANT** and **ACTIVE** :data:`~stem.Signal`
  **DROPGUARDS**                        DROPGUARDS requests
  **EVENT_AUTHDIR_NEWDESCS**            AUTHDIR_NEWDESC events
  **EVENT_BUILDTIMEOUT_SET**            BUILDTIMEOUT_SET events
  **EVENT_CIRC_MINOR**                  CIRC_MINOR events
  **EVENT_CLIENTS_SEEN**                CLIENTS_SEEN events
  **EVENT_CONF_CHANGED**                CONF_CHANGED events
  **EVENT_DESCCHANGED**                 DESCCHANGED events
  **EVENT_GUARD**                       GUARD events
  **EVENT_HS_DESC_CONTENT**             HS_DESC_CONTENT events
  **EVENT_NETWORK_LIVENESS**            NETWORK_LIVENESS events
  **EVENT_NEWCONSENSUS**                NEWCONSENSUS events
  **EVENT_NS**                          NS events
  **EVENT_SIGNAL**                      SIGNAL events
  **EVENT_STATUS**                      STATUS_GENERAL, STATUS_CLIENT, and STATUS_SERVER events
  **EVENT_STREAM_BW**                   STREAM_BW events
  **EVENT_TRANSPORT_LAUNCHED**          TRANSPORT_LAUNCHED events
  **EVENT_CONN_BW**                     CONN_BW events
  **EVENT_CIRC_BW**                     CIRC_BW events
  **EVENT_CELL_STATS**                  CELL_STATS events
  **EVENT_TB_EMPTY**                    TB_EMPTY events
  **EVENT_HS_DESC**                     HS_DESC events
  **EXTENDCIRCUIT_PATH_OPTIONAL**       EXTENDCIRCUIT queries can omit the path if the circuit is zero
  **FEATURE_EXTENDED_EVENTS**           'EXTENDED_EVENTS' optional feature
  **FEATURE_VERBOSE_NAMES**             'VERBOSE_NAMES' optional feature
  **GETINFO_CONFIG_TEXT**               'GETINFO config-text' query
  **GETINFO_GEOIP_AVAILABLE**           'GETINFO ip-to-country/ipv4-available' query and its ipv6 counterpart
  **GETINFO_MICRODESCRIPTORS**          'GETINFO md/all' query
  **GETINFO_UPTIME**                    'GETINFO uptime' query
  **HIDDEN_SERVICE_V3**                 Support for v3 hidden services
  **HSFETCH**                           HSFETCH requests
  **HSFETCH_V3**                        HSFETCH for version 3 hidden services
  **HSPOST**                            HSPOST requests
  **ADD_ONION**                         ADD_ONION and DEL_ONION requests
  **ADD_ONION_BASIC_AUTH**              ADD_ONION supports basic authentication
  **ADD_ONION_NON_ANONYMOUS**           ADD_ONION supports non-anonymous mode
  **ADD_ONION_MAX_STREAMS**             ADD_ONION support for MaxStreamsCloseCircuit
  **LOADCONF**                          LOADCONF requests
  **MICRODESCRIPTOR_IS_DEFAULT**        Tor gets microdescriptors by default rather than server descriptors
  **SAVECONF_FORCE**                    Added the 'FORCE' flag to SAVECONF
  **TAKEOWNERSHIP**                     TAKEOWNERSHIP requests
  **TORRC_CONTROL_SOCKET**              'ControlSocket <path>' config option
  **TORRC_PORT_FORWARDING**             'PortForwarding' config option
  **TORRC_DISABLE_DEBUGGER_ATTACHMENT** 'DisableDebuggerAttachment' config option
  **TORRC_VIA_STDIN**                   Allow torrc options via 'tor -f -' (:trac:`13865`)
  **ONION_SERVICE_AUTH_ADD**            For adding ClientAuthV3 to a v3 onion service via ADD_ONION
  ===================================== ===========
"""

import os
import re

import stem.prereq
import stem.util
import stem.util.enum
import stem.util.system

if stem.prereq._is_lru_cache_available():
  from functools import lru_cache
else:
  from stem.util.lru_cache import lru_cache

# cache for the get_system_tor_version function
VERSION_CACHE = {}

VERSION_PATTERN = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)(\.[0-9]+)?(-\S*)?(( \(\S*\))*)$')


[docs]def get_system_tor_version(tor_cmd = 'tor'): """ Queries tor for its version. This is os dependent, only working on linux, osx, and bsd. :param str tor_cmd: command used to run tor :returns: :class:`~stem.version.Version` provided by the tor command :raises: **IOError** if unable to query or parse the version """ if tor_cmd not in VERSION_CACHE: version_cmd = '%s --version' % tor_cmd try: version_output = stem.util.system.call(version_cmd) except OSError as exc: # make the error message nicer if this is due to tor being unavialable if 'No such file or directory' in str(exc): if os.path.isabs(tor_cmd): exc = "Unable to check tor's version. '%s' doesn't exist." % tor_cmd else: exc = "Unable to run '%s'. Maybe tor isn't in your PATH?" % version_cmd raise IOError(exc) for line in version_output: # output example: # Oct 21 07:19:27.438 [notice] Tor v0.2.1.30. This is experimental software. Do not rely on it for strong anonymity. (Running on Linux i686) # Tor version 0.2.1.30. if line.startswith('Tor version ') and line.endswith('.'): try: version_str = line[12:-1] VERSION_CACHE[tor_cmd] = Version(version_str) break except ValueError as exc: raise IOError(exc) if tor_cmd not in VERSION_CACHE: raise IOError("'%s' didn't provide a parseable version:\n\n%s" % (version_cmd, '\n'.join(version_output))) return VERSION_CACHE[tor_cmd]
@lru_cache() def _get_version(version_str): return Version(version_str)
[docs]class Version(object): """ Comparable tor version. These are constructed from strings that conform to the 'new' style in the `tor version-spec <https://gitweb.torproject.org/torspec.git/tree/version-spec.txt>`_, such as "0.1.4" or "0.2.2.23-alpha (git-7dcd105be34a4f44)". .. versionchanged:: 1.6.0 Added all_extra parameter. :var int major: major version :var int minor: minor version :var int micro: micro version :var int patch: patch level (**None** if undefined) :var str status: status tag such as 'alpha' or 'beta-dev' (**None** if undefined) :var str extra: first extra information without its parentheses such as 'git-8be6058d8f31e578' (**None** if undefined) :var list all_extra: all extra information entries, without their parentheses :var str git_commit: git commit id (**None** if it wasn't provided) :param str version_str: version to be parsed :raises: **ValueError** if input isn't a valid tor version """ def __init__(self, version_str): self.version_str = version_str version_parts = VERSION_PATTERN.match(version_str) if version_parts: major, minor, micro, patch, status, extra_str, _ = version_parts.groups() # The patch and status matches are optional (may be None) and have an extra # proceeding period or dash if they exist. Stripping those off. if patch: patch = int(patch[1:]) if status: status = status[1:] self.major = int(major) self.minor = int(minor) self.micro = int(micro) self.patch = patch self.status = status self.all_extra = [entry[1:-1] for entry in extra_str.strip().split()] if extra_str else [] self.extra = self.all_extra[0] if self.all_extra else None self.git_commit = None for extra in self.all_extra: if extra and re.match('^git-[0-9a-f]{16}$', extra): self.git_commit = extra[4:] break else: raise ValueError("'%s' isn't a properly formatted tor version" % version_str) def __str__(self): """ Provides the string used to construct the version. """ return self.version_str def _compare(self, other, method): """ Compares version ordering according to the spec. """ if not isinstance(other, Version): return False for attr in ('major', 'minor', 'micro', 'patch'): my_version = getattr(self, attr) other_version = getattr(other, attr) if my_version is None: my_version = 0 if other_version is None: other_version = 0 if my_version != other_version: return method(my_version, other_version) # According to the version spec... # # If we *do* encounter two versions that differ only by status tag, we # compare them lexically as ASCII byte strings. my_status = self.status if self.status else '' other_status = other.status if other.status else '' return method(my_status, other_status) def __hash__(self): return stem.util._hash_attr(self, 'major', 'minor', 'micro', 'patch', 'status', cache = True) def __eq__(self, other): return self._compare(other, lambda s, o: s == o) def __ne__(self, other): return not self == other def __gt__(self, other): """ Checks if this version meets the requirements for a given feature. We can be compared to either a :class:`~stem.version.Version` or :class:`~stem.version._VersionRequirements`. """ if isinstance(other, _VersionRequirements): for rule in other.rules: if rule(self): return True return False return self._compare(other, lambda s, o: s > o) def __ge__(self, other): if isinstance(other, _VersionRequirements): for rule in other.rules: if rule(self): return True return False return self._compare(other, lambda s, o: s >= o)
class _VersionRequirements(object): """ Series of version constraints that can be compared to. For instance, this allows for comparisons like 'if I'm greater than version X in the 0.2.2 series, or greater than version Y in the 0.2.3 series'. This is a logical 'or' of the series of rules. """ def __init__(self): self.rules = [] def greater_than(self, version, inclusive = True): """ Adds a constraint that we're greater than the given version. :param stem.version.Version version: version we're checking against :param bool inclusive: if comparison is inclusive or not """ if inclusive: self.rules.append(lambda v: version <= v) else: self.rules.append(lambda v: version < v) def less_than(self, version, inclusive = True): """ Adds a constraint that we're less than the given version. :param stem.version.Version version: version we're checking against :param bool inclusive: if comparison is inclusive or not """ if inclusive: self.rules.append(lambda v: version >= v) else: self.rules.append(lambda v: version > v) def in_range(self, from_version, to_version, from_inclusive = True, to_inclusive = False): """ Adds constraint that we're within the range from one version to another. :param stem.version.Version from_version: beginning of the comparison range :param stem.version.Version to_version: end of the comparison range :param bool from_inclusive: if comparison is inclusive with the starting version :param bool to_inclusive: if comparison is inclusive with the ending version """ def new_rule(v): if from_inclusive and to_inclusive: return from_version <= v <= to_version elif from_inclusive: return from_version <= v < to_version else: return from_version < v < to_version self.rules.append(new_rule) safecookie_req = _VersionRequirements() safecookie_req.in_range(Version('0.2.2.36'), Version('0.2.3.0')) safecookie_req.greater_than(Version('0.2.3.13')) Requirement = stem.util.enum.Enum( ('AUTH_SAFECOOKIE', safecookie_req), ('DESCRIPTOR_COMPRESSION', Version('0.3.1.1-alpha')), ('DORMANT_MODE', Version('0.4.0.1-alpha')), ('DROPGUARDS', Version('0.2.5.1-alpha')), ('EVENT_AUTHDIR_NEWDESCS', Version('0.1.1.10-alpha')), ('EVENT_BUILDTIMEOUT_SET', Version('0.2.2.7-alpha')), ('EVENT_CIRC_MINOR', Version('0.2.3.11-alpha')), ('EVENT_CLIENTS_SEEN', Version('0.2.1.10-alpha')), ('EVENT_CONF_CHANGED', Version('0.2.3.3-alpha')), ('EVENT_DESCCHANGED', Version('0.1.2.2-alpha')), ('EVENT_GUARD', Version('0.1.2.5-alpha')), ('EVENT_HS_DESC_CONTENT', Version('0.2.7.1-alpha')), ('EVENT_NS', Version('0.1.2.3-alpha')), ('EVENT_NETWORK_LIVENESS', Version('0.2.7.2-alpha')), ('EVENT_NEWCONSENSUS', Version('0.2.1.13-alpha')), ('EVENT_SIGNAL', Version('0.2.3.1-alpha')), ('EVENT_STATUS', Version('0.1.2.3-alpha')), ('EVENT_STREAM_BW', Version('0.1.2.8-beta')), ('EVENT_TRANSPORT_LAUNCHED', Version('0.2.5.0-alpha')), ('EVENT_CONN_BW', Version('0.2.5.2-alpha')), ('EVENT_CIRC_BW', Version('0.2.5.2-alpha')), ('EVENT_CELL_STATS', Version('0.2.5.2-alpha')), ('EVENT_TB_EMPTY', Version('0.2.5.2-alpha')), ('EVENT_HS_DESC', Version('0.2.5.2-alpha')), ('EXTENDCIRCUIT_PATH_OPTIONAL', Version('0.2.2.9')), ('FEATURE_EXTENDED_EVENTS', Version('0.2.2.1-alpha')), ('FEATURE_VERBOSE_NAMES', Version('0.2.2.1-alpha')), ('GETINFO_CONFIG_TEXT', Version('0.2.2.7-alpha')), ('GETINFO_GEOIP_AVAILABLE', Version('0.3.2.1-alpha')), ('GETINFO_MICRODESCRIPTORS', Version('0.3.5.1-alpha')), ('GETINFO_UPTIME', Version('0.3.5.1-alpha')), ('HIDDEN_SERVICE_V3', Version('0.3.3.1-alpha')), ('HSFETCH', Version('0.2.7.1-alpha')), ('HSFETCH_V3', Version('0.4.1.1-alpha')), ('HSPOST', Version('0.2.7.1-alpha')), ('ADD_ONION', Version('0.2.7.1-alpha')), ('ADD_ONION_BASIC_AUTH', Version('0.2.9.1-alpha')), ('ADD_ONION_NON_ANONYMOUS', Version('0.2.9.3-alpha')), ('ADD_ONION_MAX_STREAMS', Version('0.2.7.2-alpha')), ('LOADCONF', Version('0.2.1.1')), ('MICRODESCRIPTOR_IS_DEFAULT', Version('0.2.3.3')), ('SAVECONF_FORCE', Version('0.3.1.1-alpha')), ('TAKEOWNERSHIP', Version('0.2.2.28-beta')), ('TORRC_CONTROL_SOCKET', Version('0.2.0.30')), ('TORRC_PORT_FORWARDING', Version('0.2.3.1-alpha')), ('TORRC_DISABLE_DEBUGGER_ATTACHMENT', Version('0.2.3.9')), ('TORRC_VIA_STDIN', Version('0.2.6.3-alpha')), ('ONION_SERVICE_AUTH_ADD', Version('0.4.6.1-alpha')), )