# Stem Docs

## Download Tor Descriptors

Tor relays provide a mirror for the tor relay descriptors it has cached. These are available from its ORPort using Tor’s wire protocol, and optionally with http as well from a DirPort.

    """
    Simple script to dowload a descriptor from Tor's ORPort or DirPort.
    """

    import collections
    import getopt
    import sys

    import stem
    import stem.descriptor.remote
    import stem.util.connection
    import stem.util.tor_tools

    # By default downloading moria1's server descriptor from itself.

    DEFAULT_ARGS = {
      'descriptor_type': 'server',
      'fingerprint': '9695DFC35FFEB861329B9F1AB04C46397020CE31',
      'download_from': stem.DirPort('128.31.0.34', 9131),
      'print_help': False,
    }

    VALID_TYPES = ('server', 'extrainfo', 'consensus')

    HELP_TEXT = """\
    Downloads a descriptor through Tor's ORPort or DirPort.

      -t, --type TYPE                 descriptor type to download, options are:
                                        %s
      -f, --fingerprint FP            relay to download the descriptor of
          --orport ADDRESS:PORT       ORPort to download from
          --dirport ADDRESS:PORT      DirPort to download from
      -h, --help                      presents this help
    """ % ', '.join(VALID_TYPES)

    def parse(argv):
      """
      Parses our arguments, providing a named tuple with their values.

      :param list argv: input arguments to be parsed

      :returns: a **named tuple** with our parsed arguments

      :raises: **ValueError** if we got an invalid argument
      """

      args = dict(DEFAULT_ARGS)

      try:
        recognized_args, unrecognized_args = getopt.getopt(argv, 't:f:h', ['type=', 'fingerprint=', 'orport=', 'dirport=', 'help'])

        if unrecognized_args:
          raise getopt.GetoptError("'%s' aren't recognized arguments" % "', '".join(unrecognized_args))
      except Exception as exc:
        raise ValueError('%s (for usage provide --help)' % exc)

      for opt, arg in recognized_args:
        if opt in ('-t', '--type'):
          if arg not in VALID_TYPES:
            raise ValueError("'%s' isn't a recognized decriptor type, options are: %s" % (arg, ', '.join(VALID_TYPES)))

          args['descriptor_type'] = arg
        elif opt in ('-f', '--fingerprint'):
          if not stem.util.tor_tools.is_valid_fingerprint(arg):
            raise ValueError("'%s' isn't a relay fingerprint" % arg)

          args['fingerprint'] = arg
        elif opt in ('--orport', '--dirport'):
          if ':' not in arg:
            raise ValueError("'%s' should be of the form 'address:port'" % arg)

          address, port = arg.rsplit(':', 1)

          if not stem.util.connection.is_valid_ipv4_address(address):
            raise ValueError("'%s' isn't a valid IPv4 address" % address)
          elif not stem.util.connection.is_valid_port(port):
            raise ValueError("'%s' isn't a valid port number" % port)

          endpoint_class = stem.ORPort if opt == '--orport' else stem.DirPort
          args['download_from'] = endpoint_class(address, port)
        elif opt in ('-h', '--help'):
          args['print_help'] = True

      # translates our args dict into a named tuple

      Args = collections.namedtuple('Args', args.keys())
      return Args(**args)

    def main():
      try:
        args = parse(sys.argv[1:])
      except ValueError as exc:
        print(exc)
        sys.exit(1)

      if args.print_help:
        print(HELP_TEXT)
        sys.exit()

      print('Downloading %s descriptor from %s:%s...\n' % (args.descriptor_type, args.download_from.address, args.download_from.port))
      desc = None

      if args.descriptor_type in ('server', 'extrainfo'):
        if args.descriptor_type == 'server':
          download_func = stem.descriptor.remote.get_server_descriptors
        else:
          download_func = stem.descriptor.remote.get_extrainfo_descriptors

        desc = download_func(
          fingerprints = [args.fingerprint],
          endpoints = [args.download_from],
        ).run()[0]
      elif args.descriptor_type == 'consensus':
        for consensus_desc in stem.descriptor.remote.get_consensus(endpoints = [args.download_from]):
          if consensus_desc.fingerprint == args.fingerprint:
            desc = consensus_desc
            break

        if not desc:
          print('Unable to find a descriptor for %s in the consensus' % args.fingerprint)
          sys.exit(1)
      else:
        print("'%s' is not a recognized descriptor type, options are: %s" % (args.descriptor_type, ', '.join(VALID_TYPES)))
        sys.exit(1)

      print(desc)

    if __name__ == '__main__':
      main()

    % python download_descriptor.py --type consensus --dirport 128.31.0.34:9131
    Downloading consensus descriptor from 128.31.0.34:9131...

    r moria1 lpXfw1/+uGEym58asExGOXAgzjE IpcU7dolas8+Q+oAzwgvZIWx7PA 2018-05-23 02:41:25 128.31.0.34 9101 9131
    s Authority Fast Running Stable V2Dir Valid
    v Tor 0.3.3.5-rc-dev
    pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
    w Bandwidth=20 Unmeasured=1
    p reject 1-65535