diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3147f0a --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +LOCAL_DOCTEST=/usr/local/bin/toxcore_run_doctest3.bash +DOCTEST=${LOCAL_DOCTEST} + +lint:: + sh .pylint.sh + +rsync:: + bash .rsync.sh + +doctest: + export PYTHONPATH=${PWD} + ${DOCTEST} stem_examples.txt diff --git a/docs/compare_flags.html b/docs/compare_flags.html new file mode 100644 index 0000000..0bf0e0b --- /dev/null +++ b/docs/compare_flags.html @@ -0,0 +1,167 @@ + + + + + + + + + + Comparing Directory Authority Flags — Stem 1.8.1-maint documentation + + + + + + + + + + + + + +

+ Stem Docs

+

Comparing Directory Authority Flags

+
+
+ +

+ +

+

+ +
+
+ + +
+

Comparing Directory Authority Flags

+../../_images/back.png +

Compares the votes of two directory authorities, in this case moria1 and +maatuska, with a special interest in the 'Running' flag.

+
import collections
+
+import stem.descriptor
+import stem.descriptor.remote
+import stem.directory
+
+# Query all authority votes asynchronously.
+
+downloader = stem.descriptor.remote.DescriptorDownloader(
+  document_handler = stem.descriptor.DocumentHandler.DOCUMENT,
+)
+
+# An ordered dictionary ensures queries are finished in the order they were
+# added.
+
+queries = collections.OrderedDict()
+
+for name, authority in stem.directory.Authority.from_cache().items():
+  if authority.v3ident is None:
+    continue  # authority doesn't vote if it lacks a v3ident
+
+  queries[name] = downloader.get_vote(authority)
+
+# Wait for the votes to finish being downloaded, this produces a dictionary of
+# authority nicknames to their vote.
+
+votes = dict((name, query.run()[0]) for (name, query) in queries.items())
+
+# Get a superset of all the fingerprints in all the votes.
+
+all_fingerprints = set()
+
+for vote in votes.values():
+  all_fingerprints.update(vote.routers.keys())
+
+# Finally, compare moria1's votes to maatuska's votes.
+
+for fingerprint in all_fingerprints:
+  moria1_vote = votes['moria1'].routers.get(fingerprint)
+  maatuska_vote = votes['maatuska'].routers.get(fingerprint)
+
+  if not moria1_vote and not maatuska_vote:
+    print("both moria1 and maatuska haven't voted about %s" % fingerprint)
+  elif not moria1_vote:
+    print("moria1 hasn't voted about %s" % fingerprint)
+  elif not maatuska_vote:
+    print("maatuska hasn't voted about %s" % fingerprint)
+  elif 'Running' in moria1_vote.flags and 'Running' not in maatuska_vote.flags:
+    print("moria1 has the Running flag but maatuska doesn't: %s" % fingerprint)
+  elif 'Running' in maatuska_vote.flags and 'Running' not in moria1_vote.flags:
+    print("maatuska has the Running flag but moria1 doesn't: %s" % fingerprint)
+
+
+
% python compare_flags.py
+maatuska has the Running flag but moria1 doesn't: 92FCB6748A40E6088E22FBAB943AB2DD743EA818
+maatuska has the Running flag but moria1 doesn't: 6871F682350BA931838C0EC1E4A23044DAE06A73
+maatuska has the Running flag but moria1 doesn't: E2BB13AA2F6960CD93ABE5257A825687F3973C62
+moria1 has the Running flag but maatuska doesn't: 546C54E2A89D88E0794D04AECBF1AC8AC9DA81DE
+moria1 has the Running flag but maatuska doesn't: DCAEC3D069DC39AAE43D13C8AF31B5645E05ED61
+...
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/download_descriptor.html b/docs/download_descriptor.html new file mode 100644 index 0000000..9f8bd38 --- /dev/null +++ b/docs/download_descriptor.html @@ -0,0 +1,250 @@ + + + + + + + + + + Download Tor Descriptors — Stem 1.8.1-maint documentation + + + + + + + + + + + + + +

+ Stem Docs

+

Download Tor Descriptors

+
+
+ +

+ +

+

+ +
+
+ + +
+

Download Tor Descriptors

+../../_images/back.png +

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
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/exit_used.html b/docs/exit_used.html new file mode 100644 index 0000000..5e41313 --- /dev/null +++ b/docs/exit_used.html @@ -0,0 +1,165 @@ + + + + + + + + + + Determine The Exit You're Using — Stem 1.8.1-maint documentation + + + + + + + + + + + + + +

+ Stem Docs

+

Determine The Exit You're Using

+
+
+ +

+ +

+

+ +
+
+ + +
+

Determine The Exit You're Using

+../../_images/back.png +

Lets say you're using Tor and one day you run into something odd. Maybe a +misconfigured relay, or maybe one that's being malicious. How can you figure +out what exit you're using?

+

Here's a simple script that prints information about the exits used to service +the requests going through Tor...

+
import functools
+
+from stem import StreamStatus
+from stem.control import EventType, Controller
+
+def main():
+  print("Tracking requests for tor exits. Press 'enter' to end.")
+  print("")
+
+  with Controller.from_port() as controller:
+    controller.authenticate()
+
+    stream_listener = functools.partial(stream_event, controller)
+    controller.add_event_listener(stream_listener, EventType.STREAM)
+
+    raw_input()  # wait for user to press enter
+
+
+def stream_event(controller, event):
+  if event.status == StreamStatus.SUCCEEDED and event.circ_id:
+    circ = controller.get_circuit(event.circ_id)
+
+    exit_fingerprint = circ.path[-1][0]
+    exit_relay = controller.get_network_status(exit_fingerprint)
+
+    print("Exit relay for our connection to %s" % (event.target))
+    print("  address: %s:%i" % (exit_relay.address, exit_relay.or_port))
+    print("  fingerprint: %s" % exit_relay.fingerprint)
+    print("  nickname: %s" % exit_relay.nickname)
+    print("  locale: %s" % controller.get_info("ip-to-country/%s" % exit_relay.address, 'unknown'))
+    print("")
+
+
+if __name__ == '__main__':
+  main()
+
+
+

