diff --git a/support_onions.py b/support_onions.py index a806b31..4be62e0 100644 --- a/support_onions.py +++ b/support_onions.py @@ -9,6 +9,16 @@ import socket import select import time +import getpass +if False: + import cepa as stem + from cepa.control import Controller + from cepa.connection import MissingPassword +else: + import stem + from stem.control import Controller + from stem.connection import MissingPassword + global LOG import logging import warnings @@ -17,6 +27,77 @@ LOG = logging.getLogger() bHAVE_TORR = shutil.which('tor-resolve') +# maybe we should check these each time but we +# got them by sorting bad relays in the wild +# we'll keep a copy here +yKNOWN_NODNS = """ +--- + - 0x0.is + - a9.wtf + - aklad5.com + - artikel5ev.de + - arvanode.net + - dodo.pm + - dra-family.github.io + - eraldonion.org + - erjan.net + - galtland.network + - ineapple.cx + - lonet.sh + - moneneis.de + - olonet.sh + - or-exit-2.aa78i2efsewr0neeknk.xyz + - or.wowplanet.de + - ormycloud.org + - plied-privacy.net + - redacted.org + - rification-for-nusenu.net + - rofl.cat + - rsv.ch + - sv.ch + - thingtohide.nl + - tikel10.org + - tor.wowplanet.de + - tor-exit-2.aa78i2efsewr0neeknk.xyz + - tor-exit-3.aa78i2efsewr0neeknk.xyz + - torix-relays.org + - tse.com + - tuxli.org + - w.digidow.eu + - w.cccs.de +""" + +oSTEM_CONTROLER = None +def oGetStemController(log_level=10, sock_or_pair='/run/tor/control'): + + global oSTEM_CONTROLER + if oSTEM_CONTROLER: return oSTEM_CONTROLER + from stem.util.log import Runlevel + Runlevel = log_level + + if os.path.exists(sock_or_pair): + LOG.info(f"controller from socket {sock_or_pair}") + controller = Controller.from_socket_file(path=sock_or_pair) + else: + if ':' in sock_or_pair: + port = sock_or_pair.split(':')[1] + else: + port = sock_or_pair + try: + port = int(port) + except: port = 9051 + LOG.info(f"controller from port {port}") + controller = Controller.from_port(port=port) + try: + controller.authenticate() + except (Exception, MissingPassword): + sys.stdout.flush() + p = getpass.unix_getpass(prompt='Controller Password: ', stream=sys.stderr) + controller.authenticate(p) + oSTEM_CONTROLER = controller + LOG.debug(f"{controller}") + return oSTEM_CONTROLER + def bAreWeConnected(): # FixMe: Linux only sFile = f"/proc/{os.getpid()}/net/route" @@ -28,13 +109,13 @@ def bAreWeConnected(): i += 1 return i > 0 -def sMapaddressResolv(target, iPort=9051): +def sMapaddressResolv(target, iPort=9051, log_level=10): if not stem: LOG.warn('please install the stem Python package') return '' try: - controller = oGetStemController(log_level=10) + controller = oGetStemController(log_level=log_level) map_dict = {"0.0.0.0": target} map_ret = controller.map_address(map_dict) @@ -44,30 +125,94 @@ def sMapaddressResolv(target, iPort=9051): LOG.exception(e) return '' -def lIntroductionPoints(target, iPort=9051): - if stem == False: return '' - from stem import StreamStatus - from stem.control import EventType, Controller - import getpass +def vwait_for_controller(controller, wait_boot=10): + if bAreWeConnected() is False: + raise SystemExit("we are not connected") + percent = i = 0 + # You can call this while boostrapping + while percent < 100 and i < wait_boot: + bootstrap_status = controller.get_info("status/bootstrap-phase") + progress_percent = re.match('.* PROGRESS=([0-9]+).*', bootstrap_status) + percent = int(progress_percent.group(1)) + LOG.info(f"Bootstrapping {percent}%") + time.sleep(5) + i += 5 + +def bin_to_hex(raw_id, length=None): + if length is None: length = len(raw_id) + res = ''.join('{:02x}'.format(raw_id[i]) for i in range(length)) + return res.upper() + +def lIntroductionPoints(controller=None, lOnions=[], itimeout=120, log_level=10): + """now working !!! stem 1.8.x timeout must be huge >120""" + try: + from cryptography.utils import int_from_bytes + except ImportError: + # guessing - not in the current cryptography but stem expects it + def int_from_bytes(**args): return int.to_bytes(*args) + cryptography.utils.int_from_bytes = int_from_bytes + # this will fai if the trick above didnt work + from stem.prereq import is_crypto_available + is_crypto_available(ed25519 = True) + + from stem.descriptor.hidden_service import HiddenServiceDescriptorV3 + from stem.client.datatype import LinkByFingerprint + + if type(lOnions) not in [set, tuple, list]: + lOnions = list(lOnions) + if controller is None: + controller = oGetStemController(log_level=log_level) l = [] try: - controller = oGetStemController(log_level=10) - desc = controller.get_hidden_service_descriptor(target) - l = desc.introduction_points() - if l: - LOG.warn(f"{elt} NO introduction points for {target}\n") - return l - LOG.debug(f"{elt} len(l) introduction points for {target}") - - for introduction_point in l: - l.append('%s:%s => %s' % (introduction_point.address, - introduction_point.port, - introduction_point.identifier)) + for elt in lOnions: + LOG.info(f"controller.get_hidden_service_descriptor {elt}") + desc = controller.get_hidden_service_descriptor(elt, + await_result=True, + timeout=itimeout) +# LOG.log(40, f"{dir(desc)} get_hidden_service_descriptor") + # timeouts 20 sec + # mistakenly a HSv2 descriptor + hs_address = HiddenServiceDescriptorV3.from_str(str(desc)) # reparse as HSv3 + oInnerLayer = hs_address.decrypt(elt) +# LOG.log(40, f"{dir(oInnerLayer)}") + # IntroductionPointV3 + n = oInnerLayer.introduction_points + if not n: + LOG.warn(f"NO introduction points for {elt}") + continue + LOG.info(f"{elt} {len(n)} introduction points") + lp = [] + for introduction_point in n: + for linkspecifier in introduction_point.link_specifiers: + if isinstance(linkspecifier, LinkByFingerprint): +# LOG.log(40, f"Getting fingerprint for {linkspecifier}") + if hasattr(linkspecifier, 'fingerprint'): + assert len(linkspecifier.value) == 20 + lp += [bin_to_hex(linkspecifier.value)] + LOG.info(f"{len(lp)} introduction points for {elt}") + l += lp except Exception as e: - LOG.exception(e) + LOG.exception(e) return l +def zResolveDomain(domain): + try: + ip = sTorResolve(domain) + except Exception as e: + ip = '' + if ip == '': + try: + lpair = getaddrinfo(domain, 443) + except Exception as e: + LOG.warn("{e}") + lpair = None + if lpair is None: + LOG.warn(f"TorResolv and getaddrinfo failed for {domain}") + return '' + ip = lpair[0] + return ip + def sTorResolve(target, verbose=False, sHost='127.0.0.1', @@ -76,7 +221,9 @@ def sTorResolve(target, SOCK_TIMEOUT_TRIES=3, ): MAX_INFO_RESPONSE_PACKET_LENGTH = 8 - + if '@' in target: + LOG.warn(f"sTorResolve failed invalid hostname {target}" ) + return '' target = target.strip('/') seb = b"\o004\o360\o000\o000\o000\o000\o000\o001\o000" seb = b"\x04\xf0\x00\x00\x00\x00\x00\x01\x00" @@ -122,7 +269,7 @@ def sTorResolve(target, sLabel = "5 No reply #" else: sLabel = "5 No data #" - LOG.info(sLabel +f"{i} from {sHost} {iPort}" ) + LOG.info(sLabel +f"{i} on {sHost}:{iPort}" ) sock.close() raise SystemExit(5) @@ -134,7 +281,7 @@ def sTorResolve(target, return f"{data[4]}.{data[5]}.{data[6]}.{data[7]}" else: # 91 - LOG.warn(f"tor-resolve failed for {target} from {sHost} {iPort}" ) + LOG.warn(f"tor-resolve failed for {target} on {sHost}:{iPort}" ) os.system(f"tor-resolve -4 {target} > /tmp/e 2>/dev/null") # os.system("strace tor-resolve -4 "+target+" 2>&1|grep '^sen\|^rec'") @@ -200,3 +347,45 @@ def icheck_torrc(sFile, oArgs): print('VirtualAddrNetworkIPv4 172.16.0.0/12') return 0 +def lExitExcluder(oArgs, iPort=9051, log_level=10): + """ + https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py + """ + if not stem: + LOG.warn('please install the stem Python package') + return '' + LOG.debug('lExcludeExitNodes') + + try: + controller = oGetStemController(log_level=log_level) + # generator + relays = controller.get_server_descriptors() + except Exception as e: + LOG.error(f'Failed to get relay descriptors {e}') + return None + + if controller.is_set('ExcludeExitNodes'): + LOG.info('ExcludeExitNodes is in use already.') + return None + + exit_excludelist=[] + LOG.debug("Excluded exit relays:") + for relay in relays: + if relay.exit_policy.is_exiting_allowed() and not relay.contact: + if is_valid_fingerprint(relay.fingerprint): + exit_excludelist.append(relay.fingerprint) + LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint) + else: + LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint) + + try: + controller.set_conf('ExcludeExitNodes', exit_excludelist) + LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist)) + except Exception as e: + LOG.exception('ExcludeExitNodes ' +str(e)) + return exit_excludelist + +if __name__ == '__main__': + target = 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad' + controller = oGetStemController(log_level=10) + lIntroductionPoints(controller, [target], itimeout=120)