o
    u].                     @   s   d Z ddlZddlZddlZddlmZmZmZm	Z	m
Z
mZmZmZmZmZmZ ddlmZmZ ej r<ddlmZ nddlmZ dZdZdd	d
Zdd ZedddZeddZedddd dZedddd dZ eddZ!G dd deZ"dS )au  
Parsing for Tor microdescriptors, which contain a distilled version of a
relay's server descriptor. As of Tor version 0.2.3.3-alpha Tor no longer
downloads server descriptors by default, opting for microdescriptors instead.

Unlike most descriptor documents these aren't available on the metrics site
(since they don't contain any information that the server descriptors don't).

The limited information in microdescriptors make them rather clunky to use
compared with server descriptors. For instance microdescriptors lack the
relay's fingerprint, making it difficut to use them to look up the relay's
other descriptors.

To do so you need to match the microdescriptor's digest against its
corresponding router status entry. For added fun as of this writing the
controller doesn't even surface those router status entries
(:trac:`7953`).

For instance, here's an example that prints the nickname and fingerprints of
the exit relays.

::

  import os

  from stem.control import Controller
  from stem.descriptor import parse_file

  with Controller.from_port(port = 9051) as controller:
    controller.authenticate()

    exit_digests = set()
    data_dir = controller.get_conf('DataDirectory')

    for desc in controller.get_microdescriptors():
      if desc.exit_policy.is_exiting_allowed():
        exit_digests.add(desc.digest)

    print 'Exit Relays:'

    for desc in parse_file(os.path.join(data_dir, 'cached-microdesc-consensus')):
      if desc.digest in exit_digests:
        print '  %s (%s)' % (desc.nickname, desc.fingerprint)

Doing the same is trivial with server descriptors...

::

  from stem.descriptor import parse_file

  print 'Exit Relays:'

  for desc in parse_file('/home/atagar/.tor/cached-descriptors'):
    if desc.exit_policy.is_exiting_allowed():
      print '  %s (%s)' % (desc.nickname, desc.fingerprint)

**Module Overview:**

::

  Microdescriptor - Tor microdescriptor.
    N)
Descriptor
DigestHashDigestEncoding_descriptor_content_descriptor_components_read_until_keywords_values_parse_simple_line_parse_protocol_line_parse_key_block_random_crypto_blob)_parse_a_line_parse_p_line)	lru_cache)	onion-key)r   ntor-onion-keyfamilypp6prFc           	      k   s    	 t d| }g }|  }|r|| ndS 	 |  }|  }|s#n|ds-|dr3| | n|| q|rb|d drH|dd }tttj	|}t
d	|}t|||fi |V  ndS q)
a  
  Iterates over the microdescriptors in a file.

  :param file descriptor_file: file with descriptor content
  :param bool validate: checks the validity of the descriptor's content if
    **True**, skips these checks otherwise
  :param dict kwargs: additional arguments for the descriptor constructor

  :returns: iterator for Microdescriptor instances in the file

  :raises:
    * **ValueError** if the contents is malformed and validate is True
    * **IOError** if the file can't be read
  Tr      @s	   onion-keyr   s   @type   N    )r   readlineappendtell
startswithseeklistmapbytesstripjoinMicrodescriptor)	Zdescriptor_filevalidatekwargsannotationsZdescriptor_linesZonion_key_lineZlast_positionlineZdescriptor_text r(   A/usr/lib/python3/dist-packages/stem/descriptor/microdescriptor.py_parse_filen   s4   


r*   c                 C   sx   i }t d|D ]/}| }t|dkr1|d |d }}||v r&td| || _|| _|||< qtd| || _d S )Nid   r   r   zPThere can only be one 'id' line per a key type, but '%s' appeared multiple timesz='id' lines should contain both the key type and digest: id %s)r   splitlen
ValueErroridentifier_type
identifieridentifiers)
descriptorentriesZ
identitiesentryZ
entry_compZkey_typeZ	key_valuer(   r(   r)   _parse_id_line   s   

r6   r   	onion_keyRSA PUBLIC KEYr   ntor_onion_keyr   c                 C   s
   |  dS )N )r-   vr(   r(   r)   <lambda>   s   
 r=   )funcr   exit_policy_v6c                 C   s   t j| S )N)stemexit_policyMicroExitPolicyr;   r(   r(   r)   r=      s    r   	protocolsc                       s   e Zd ZdZdZdefdefg efg efe	j
defdefdefdefi efi efd
ZeeeeeeeedZedd	d
Zd fdd	ZejejfddZe dd Zdd Zdd ZdddZ  ZS )r#   ak  
  Microdescriptor (`descriptor specification
  <https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt>`_)

  :var str onion_key: **\*** key used to encrypt EXTEND cells
  :var str ntor_onion_key: base64 key used to encrypt EXTEND in the ntor protocol
  :var list or_addresses: **\*** alternative for our address/or_port attributes, each
    entry is a tuple of the form (address (**str**), port (**int**), is_ipv6
    (**bool**))
  :var list family: **\*** nicknames or fingerprints of declared family
  :var stem.exit_policy.MicroExitPolicy exit_policy: **\*** relay's exit policy
  :var stem.exit_policy.MicroExitPolicy exit_policy_v6: **\*** exit policy for IPv6
  :var hash identifiers: mapping of key types (like rsa1024 or ed25519) to
    their base64 encoded identity, this is only used for collision prevention
    (:trac:`11743`)
  :var dict protocols: mapping of protocols to their supported versions

  :var str identifier: base64 encoded identity digest (**deprecated**, use
    identifiers instead)
  :var str identifier_type: identity digest key type (**deprecated**, use
    identifiers instead)

  **\*** attribute is required when we're parsed with validation

  .. versionchanged:: 1.1.0
     Added the identifier and identifier_type attributes.

  .. versionchanged:: 1.5.0
     Added the identifiers attribute, and deprecated identifier and
     identifier_type since the field can now appear multiple times.

  .. versionchanged:: 1.6.0
     Added the protocols attribute.

  .. versionchanged:: 1.8.0
     Replaced our **digest** attribute with a much more flexible **digest()**
     method. Unfortunately I cannot do this in a backward compatible way
     because of the name conflict. The old digest had multiple problems (for
     instance, being hex rather than base64 encoded), so hopefully no one was
     using it. Very sorry if this causes trouble for anyone.
  microdescriptorNzreject 1-65535)