Now if you make a request over Tor...

+
% curl --socks4a 127.0.0.1:9050 google.com
+<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
+<TITLE>301 Moved</TITLE></HEAD><BODY>
+<H1>301 Moved</H1>
+The document has moved
+<A HREF="http://www.google.com/">here</A>.
+</BODY></HTML>
+
+

... this script will tell you about the exit...

+
% python exit_used.py
+Tracking requests for tor exits. Press 'enter' to end.
+
+Exit relay for our connection to 64.15.112.44:80
+  address: 31.172.30.2:443
+  fingerprint: A59E1E7C7EAEE083D756EE1FF6EC31CA3D8651D7
+  nickname: chaoscomputerclub19
+  locale: unknown
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/list_circuits.html b/docs/list_circuits.html new file mode 100644 index 0000000..f69b0da --- /dev/null +++ b/docs/list_circuits.html @@ -0,0 +1,146 @@ + + + + + + + + + + List Circuits — Stem 1.8.1-maint documentation + + + + + + + + + + + + + +

+ Stem Docs

+

List Circuits

+
+
+ +

+ +

+

+ +
+
+ + +
+

List Circuits

+../../_images/back.png +

Tor creates new circuits and tears down old ones on your behalf, so how can you +get information about circuits Tor currently has available?

+
from stem import CircStatus
+from stem.control import Controller
+
+with Controller.from_port(port = 9051) as controller:
+  controller.authenticate()
+
+  for circ in sorted(controller.get_circuits()):
+    if circ.status != CircStatus.BUILT:
+      continue
+
+    print("")
+    print("Circuit %s (%s)" % (circ.id, circ.purpose))
+
+    for i, entry in enumerate(circ.path):
+      div = '+' if (i == len(circ.path) - 1) else '|'
+      fingerprint, nickname = entry
+
+      desc = controller.get_network_status(fingerprint, None)
+      address = desc.address if desc else 'unknown'
+
+      print(" %s- %s (%s, %s)" % (div, fingerprint, nickname, address))
+
+
+
% python list_circuits.py
+
+Circuit 4 (GENERAL)
+ |- B1FA7D51B8B6F0CB585D944F450E7C06EDE7E44C (ByTORAndTheSnowDog, 173.209.180.61)
+ |- 0DD9935C5E939CFA1E07B8DDA6D91C1A2A9D9338 (afo02, 87.238.194.176)
+ +- DB3B1CFBD3E4D97B84B548ADD5B9A31451EEC4CC (edwardsnowden3, 109.163.234.10)
+
+Circuit 6 (GENERAL)
+ |- B1FA7D51B8B6F0CB585D944F450E7C06EDE7E44C (ByTORAndTheSnowDog, 173.209.180.61)
+ |- EC01CB4766BADC1611678555CE793F2A7EB2D723 (sprockets, 46.165.197.96)
+ +- 9EA317EECA56BDF30CAEB208A253FB456EDAB1A0 (bolobolo1, 96.47.226.20)
+
+Circuit 10 (GENERAL)
+ |- B1FA7D51B8B6F0CB585D944F450E7C06EDE7E44C (ByTORAndTheSnowDog, 173.209.180.61)
+ |- 00C2C2A16AEDB51D5E5FB7D6168FC66B343D822F (ph3x, 86.59.119.83)
+ +- 65242C91BFF30F165DA4D132C81A9EBA94B71D62 (torexit16, 176.67.169.171)
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/outdated_relays.html b/docs/outdated_relays.html new file mode 100644 index 0000000..1031613 --- /dev/null +++ b/docs/outdated_relays.html @@ -0,0 +1,139 @@ + + + + + + + + + + List Outdated Relays — Stem 1.8.1-maint documentation + + + + + + + + + + + + + +

+ Stem Docs

+

List Outdated Relays

+
+
+ +

+ +

+

+ +
+
+ + +
+

List Outdated Relays

+../../_images/back.png +

Time marches on. Tor makes new releases, and at some point needs to drop +support for old ones. Below is the script we used on ticket 9476 to reach out +to relay operators that needed to upgrade.

+
from stem.descriptor.remote import DescriptorDownloader
+from stem.version import Version
+
+downloader = DescriptorDownloader()
+count, with_contact = 0, 0
+
+print("Checking for outdated relays...")
+print("")
+
+for desc in downloader.get_server_descriptors():
+  if desc.tor_version < Version('0.2.3.0'):
+    count += 1
+
+    if desc.contact:
+      print('  %-15s %s' % (desc.tor_version, desc.contact.decode("utf-8", "replace")))
+      with_contact += 1
+
+print("")
+print("%i outdated relays found, %i had contact information" % (count, with_contact))
+
+
+
% python outdated_relays.py
+Checking for outdated relays...
+
+  0.2.2.39        Random Person admin@gtr-10.de
+  0.2.2.36        dobrovich_psckaal at vietrievus dot ok
+  0.2.2.39        anonymous6 anonymous@mailinator.com
+  0.2.2.39        anonymous12 anonymous@mailinator.com
+  ...
+
+316 outdated relays found, 120 had contact information
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/over_the_river.html b/docs/over_the_river.html new file mode 100644 index 0000000..7650fd9 --- /dev/null +++ b/docs/over_the_river.html @@ -0,0 +1,362 @@ + + + + + + + + + + Over the River and Through the Wood — Stem 1.8.1-maint documentation + + + + + + + + + + + + + +

+ Stem Docs

+

Over the River and Through the Wood

+
+
+ +

+ +

+

+ +
+
+ + +
+

Over the River and Through the Wood

+

Hidden services +give you a way of providing a service without exposing your address. These +services are only accessible through Tor or Tor2web, +and useful for a surprising number of things...

+ +

Tor2web provides a quick and easy way of seeing if +your hidden service is working. To use it simply replace the .onion of +your address with .tor2web.org...

+../_images/duck_duck_go_hidden_service.png +
+

Running a hidden service

+

Hidden services can be configured through your torrc, +but Stem also provides some methods to easily work with them...

+
+
    +
  • create_hidden_service()
  • +
  • remove_hidden_service()
  • +
  • get_hidden_service_conf()
  • +
  • set_hidden_service_conf()
  • +
+
+

The main threat to your anonymity when running a hidden service is the service +itself. Debug information for instance might leak your real address, +undermining what Tor provides. This includes the following example, do not +rely on it not to leak.

+

But with that out of the way lets take a look at a simple Flask example based on one by Jordan Wright...

+
import os
+import shutil
+
+from stem.control import Controller
+from flask import Flask
+
+app = Flask(__name__)
+
+
+@app.route('/')
+def index():
+  return "<h1>Hi Grandma!</h1>"
+
+
+print(' * Connecting to tor')
+
+with Controller.from_port() as controller:
+  controller.authenticate()
+
+  # All hidden services have a directory on disk. Lets put ours in tor's data
+  # directory.
+
+  hidden_service_dir = os.path.join(controller.get_conf('DataDirectory', '/tmp'), 'hello_world')
+
+  # Create a hidden service where visitors of port 80 get redirected to local
+  # port 5000 (this is where Flask runs by default).
+
+  print(" * Creating our hidden service in %s" % hidden_service_dir)
+  result = controller.create_hidden_service(hidden_service_dir, 80, target_port = 5000)
+
+  # The hostname is only available when we can read the hidden service
+  # directory. This requires us to be running with the same user as tor.
+
+  if result.hostname:
+    print(" * Our service is available at %s, press ctrl+c to quit" % result.hostname)
+  else:
+    print(" * Unable to determine our service's hostname, probably due to being unable to read the hidden service directory")
+
+  try:
+    app.run()
+  finally:
+    # Shut down the hidden service and clean it off disk. Note that you *don't*
+    # want to delete the hidden service directory if you'd like to have this
+    # same *.onion address in the future.
+
+    print(" * Shutting down our hidden service")
+    controller.remove_hidden_service(hidden_service_dir)
+    shutil.rmtree(hidden_service_dir)
+
+
+

Now if we run this...

+
% python example.py
+ * Connecting to tor
+ * Creating our hidden service in /home/atagar/.tor/hello_world
+ * Our service is available at uxiuaxejc3sxrb6i.onion, press ctrl+c to quit
+ * Running on http://127.0.0.1:5000/
+127.0.0.1 - - [15/Dec/2014 13:05:43] "GET / HTTP/1.1" 200 -
+ * Shutting down our hidden service
+
+

... we'll have a service we can visit via the Tor Browser Bundle...

+../_images/hidden_service.png +
+
+

Hidden service authentication

+

Hidden services you create can restrict their access, requiring in essence a +password...

+
>>> from stem.control import Controller
+>>> controller = Controller.from_port()
+>>> controller.authenticate()
+>>> response = controller.create_ephemeral_hidden_service({80: 8080}, await_publication=True, basic_auth={'bob': None, 'alice': None})
+>>> response.service_id, response.client_auth
+('l3lnorirzn7hrjnw', {'alice': 'I6AMKiay+UkM5MfrvdnF2A', 'bob': 'VLsbrSGyrb5JYEvZmQ3tMg'})
+
+
+

To access this service users simply provide this credential to tor via their +torrc or SETCONF prior to visiting it...

+
>>> controller.set_conf('HidServAuth', 'l3lnorirzn7hrjnw.onion I6AMKiay+UkM5MfrvdnF2A')
+
+
+
+
+

Ephemeral hidden services

+

In the above example you may have noticed the note that said...

+
# The hostname is only available when we can read the hidden service
+# directory. This requires us to be running with the same user as tor.
+
+
+

This has been a limitation of hidden services for years. However, as of version +0.2.7.1 Tor offers another style for making services called ephemeral hidden +services.

+

Ephemeral services can only be created through the controller, and only exist +as long as your controller is attached unless you provide the detached +flag. Controllers can only see their own ephemeral services, and ephemeral +services that are detached. In other words, attached ephemeral services can +only be managed by their own controller.

+

Stem provides three methods to work with ephemeral hidden services...

+
+
    +
  • list_ephemeral_hidden_services()
  • +
  • create_ephemeral_hidden_service()
  • +
  • remove_ephemeral_hidden_service()
  • +
+
+

For example, with a ephemeral service our earlier example becomes as simple as...

+
from stem.control import Controller
+from flask import Flask
+
+app = Flask(__name__)
+
+
+@app.route('/')
+def index():
+  return "<h1>Hi Grandma!</h1>"
+
+
+print(' * Connecting to tor')
+
+with Controller.from_port() as controller:
+  controller.authenticate()
+
+  # Create a hidden service where visitors of port 80 get redirected to local
+  # port 5000 (this is where Flask runs by default).
+
+  response = controller.create_ephemeral_hidden_service({80: 5000}, await_publication = True)
+  print(" * Our service is available at %s.onion, press ctrl+c to quit" % response.service_id)
+
+  try:
+    app.run()
+  finally:
+    print(" * Shutting down our hidden service")
+
+
+