r7   r9   Zor_addressesr   rA   r?   r0   r1   r2   rC   )r   r   ar   r   r   r   r+   r(   Fc                 C   s(   |r	t d| j t||dtdffS )NzSigning of %s not implementedr   r8   )NotImplementedError__name__r   r   )clsattrZexcludesignr(   r(   r)   content  s
   
zMicrodescriptor.contentc                    sV   t t| j|| d |r|ng | _t||}|r&| || | | d S || _d S )N)Z	lazy_load)superr#   __init___annotation_linesr   _parse_check_constraintsZ_entries)selfZraw_contentsr$   r&   r4   	__class__r(   r)   rM     s   

zMicrodescriptor.__init__c                 C   sP   |t jkrtjt|  |S |t jkr"tjt	|  |S t
d| )a  
    Digest of this microdescriptor. These are referenced by...

      * **Microdescriptor Consensus**

        * Referer: :class:`~stem.descriptor.router_status_entry.RouterStatusEntryMicroV3` **digest** attribute
        * Format: **SHA256/BASE64**

    .. versionadded:: 1.8.0

    :param stem.descriptor.DigestHash hash_type: digest hashing algorithm
    :param stem.descriptor.DigestEncoding encoding: digest encoding

    :returns: **hashlib.HASH** or **str** based on our encoding argument
    zEMicrodescriptor digests are only available in sha1 and sha256, not %s)r   ZSHA1r@   r3   Z_encode_digesthashlibZsha1Z	get_bytesSHA256Zsha256rF   )rQ   Z	hash_typeencodingr(   r(   r)   digest  s
   

zMicrodescriptor.digestc                 C   s>   i }| j D ]}d|v r|dd\}}|||< qd||< q|S )a  
    Provides content that appeared prior to the descriptor. If this comes from
    the cached-microdescs then this commonly contains content like...

    ::

      @last-listed 2013-02-24 00:18:30

    :returns: **dict** with the key/value pairs in our annotations
        r   N)rN   r-   )rQ   Zannotation_dictr'   keyvaluer(   r(   r)   get_annotations4  s   


zMicrodescriptor.get_annotationsc                 C   s   | j S )aM  
    Provides the lines of content that appeared prior to the descriptor. This
    is the same as the
    :func:`~stem.descriptor.microdescriptor.Microdescriptor.get_annotations`
    results, but with the unparsed lines and ordering retained.

    :returns: **list** with the lines of annotation that came before this descriptor
    )rN   )rQ   r(   r(   r)   get_annotation_linesL  s   
z$Microdescriptor.get_annotation_linesc                 C   sl   t D ]}||vrtd| qtD ]}||v r%t|| dkr%td| qdt| d kr4tddS )z
    Does a basic check that the entries conform to this descriptor type's
    constraints.

    :param dict entries: keyword => (value, pgp key) entries

    :raises: **ValueError** if an issue arises in validation
    z&Microdescriptor must have a '%s' entryr   z8The '%s' entry can only appear once in a microdescriptorr   r   z3Microdescriptor must start with a 'onion-key' entryN)REQUIRED_FIELDSr/   SINGLE_FIELDSr.   r   keys)rQ   r4   keywordr(   r(   r)   rP   X  s   
z"Microdescriptor._check_constraintsc                 C   s   |rdS dS )NZmicrodescriptorsrD   r(   )rQ   Z	is_pluralr(   r(   r)   _namem  s   zMicrodescriptor._name)Nr(   F)FNF) rG   
__module____qualname____doc__ZTYPE_ANNOTATION_NAME_parse_onion_key_line_parse_ntor_onion_key_liner   _parse_family_liner@   rA   rB   r   _parse_p6_liner6   _parse_pr_lineZ
ATTRIBUTESZPARSER_FOR_LINEclassmethodrK   rM   r   rU   r   ZBASE64rW   r   r[   r\   rP   ra   __classcell__r(   r(   rR   r)   r#      s@    *
r#   rb   )#re   rT   Zstem.exit_policyr@   Zstem.prereqZstem.descriptorr   r   r   r   r   r   r   r	   r
   r   r   Z#stem.descriptor.router_status_entryr   r   ZprereqZ_is_lru_cache_available	functoolsr   Zstem.util.lru_cacher]   r^   r*   r6   rf   rg   rh   ri   rj   r#   r(   r(   r(   r)   <module>   s&   ?4


9