Ephemeral hidden services do not touch disk, and as such are easier to work +with but require you to persist your service's private key yourself if you want +to reuse a '.onion' address...

+
import os
+from stem.control import Controller
+
+key_path = os.path.expanduser('~/my_service_key')
+
+with Controller.from_port() as controller:
+  controller.authenticate()
+
+  if not os.path.exists(key_path):
+    service = controller.create_ephemeral_hidden_service({80: 5000}, await_publication = True)
+    print("Started a new hidden service with the address of %s.onion" % service.service_id)
+
+    with open(key_path, 'w') as key_file:
+      key_file.write('%s:%s' % (service.private_key_type, service.private_key))
+  else:
+    with open(key_path) as key_file:
+      key_type, key_content = key_file.read().split(':', 1)
+
+    service = controller.create_ephemeral_hidden_service({80: 5000}, key_type = key_type, key_content = key_content, await_publication = True)
+    print("Resumed %s.onion" % service.service_id)
+
+  raw_input('press any key to shut the service down...')
+  controller.remove_ephemeral_hidden_service(service.service_id)
+
+
+
+
+

Hidden service descriptors

+

Like relays, hidden services publish documents about themselves called hidden +service descriptors. These contain low level details for establishing +connections. Hidden service descriptors are available from the tor process via +its get_hidden_service_descriptor() method...

+
from stem.control import Controller
+
+with Controller.from_port(port = 9051) as controller:
+  controller.authenticate()
+
+  # descriptor of duck-duck-go's hidden service (http://3g2upl4pq6kufc4m.onion)
+
+  print(controller.get_hidden_service_descriptor('3g2upl4pq6kufc4m'))
+
+
+
% python print_duck_duck_go_descriptor.py
+
+rendezvous-service-descriptor e5dkwgp6vt7axoozixrbgjymyof7ab6u
+version 2
+permanent-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE
+aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg
+I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE=
+-----END RSA PUBLIC KEY-----
+secret-id-part bmsctib2pzirgo7cltlxdm5fxqcitt5e
+publication-time 2015-05-11 20:00:00
+protocol-versions 2,3
+introduction-points
+-----BEGIN MESSAGE-----
+aW50cm9kdWN0aW9uLXBvaW50IHZzcm4ycGNtdzNvZ21mNGo3dGpxeHptdml1Y2Rr
+NGtpCmlwLWFkZHJlc3MgMTc2LjkuNTkuMTcxCm9uaW9uLXBvcnQgOTAwMQpvbmlv
+... etc...
+
+

A hidden service's introduction points are a base64 encoded field that's +possibly encrypted. These can be decoded (and decrypted if necessary) with the +descriptor's +introduction_points() +method.

+
from stem.control import Controller
+
+with Controller.from_port(port = 9051) as controller:
+  controller.authenticate()
+  desc = controller.get_hidden_service_descriptor('3g2upl4pq6kufc4m')
+
+  print("DuckDuckGo's introduction points are...\n")
+
+  for introduction_point in desc.introduction_points():
+    print('  %s:%s => %s' % (introduction_point.address, introduction_point.port, introduction_point.identifier))
+
+
+
% python print_duck_duck_go_introduction_points.py
+
+DuckDuckGo's introduction points are...
+
+  176.9.59.171:9001 => vsrn2pcmw3ogmf4j7tjqxzmviucdk4ki
+  104.131.106.181:9001 => gcl2kpqx5qnkpgxjf6x7ulqncoqj7ghh
+  188.166.58.218:443 => jeymnbhs2d6l2oib7jjvweavg45m6gju
+
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/docs/relay_connections.html b/docs/relay_connections.html new file mode 100644 index 0000000..27a33c0 --- /dev/null +++ b/docs/relay_connections.html @@ -0,0 +1,276 @@ + + + + + + + + + + Connection Summary — Stem 1.8.1-maint documentation + + + + + + + + + + + + + +

+ Stem Docs

+

Connection Summary

+
+
+ +

+ +

+

+ +
+
+ + +
+

Connection Summary

+../../_images/back.png +

The following provides a summary of your relay's inbound and outbound +connections. Couple important notes...

+
+
    +
  • To use this you must set DisableDebuggerAttachment 0 in your torrc. +Otherwise connection information will be unavailable.

    +
  • +
  • Be careful about the data you look at. Inspection of client and exit +traffic especially is wiretapping and not only unethical but likely +illegal.

    +

    That said, a general overview like this should be fine.

    +
  • +
+
+
import argparse
+import collections
+import time
+
+import stem.connection
+import stem.util.system
+import stem.util.str_tools
+
+from stem.control import Listener
+from stem.util.connection import get_connections, port_usage, is_valid_ipv4_address
+
+HEADER_LINE = " {version}   uptime: {uptime}   flags: {flags}\n"
+
+DIV = '+%s+%s+%s+' % ('-' * 30, '-' * 6, '-' * 6)
+COLUMN = '| %-28s | %4s | %4s |'
+
+INBOUND_ORPORT = 'Inbound to our ORPort'
+INBOUND_DIRPORT = 'Inbound to our DirPort'
+INBOUND_CONTROLPORT = 'Inbound to our ControlPort'
+
+OUTBOUND_ORPORT = 'Outbound to a relay'
+OUTBOUND_EXIT = 'Outbound exit traffic'
+OUTBOUND_UNKNOWN = 'Outbound uncategorized'
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument("--ctrlport", help="default: 9051 or 9151")
+  parser.add_argument("--resolver", help="default: autodetected")
+  args = parser.parse_args()
+
+  control_port = int(args.ctrlport) if args.ctrlport else 'default'
+  controller = stem.connection.connect(control_port = ('127.0.0.1', control_port))
+
+  if not controller:
+    return
+
+  desc = controller.get_network_status(default = None)
+  pid = controller.get_pid()
+
+  print(HEADER_LINE.format(
+    version = str(controller.get_version()).split()[0],
+    uptime = stem.util.str_tools.short_time_label(time.time() - stem.util.system.start_time(pid)),
+    flags = ', '.join(desc.flags if desc else ['none']),
+  ))
+
+  policy = controller.get_exit_policy()
+  relays = {}  # address => [orports...]
+
+  for desc in controller.get_network_statuses():
+    relays.setdefault(desc.address, []).append(desc.or_port)
+
+  # categorize our connections
+
+  categories = collections.OrderedDict((
+    (INBOUND_ORPORT, []),
+    (INBOUND_DIRPORT, []),
+    (INBOUND_CONTROLPORT, []),
+    (OUTBOUND_ORPORT, []),
+    (OUTBOUND_EXIT, []),
+    (OUTBOUND_UNKNOWN, []),
+  ))
+
+  exit_connections = {}  # port => [connections]
+
+  for conn in get_connections(resolver = args.resolver, process_pid = pid):
+    if conn.protocol == 'udp':
+        continue
+
+    if conn.local_port in controller.get_ports(Listener.OR, []):
+      categories[INBOUND_ORPORT].append(conn)
+    elif conn.local_port in controller.get_ports(Listener.DIR, []):
+      categories[INBOUND_DIRPORT].append(conn)
+    elif conn.local_port in controller.get_ports(Listener.CONTROL, []):
+      categories[INBOUND_CONTROLPORT].append(conn)
+    elif conn.remote_port in relays.get(conn.remote_address, []):
+      categories[OUTBOUND_ORPORT].append(conn)
+    elif policy.can_exit_to(conn.remote_address, conn.remote_port):
+      categories[OUTBOUND_EXIT].append(conn)
+      exit_connections.setdefault(conn.remote_port, []).append(conn)
+    else:
+      categories[OUTBOUND_UNKNOWN].append(conn)
+
+  print(DIV)
+  print(COLUMN % ('Type', 'IPv4', 'IPv6'))
+  print(DIV)
+
+  total_ipv4, total_ipv6 = 0, 0
+
+  for label, connections in categories.items():
+    if len(connections) == 0:
+      continue
+
+    ipv4_count = len([conn for conn in connections if is_valid_ipv4_address(conn.remote_address)])
+    ipv6_count = len(connections) - ipv4_count
+
+    total_ipv4, total_ipv6 = total_ipv4 + ipv4_count, total_ipv6 + ipv6_count
+    print(COLUMN % (label, ipv4_count, ipv6_count))
+
+  print(DIV)
+  print(COLUMN % ('Total', total_ipv4, total_ipv6))
+  print(DIV)
+  print('')
+
+  if exit_connections:
+    print(DIV)
+    print(COLUMN % ('Exit Port', 'IPv4', 'IPv6'))
+    print(DIV)
+
+    total_ipv4, total_ipv6 = 0, 0
+
+    for port in sorted(exit_connections):
+      connections = exit_connections[port]
+      ipv4_count = len([conn for conn in connections if is_valid_ipv4_address(conn.remote_address)])
+      ipv6_count = len(connections) - ipv4_count
+      total_ipv4, total_ipv6 = total_ipv4 + ipv4_count, total_ipv6 + ipv6_count
+
+      usage = port_usage(port)
+      label = '%s (%s)' % (port, usage) if usage else port
+
+      print(COLUMN % (label, ipv4_count, ipv6_count))
+
+    print(DIV)
+    print(COLUMN % ('Total', total_ipv4, total_ipv6))
+    print(DIV)
+    print('')
+
+
+if __name__ == '__main__':
+  main()
+
+
+
% relay_connections.py --ctrlport 29051
+
+ 0.3.2.0-alpha-dev   uptime: 01:20:44   flags: none
+
++------------------------------+------+------+
+| Type                         | IPv4 | IPv6 |
++------------------------------+------+------+
+| Inbound to our ORPort        | 2400 |    3 |
+| Inbound to our DirPort       |   12 |    0 |
+| Inbound to our ControlPort   |    2 |    0 |
+| Outbound to a relay          |  324 |    0 |
+| Outbound exit traffic        |    3 |    0 |
++------------------------------+------+------+
+| Total                        | 2741 |    3 |
++------------------------------+------+------+
+
++------------------------------+------+------+
+| Exit Port                    | IPv4 | IPv6 |
++------------------------------+------+------+
+| 443 (HTTPS)                  |    1 |    0 |
+| 8443 (PCsync HTTPS)          |    1 |    0 |
+| 54682                        |    1 |    0 |
++------------------------------+------+------+
+| Total                        |    3 |    0 |
++------------------------------+------+------+
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3b7b8f6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[project] +name = "stem_examples" +version = "2023.12" +description = "examples of using stem" +authors = [{ name = "emdee", email = "Ingvar@gitgub.com" } ] +requires-python = ">=3.6" +dependencies = [ + 'stem', +] +keywords = ["stem", "python3", "tor"] +classifiers = [ + "License :: OSI Approved", + "Operating System :: POSIX :: BSD :: FreeBSD", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + ] +dynamic = ["version", "readme", ] # cannot be dynamic ['license'] +scripts = { exclude_badExits = "stem_examples.exclude_badExits:iMain" } + +#[project.license] +#file = "LICENSE.md" + +[project.urls] +repository = "https://git.plastiras.org/emdee/stem_examples" + +[build-system] +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +version = {attr = "stem_examples.__version__"} +readme = {file = ["README.md", "exclude_badExits.md"]} + +[tool.setuptools] +packages = ["stem_examples"] + +#[tool.setuptools.packages.find] +#where = "src" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..c077948 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,67 @@ +[metadata] +classifiers = + License :: OSI Approved + License :: OSI Approved :: BSD 1-clause + Intended Audience :: Web Developers + Operating System :: Microsoft :: Windows + Operating System :: POSIX :: BSD :: FreeBSD + Operating System :: POSIX :: Linux + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: Implementation :: CPython + Framework :: AsyncIO + +[options] +zip_safe = false +python_requires = ~=3.6 +include_package_data = false +install_requires = + qasync + cryptography + rsa + stem + ruamel.yaml +package_dir= + =src +packages=find: + +[options.packages.find] +where=src + +[options.entry_points] +console_scripts = + phantompy = phantompy.__main__:iMain + exclude_badExits = exclude_badExits:iMain + +[easy_install] +zip_ok = false + +[flake8] +jobs = 1 +max-line-length = 88 +ignore = + E111 + E114 + E128 + E225 + E261 + E302 + E305 + E402 + E501 + E502 + E541 + E701 + E702 + E704 + E722 + E741 + F508 + F541 + W503 + W601 diff --git a/src/stem_examples/__init__.py b/src/stem_examples/__init__.py old mode 100644 new mode 100755 diff --git a/src/stem_examples/check_digests.py b/src/stem_examples/check_digests.py index 1898b97..f8f5ce0 100755 --- a/src/stem_examples/check_digests.py +++ b/src/stem_examples/check_digests.py @@ -31,7 +31,7 @@ def download_descriptors(fingerprint): ) if __name__ == '__main__': - set_socks_proxy() + set_socks_proxy() fingerprint = input("What relay fingerprint would you like to validate?\n") print('') # blank line diff --git a/src/stem_examples/compare_flags.py b/src/stem_examples/compare_flags.py old mode 100644 new mode 100755 index 57662e0..f65c00a --- a/src/stem_examples/compare_flags.py +++ b/src/stem_examples/compare_flags.py @@ -1,4 +1,5 @@ # -*-mode: python; py-indent-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- +# import sys import collections @@ -14,36 +15,36 @@ def iMain(): downloader = stem.descriptor.remote.DescriptorDownloader( document_handler = stem.descriptor.DocumentHandler.DOCUMENT, ) - + # An ordered dictionary ensures queries are finished in the order they were # added. - + queries = collections.OrderedDict() - + for name, authority in stem.directory.Authority.from_cache().items(): if authority.v3ident is None: continue # authority doesn't vote if it lacks a v3ident - + queries[name] = downloader.get_vote(authority) - + # Wait for the votes to finish being downloaded, this produces a dictionary of # authority nicknames to their vote. - + votes = dict((name, query.run()[0]) for (name, query) in queries.items()) - + # Get a superset of all the fingerprints in all the votes. - + all_fingerprints = set() - + for vote in votes.values(): all_fingerprints.update(vote.routers.keys()) - + # Finally, compare moria1's votes to maatuska's votes. - + for fingerprint in all_fingerprints: moria1_vote = votes['moria1'].routers.get(fingerprint) maatuska_vote = votes['maatuska'].routers.get(fingerprint) - + if not moria1_vote and not maatuska_vote: print("both moria1 and maatuska haven't voted about %s" % fingerprint) elif not moria1_vote: @@ -54,7 +55,7 @@ def iMain(): print("moria1 has the Running flag but maatuska doesn't: %s" % fingerprint) elif 'Running' in maatuska_vote.flags and 'Running' not in moria1_vote.flags: print("maatuska has the Running flag but moria1 doesn't: %s" % fingerprint) - + return 0 if __name__ == '__main__': diff --git a/src/stem_examples/exit_used.py b/src/stem_examples/exit_used.py index 49ef903..41b24b8 100755 --- a/src/stem_examples/exit_used.py +++ b/src/stem_examples/exit_used.py @@ -1,5 +1,12 @@ # -*-mode: python; py-indent-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- # https://stem.torproject.org/tutorials/examples/exit_used.html +__doc__ = """Determine The Exit You're Using + +Lets say you're using Tor and one day you run into something odd. Maybe a +misconfigured relay, or maybe one that's being malicious. +How can you figure out what exit you're using? + +""" import functools import sys @@ -25,7 +32,7 @@ def stream_event(controller, event): from tor_controller import get_controller -def main(): +def iMain(): print("Please wait for requests for tor exits. Press 'enter' to end.") print("") if os.path.exists('/run/tor/control'): @@ -38,9 +45,9 @@ def main(): stream_listener = functools.partial(stream_event, controller) controller.add_event_listener(stream_listener, EventType.STREAM) + +if __name__ == '__main__': + iMain() print('Press Enter') input() # wait for user to press enter -if __name__ == '__main__': - main() - diff --git a/src/stem_examples/introduction_points.py b/src/stem_examples/introduction_points.py old mode 100644 new mode 100755 index e39488d..4a2c3a6 --- a/src/stem_examples/introduction_points.py +++ b/src/stem_examples/introduction_points.py @@ -1,13 +1,36 @@ +#!/usr/local/bin/python3.sh # -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*- -# http://vt5hknv6sblkgf22.onion/tutorials/over_the_river.html +# https://stem.torproject.org/tutorials/over_the_river.html + +__doc__ = """ +Hidden services give you a way of providing a service without exposing your address. These services are only accessible through Tor or Tor2web, and useful for a surprising number of things... + + Hosting an anonymized site. This is usually the first thing that comes to mind, and something we'll demonstrate in a sec. + Providing an endpoint Tor users can reach without exiting the Tor network. This eliminates the risk of an unreliable or malicious exit getting in the way. Great examples of this are Facebook (facebookcorewwwi.onion) and DuckDuckGo (3g2upl4pq6kufc4m.onion). + Personal services. For instance you can host your home SSH server as a hidden service to prevent eavesdroppers from knowing where you live while traveling abroad. + +Tor2web provides a quick and easy way of seeing if your hidden service is working. To use it simply replace the .onion of your address with .tor2web.org... + +This script tests if you can reach a hidden service, passed as an onion address +as an argument. If no argument is given, 3 common onion sites are tested: +Facebook, DuckDuckGo, and . +""" import sys import os import getpass + from stem.control import Controller from tor_controller import get_controller +lKNOWN_ONIONS = [ + 'facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd', # facebook + 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad', # ddg + 'zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad', # hks +] + + def iMain(lArgs=None): if lArgs is None: lArgs = sys.argv[1:] @@ -16,10 +39,10 @@ def iMain(lArgs=None): controller = get_controller(unix='/run/tor/control') else: controller = get_controller(port=9051) - + password = os.environ.get('TOR_CONTROLLER_PASSWORD') controller.authenticate(password) - + for elt in lArgs: desc = controller.get_hidden_service_descriptor(elt, await_result=True, timeout=None) print(f"{desc} get_hidden_service_descriptor\n") @@ -28,28 +51,22 @@ def iMain(lArgs=None): print(f"{elt} NO introduction points\n") continue print(f"{elt} introduction points are...\n") - + for introduction_point in l: print(' %s:%s => %s' % (introduction_point.address, introduction_point.port, introduction_point.identifier)) - + except Exception as e: print(e) finally: del controller return 0 -lKNOWN_ONIONS = [ - 'facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd', # facebook - 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad', # ddg - 'zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad', # hks -] - if __name__ == '__main__': if len(sys.argv) <= 1: lArgs = lKNOWN_ONIONS else: - lArgs = sys.argv[1:] + lArgs = sys.argv[1:] sys.exit(iMain()) diff --git a/src/stem_examples/list_circuits.py b/src/stem_examples/list_circuits.py index 2a0a3ae..6e74f23 100755 --- a/src/stem_examples/list_circuits.py +++ b/src/stem_examples/list_circuits.py @@ -13,31 +13,31 @@ def iMain(): controller = get_controller(unix='/run/tor/control') else: controller = get_controller(port=9051) - + password = os.environ.get('TOR_CONTROLLER_PASSWORD') try: controller.authenticate(password) - + for circ in sorted(controller.get_circuits()): if circ.status != CircStatus.BUILT: continue - + print("") print("Circuit %s (%s)" % (circ.id, circ.purpose)) - + for i, entry in enumerate(circ.path): div = '+' if (i == len(circ.path) - 1) else '|' fingerprint, nickname = entry - + desc = controller.get_network_status(fingerprint, None) address = desc.address if desc else 'unknown' - + print(" %s- %s (%s, %s)" % (div, fingerprint, nickname, address)) - + except Exception as e: print(e) finally: - del controller + del controller return 0 if __name__ == '__main__': diff --git a/src/stem_examples/mappaddress.py b/src/stem_examples/mappaddress.py old mode 100644 new mode 100755 index dd09050..a71ea85 --- a/src/stem_examples/mappaddress.py +++ b/src/stem_examples/mappaddress.py @@ -21,7 +21,7 @@ def sMapaddressResolv(target, iPort=9051): controller = get_controller(unix='/run/tor/control') else: controller = get_controller(port=9051) - + password = os.environ.get('TOR_CONTROLLER_PASSWORD') controller.authenticate(password) diff --git a/src/stem_examples/outdated_relays.py b/src/stem_examples/outdated_relays.py old mode 100644 new mode 100755 index c86c217..97b7d83 --- a/src/stem_examples/outdated_relays.py +++ b/src/stem_examples/outdated_relays.py @@ -10,15 +10,15 @@ def iMain(): elts = downloader.get_server_descriptors() print(f"Checking for outdated relays len server_descriptors={len(list(elts))}...") print("") - + for desc in elts: if desc.tor_version < Version('0.2.3.0'): count += 1 - + if desc.contact: print(' %-15s %s' % (desc.tor_version, desc.contact.decode("utf-8", "replace"))) with_contact += 1 - + print("") print("%i outdated relays found, %i had contact information" % (count, with_contact)) # http://vt5hknv6sblkgf22.onion/tutorials/examples/outdated_relays.htmlhttp://vt5hknv6sblkgf22.onion/tutorials/examples/outdated_relays.html diff --git a/src/stem_examples/relay_connections.py b/src/stem_examples/relay_connections.py index 56c180d..50f8635 100755 --- a/src/stem_examples/relay_connections.py +++ b/src/stem_examples/relay_connections.py @@ -1,6 +1,15 @@ # -*-mode: python; py-indent-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- -# http://vt5hknv6sblkgf22.onion/tutorials/examples/relay_connections.html +# https://stem.torproject.org/tutorials/examples/relay_connections.html + +__doc__ = """Connection Summary + +The following provides a summary of your relay's inbound and outbound connections. + +To use this you must set DisableDebuggerAttachment 0 in your +torrc. Otherwise connection information will be unavailable. +""" + import argparse import collections import os @@ -37,7 +46,7 @@ OUTBOUND_UNKNOWN = 'Outbound uncategorized' def iMain(lArgs=None): if lArgs is None: lArgs = sys.argv[1:] - + parser = argparse.ArgumentParser() parser.add_argument("--ctrlport", default=9051, type=int, help="default: 9051") parser.add_argument("--resolver", help="default: autodetected") @@ -49,7 +58,7 @@ def iMain(lArgs=None): else: controller = get_controller(port=control_port) # controller = stem.connection.connect(control_port = ('127.0.0.1', control_port)) - + password = os.environ.get('TOR_CONTROLLER_PASSWORD') controller.authenticate(password) diff --git a/src/stem_examples/tor_bootstrap_check.py b/src/stem_examples/tor_bootstrap_check.py index 92b9337..2be1210 100755 --- a/src/stem_examples/tor_bootstrap_check.py +++ b/src/stem_examples/tor_bootstrap_check.py @@ -1,4 +1,10 @@ -#!/usr/bin/python3 -u +#!/usr/local/bin/python3.sh -u +# -*-mode: python; py-indent-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + +__doc__ = """ +A script by adrelanos@riseup.net to check what percentage of boostrapping +tor is at. +""" ## Copyright (C) 2012 - 2020 ENCRYPTED SUPPORT LP ## See the file COPYING for copying conditions. @@ -43,4 +49,4 @@ def iMain(lArgs=None): if __name__ == '__main__': sys.exit( iMain()) - + diff --git a/src/stem_examples/tor_controller.py b/src/stem_examples/tor_controller.py old mode 100644 new mode 100755 index a36f127..560f021 --- a/src/stem_examples/tor_controller.py +++ b/src/stem_examples/tor_controller.py @@ -1,4 +1,5 @@ # -*-mode: python; py-indent-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- + import os import sys import getpass @@ -29,7 +30,7 @@ def get_controller(password=None, address='127.0.0.1', port=9051, unix='/run/tor controller = Controller.from_port(address=address, port=port) if password is None: - print("DBUG: trying TOR_CONTROLLER_PASSWORD") +# print("DBUG: trying TOR_CONTROLLER_PASSWORD") password = os.environ.get('TOR_CONTROLLER_PASSWORD') else: # print(f"DBUG: using a password {len(password)}") @@ -50,7 +51,7 @@ def get_controller(password=None, address='127.0.0.1', port=9051, unix='/run/tor controller.authenticate(password) except Exception as e: print(f"ERROR: {e}") - + return controller diff --git a/stem_examples.txt b/stem_examples.txt new file mode 100644 index 0000000..b8cfa05 --- /dev/null +++ b/stem_examples.txt @@ -0,0 +1,102 @@ +# -*-mode: doctest; tab-width: 0; py-indent-offset: 4; coding: utf-8-unix -*- + +== stem_examples tor testing == + +This is a Python doctest file that is executable documentation. + +Pass the controller password if needed as an environment variable: + + >>> import os + >>> assert os.environ['TOR_CONTROLLER_PASSWORD'] + +Add our code to the PYTHONPATH + >>> import sys + >>> sys.path.append(os.path.join(os.getcwd(), 'src', 'stem_examples')) + +We'll need the settings defined in {{{/usr/local/etc/testforge/testforge.yml}}} + + >>> print("yaml", file=sys.stderr) + >>> import yaml + >>> sFacts = open('/usr/local/etc/testforge/testforge.yml').read() + >>> assert sFacts + >>> dFacts = yaml.safe_load(sFacts) + +FixMe: use the settings for the ports and directories below. + + >>> import os + >>> os.environ['http_proxy'] = 'http://'+dFacts['HTTP_PROXYHOST']+':'+str(dFacts['HTTP_PROXYPORT']) + >>> os.environ['https_proxy'] = 'http://'+dFacts['HTTPS_PROXYHOST']+':'+str(dFacts['HTTPS_PROXYPORT']) + >>> os.environ['socks_proxy'] = 'socks5://'+dFacts['SOCKS_PROXYHOST']+':'+str(dFacts['SOCKS_PROXYPORT']) + +### compare_flags Comparing Directory Authority Flags +### introduction_points Introduction Points + + >>> print("introduction_points", file=sys.stderr) + >>> import introduction_points + +The introduction points are the ways of connecting to hidden services. +We test 3 known hidden services: Facebook, DuckDuckGo and . + + >>> lKNOWN_ONIONS = [ + ... 'facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd', # facebook + ... 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad', # ddg + ... 'zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad', # hks + ... ] + +We wil expect to get back the hidden service version, the descriptor-lifetime +and then the descriptor-signing-key-cert: + + >>> introduction_points.iMain(lKNOWN_ONIONS) #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + hs-descriptor 3 + descriptor-lifetime ... + + +### exit_used Determine The Exit You're Using + +Lets say you're using Tor and one day you run into something odd. Maybe a +misconfigured relay, or maybe one that's being malicious. +How can you figure out what exit you're using? + + >>> print("exit_used", file=sys.stderr) + >>> import exit_used + +## relay_connections Connection Summary + + >>> print("relay_connections", file=sys.stderr) + >>> import relay_connections + +The following provides a summary of your relay's inbound and outbound connections. +You must be root or tor to run this: +relay_connections.iMain(["--ctrlport", "9051"]) + +## outdated_relays + + >>> print("outdated_relays", file=sys.stderr) + >>> import outdated_relays + >>> outdated_relays.iMain() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + Checking for outdated relays ... + + +## tor_bootstrap_check + + >>> print("tor_bootstrap_check", file=sys.stderr) + >>> import tor_bootstrap_check + +A script by adrelanos@riseup.net to check what percentage of boostrapping +tor is at. This fails under doctest but not from the cmdline + +>> tor_bootstrap_check.iMain() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +NOTICE ... + + + control_port = stem.socket.ControlPort(address, port) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/stem/socket.py", line 503, in __init__ + self.connect() + File "/usr/local/lib/python3.11/site-packages/stem/socket.py", line 172, in connect + self._socket = self._make_socket() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/stem/socket.py", line 538, in _make_socket + raise stem.SocketError(exc) +stem.SocketError: Socket error: 0x01: General SOCKS server failure +