This commit is contained in:
emdee 2022-11-02 08:18:52 +00:00
parent 04985b1fb2
commit 3e093a1a41

View File

@ -8,6 +8,7 @@ import re
import pickle import pickle
import logging import logging
import ctypes import ctypes
import traceback
from time import sleep from time import sleep
from threading import Thread from threading import Thread
@ -18,6 +19,7 @@ import warnings
warnings.filterwarnings('ignore') warnings.filterwarnings('ignore')
import wrapper import wrapper
import wrapper_tests
from wrapper.tox import Tox from wrapper.tox import Tox
from wrapper.toxav import ToxAV from wrapper.toxav import ToxAV
import wrapper.toxcore_enums_and_consts as enums import wrapper.toxcore_enums_and_consts as enums
@ -36,7 +38,7 @@ import wrapper.toxencryptsave as tox_encrypt_save
global LOG global LOG
LOG = logging.getLogger('app.'+'ts') LOG = logging.getLogger('app.'+'ts')
class SyniToxError(Exception): pass
NAME = 'SyniTox' NAME = 'SyniTox'
# possible CA locations picks the first one # possible CA locations picks the first one
@ -60,7 +62,8 @@ def LOG_TRACE(a):
if bVERBOSE: print('TRAC> '+a) if bVERBOSE: print('TRAC> '+a)
# https://wiki.python.org/moin/SSL # https://wiki.python.org/moin/SSL
def ssl_verify_cb(HOST): def ssl_verify_cb(HOST, override=False):
assert HOST
# wrapps host # wrapps host
def ssl_verify(*args): def ssl_verify(*args):
""" """
@ -68,6 +71,7 @@ def ssl_verify_cb(HOST):
should return true if verification passes and false otherwise should return true if verification passes and false otherwise
""" """
LOG.debug(f"ssl_verify {len(args)} {args}") LOG.debug(f"ssl_verify {len(args)} {args}")
if override: return True
ssl_conn, x509, error_num, depth, return_code = args ssl_conn, x509, error_num, depth, return_code = args
if error_num != 0: if error_num != 0:
return False return False
@ -177,28 +181,38 @@ class SyniTox(Tox):
with open(self.sMEMORY_DB, 'r') as f: with open(self.sMEMORY_DB, 'r') as f:
self.memory = pickle.load(f) self.memory = pickle.load(f)
if self._oArgs.irc_ssl != '':
self.start_ssl(self._oArgs.irc_host)
def start_ssl(self, HOST): def start_ssl(self, HOST):
if not self._ssl_context: if not self._ssl_context:
if HOST.endswith('.onion'):
override = True
else:
override = False
# TLSv1_3_METHOD does not exist # TLSv1_3_METHOD does not exist
context = SSL.Context(SSL.TLSv1_2_METHOD) context = SSL.Context(SSL.TLSv1_2_METHOD)
context.set_options(SSL.OP_NO_SSLv2|SSL.OP_NO_SSLv3|SSL.OP_NO_TLSv1) context.set_options(SSL.OP_NO_SSLv2|SSL.OP_NO_SSLv3|SSL.OP_NO_TLSv1)
if self._oArgs.irc_pem: if self._oArgs.irc_pem:
key = self._oArgs.irc_pem
assert os.path.exists(key), key
val = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT val = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
LOG.info('Using keyfile: %s' % self._oArgs.irc_pem) LOG.info('Using keyfile: %s' % self._oArgs.irc_pem)
context.use_privatekey_file(self._oArgs.irc_pem) if False:
context.use_certificate_file(key, filetype=SSL.FILETYPE_PEM)
if True:
# key = self._oArgs.irc_pem.replace('.pem', '.key')
assert os.path.exists(key), key
context.use_privatekey_file(key, filetype=SSL.FILETYPE_PEM)
else: else:
val = SSL.VERIFY_PEER val = SSL.VERIFY_PEER
context.set_verify(val, ssl_verify_cb(self._oArgs.irc_host)) context.set_verify(val, ssl_verify_cb(HOST, override))
assert os.path.exists(self._oArgs.irc_ca), self._oArgs.irc_ca assert os.path.exists(self._oArgs.irc_ca), self._oArgs.irc_ca
if os.path.isdir(self._oArgs.irc_ca): if os.path.isdir(self._oArgs.irc_ca):
context.load_verify_locations(capath=self._oArgs.irc_ca) context.load_verify_locations(capath=self._oArgs.irc_ca)
else: else:
context.load_verify_locations(cafile=self._oArgs.irc_ca) context.load_verify_locations(cafile=self._oArgs.irc_ca)
if self._oArgs.irc_ssl == 'tls1.2': if False:
pass
elif self._oArgs.irc_ssl == 'tls1.2':
context.set_min_proto_version(SSL.TLS1_2_VERSION) context.set_min_proto_version(SSL.TLS1_2_VERSION)
elif self._oArgs.irc_ssl == 'tls1.3': elif self._oArgs.irc_ssl == 'tls1.3':
context.set_min_proto_version(SSL.TLS1_3_VERSION) context.set_min_proto_version(SSL.TLS1_3_VERSION)
@ -345,6 +359,8 @@ class SyniTox(Tox):
def init_groups(self): def init_groups(self):
LOG.debug(f"init_groups proxy={self._oArgs.proxy_type}") LOG.debug(f"init_groups proxy={self._oArgs.proxy_type}")
group_name = self._oArgs.bot_name +' Test ' +self._oArgs.irc_chan group_name = self._oArgs.bot_name +' Test ' +self._oArgs.irc_chan
if not self.bRouted(): return
try:
if self.sGROUP_BOT_NUM < 0: if self.sGROUP_BOT_NUM < 0:
# ToDo: look for the first group of the profile # ToDo: look for the first group of the profile
i = self.group_get_number_groups() i = self.group_get_number_groups()
@ -363,8 +379,6 @@ class SyniTox(Tox):
LOG.info(f"init_groups GROUP_BOT_PK={self.sGROUP_BOT_PK}") LOG.info(f"init_groups GROUP_BOT_PK={self.sGROUP_BOT_PK}")
if self.bRouted():
try:
self.start_groups() self.start_groups()
except Exception as e: except Exception as e:
LOG.warn(f"init_groups self.start_groups {e}") LOG.warn(f"init_groups self.start_groups {e}")
@ -431,47 +445,85 @@ class SyniTox(Tox):
self._oArgs.proxy_host, self._oArgs.proxy_host,
self._oArgs.proxy_port) self._oArgs.proxy_port)
irc = socks.socksocket() irc = socks.socksocket()
iTIMEOUT = 15
elif self._oArgs.proxy_type == 1: elif self._oArgs.proxy_type == 1:
socks.setdefaultproxy(socks.PROXY_TYPE_HTTP, socks.setdefaultproxy(socks.PROXY_TYPE_HTTP,
self._oArgs.proxy_host, self._oArgs.proxy_host,
self._oArgs.proxy_port) self._oArgs.proxy_port)
irc = socks.socksocket() irc = socks.socksocket()
iTIMEOUT = 15
else: else:
irc = socket.socket() irc = socket.socket()
if self._oArgs.irc_ssl: iTIMEOUT = 10
if not self._ssl_context:
self.start_ssl(self._oArgs.irc_host)
irc = SSL.Connection(self._ssl_context, irc)
try: try:
host = ts.sDNSLookup(self._oArgs.irc_host) ip = ts.sDNSLookup(self._oArgs.irc_connect)
except Exception as e: except Exception as e:
LOG.warn(f"{self._oArgs.irc_host} errored in resolve {e}") LOG.warn(f"{self._oArgs.irc_host} errored in resolve {e}")
host = self._oArgs.irc_host ip = self._oArgs.irc_connect
else: else:
if not host: if not ip:
LOG.warn(f"{self._oArgs.irc_host} did not resolve.") LOG.warn(f"{self._oArgs.irc_host} did not resolve.")
host = self._oArgs.irc_host ip = self._oArgs.irc_connect
irc.connect((host, self._oArgs.irc_port)) # https://github.com/pyca/pyopenssl/issues/168
irc.do_handshake() if self._oArgs.irc_ssl:
LOG.info('IRC SSL connected ') if not self._ssl_context:
self.start_ssl(self._oArgs.irc_connect)
irc = SSL.Connection(self._ssl_context, irc)
irc.connect((ip, self._oArgs.irc_port))
if ip.endswith('.onion'):
irc.set_tlsext_host_name(None)
else: else:
irc.connect((self._oArgs.irc_host, self._oArgs.irc_port)) irc.set_tlsext_host_name(bytes(self._oArgs.irc_host, 'UTF-8'))
LOG.info('IRC connected ') irc.set_connect_state()
while True:
try:
irc.do_handshake()
except SSl.WantReadError:
rd,_,_ = select.select([irc], [], [], irc.gettimeout())
if not rd:
raise socket.timeout('timeout')
continue
except SSl.Error as e:
raise
break
for cert in irc.get_peer_cert_chain():
print(f"{cert.get_subject} {cert.get_issuer}")
else:
irc.connect((ip, self._oArgs.irc_port))
LOG.info(f"IRC {'SSL ' if self._oArgs.irc_ssl else ''} connected ")
except wrapper_tests.socks.Socks5Error as e:
if len(e.args[0]) == 2 and e.args[0][0] ==2:
LOG.warn(f"Socks5Error: do you have Tor SafeSocks set? {e.args[0]}")
return
else:
LOG.error(f"Socks5Error: {e.args}")
raise SyniToxError(f"{e.args}")
except socket.timeout as e:
LOG.warn(f"socket error: {e.args}")
return
except ( SSL.Error, ) as e: except ( SSL.Error, ) as e:
LOG.warn(f"SSL error: {e.args}") LOG.warn(f"SSL error: {e.args}")
return return
except (SSL.SysCallError, ) as e: except (SSL.SysCallError, ) as e:
LOG.warn(f"SSL error: {e.args}") LOG.warn(f"SSLSyscall error: {e.args}")
LOG.warn(traceback.format_exc())
return return
except wrapper_tests.socks.Socks5Error as e:
# (2, 'connection not allowed by ruleset')
raise
except Exception as e: except Exception as e:
LOG.warn(f"Error: {e}") LOG.warn(f"Error: {e}")
LOG.warn(traceback.format_exc())
return return
irc.send(bytes('NICK ' + nick + '\r\n', 'UTF-8' ))
irc.send(bytes('USER %s %s bla :%s\r\n' % (
ident, self._oArgs.irc_host, realname), 'UTF-8'))
self.irc = irc self.irc = irc
if not self._oArgs.irc_ssl:
self.irc.send(bytes('NICK ' + nick + '\r\n', 'UTF-8' ))
self.irc.send(bytes('USER %s %s bla :%s\r\n' % (
self._oArgs.irc_ident,
self._oArgs.irc_host,
self._oArgs.irc_name), 'UTF-8'))
def dht_init(self): def dht_init(self):
if not self.bRouted(): return if not self.bRouted(): return
@ -489,7 +541,7 @@ class SyniTox(Tox):
self.test_net() self.test_net()
lNodes = self._settings['current_nodes_tcp'] lNodes = self._settings['current_nodes_tcp']
shuffle(lNodes) shuffle(lNodes)
LOG.info(f'TCP bootstapping 6') LOG.debug(f'TCP bootstapping 6')
ts.bootstrap_tcp(lNodes[:6], [self]) ts.bootstrap_tcp(lNodes[:6], [self])
def get_all_groups(self): def get_all_groups(self):
@ -566,9 +618,9 @@ class SyniTox(Tox):
for line in lines[:5]: for line in lines[:5]:
line = str(line, 'UTF-8').strip().lower() line = str(line, 'UTF-8').strip().lower()
if 'banned' in line: if 'banned' in line:
raise RuntimeError(line) raise SyniToxError(line)
if 'error' in line and 'closing' in line: if 'error' in line and 'closing' in line:
raise RuntimeError(line) raise SyniToxError(line)
def irc_readlines(self): def irc_readlines(self):
nick = self._oArgs.irc_nick nick = self._oArgs.irc_nick
@ -598,18 +650,29 @@ class SyniTox(Tox):
self.irc_send('PONG %s\r\n' % l[1]) self.irc_send('PONG %s\r\n' % l[1])
elif len(l) < 2: elif len(l) < 2:
pass pass
elif l[1] == '376': elif l[1] in ['461', '431']:
pass
elif l[1] in ['433', '462', '477']:
if self._oArgs.irc_ssl:
LOG.warn("Maybe the certificate was not received")
raise SyniToxError(line)
elif l[1] in ['376']:
# :End of /MOTD command # :End of /MOTD command
if email == '': if self._oArgs.irc_ssl != '':
pass
elif email == '' and pwd:
LOG.info(bytes('PRIVMSG NickServ IDENTIFY %s %s\r\n'
% (nick, pwd,), 'UTF-8'))
self.irc.send(bytes('PRIVMSG NickServ IDENTIFY %s %s\r\n' self.irc.send(bytes('PRIVMSG NickServ IDENTIFY %s %s\r\n'
% (nick, pwd,), 'UTF-8')) % (nick, pwd,), 'UTF-8'))
else: elif email != '' and pwd:
LOG.info(bytes('PRIVMSG NickServ REGISTER %s %s\r\n'
% (pwd, email,), 'UTF-8'))
self.irc.send(bytes('PRIVMSG NickServ REGISTER %s %s\r\n' self.irc.send(bytes('PRIVMSG NickServ REGISTER %s %s\r\n'
% (pwd, email,), 'UTF-8')) % (pwd, email,), 'UTF-8'))
if False and fp: else:
LOG.info(f"PRIVMSG NickServ CERT ADD") LOG.error("you must provide a password to register")
# self.irc.send(bytes(f'PRIVMSG NickServ CERT ADD {fp}\r\n', 'UTF-8')) raise RuntimeError("you must provide a password to register")
#
self.irc.send(bytes('JOIN %s\r\n' % self._oArgs.irc_chan, 'UTF-8')) self.irc.send(bytes('JOIN %s\r\n' % self._oArgs.irc_chan, 'UTF-8'))
# put off init_groups until you have joined IRC # put off init_groups until you have joined IRC
self.init_groups() self.init_groups()
@ -651,14 +714,15 @@ class SyniTox(Tox):
if content.startswith('^'): if content.startswith('^'):
self.handle_command(content) self.handle_command(content)
def spin(self, n=20): def spin(self, n=20, iMax=1000):
readable = False readable = False
waiti = 0 waiti = 0
while not readable: while not readable:
waiti += 1 waiti += 1
readable, _, _ = select.select([self.irc], [], [], n/1000.0 ) readable, _, _ = select.select([self.irc], [], [], n/100.0 )
if readable and len(readable) and readable[0]: return readable
self.do(n) self.do(n)
if waiti > 100: break if waiti > iMax: break
return readable return readable
def iLoop(self): def iLoop(self):
@ -670,6 +734,8 @@ class SyniTox(Tox):
iDelay = 10 iDelay = 10
nick = self._oArgs.irc_nick nick = self._oArgs.irc_nick
realname = self._oArgs.irc_name
ident = self._oArgs.irc_ident
pwd = self._oArgs.irc_pass pwd = self._oArgs.irc_pass
email = self._oArgs.irc_email email = self._oArgs.irc_email
LOG.info(f"Looping for Tox and IRC connections") LOG.info(f"Looping for Tox and IRC connections")
@ -697,7 +763,7 @@ class SyniTox(Tox):
self.dht_init() self.dht_init()
LOG.info(f'Not DHT connected {iCount} iterating {10 + iDelay} seconds') LOG.info(f'Not DHT connected {iCount} iterating {10 + iDelay} seconds')
iDelay = iDelay + iDelay // 10 iDelay = iDelay + iDelay // 10
self.do(10 + iDelay) self.do(iDelay)
#drop through #drop through
if not group_connected and dht_conneted: if not group_connected and dht_conneted:
@ -725,22 +791,25 @@ class SyniTox(Tox):
group_connected = False group_connected = False
if not self.irc: if not self.irc:
LOG.info('Disconnected from IRC.')
self.irc_init() self.irc_init()
if not self.irc: if not self.irc:
self.do(20) self.do(20)
continue continue
LOG.info(f'Waiting on IRC to {self._oArgs.irc_host} on {self._oArgs.irc_port}') LOG.info(f'Waiting on IRC to {self._oArgs.irc_host} on {self._oArgs.irc_port}')
readable = self.spin(20) readable = self.spin(20)
if not readable: if not readable or not readable[0]:
LOG.info('Waited on IRC but nothing to read.') LOG.info('Waited on IRC but nothing to read.')
iDelay = iDelay + iDelay // 10 iDelay = iDelay + iDelay // 10
continue continue
try: try:
self.irc_readlines() pass
except Exception as e: except Exception as e:
if len(e.args) > 1 and e.args[0] == 32:
raise
elif f"{e}" != "2":
LOG.warn(f'IRC Error during read: {e}') LOG.warn(f'IRC Error during read: {e}')
# close irc? # close irc?
try: try:
@ -750,7 +819,10 @@ class SyniTox(Tox):
continue continue
else: else:
iDelay = 10 iDelay = 10
else:
iDelay = 10
self.irc_readlines()
return 0 return 0
@ -887,9 +959,12 @@ def iMain(oArgs, oOpts):
# OpenSSL.SSL.SysCallError: (9, 'EBADF') # OpenSSL.SSL.SysCallError: (9, 'EBADF')
LOG.error(f"SSL error: {e.args}") LOG.error(f"SSL error: {e.args}")
ret = 1 ret = 1
except SyniToxError as e:
LOG.error(f'Error running program:\n{e}')
ret = 2
except Exception as e: except Exception as e:
LOG.exception(f'Error running program:\n{e}') LOG.exception(f'Error running program:\n{e}')
ret = 2 ret = 3
else: else:
ret = 0 ret = 0
return ret return ret
@ -951,8 +1026,11 @@ def oArgparse(lArgv):
# choices=['', 'startls', 'direct') # choices=['', 'startls', 'direct')
# does host == connect ? # does host == connect ?
# oftcnet6xg6roj6d7id4y4cu6dchysacqj2ldgea73qzdagufflqxrid.onion:6697 # oftcnet6xg6roj6d7id4y4cu6dchysacqj2ldgea73qzdagufflqxrid.onion:6697
parser.add_argument('--irc_host', type=str, default='irc.oftc.net', # irc.oftc.net
parser.add_argument('--irc_host', type=str, default='',
help="irc.libera.chat will not work over Tor") help="irc.libera.chat will not work over Tor")
parser.add_argument('--irc_connect', type=str, default='',
help="defaults to irc_host")
parser.add_argument('--irc_port', type=int, default=6667, parser.add_argument('--irc_port', type=int, default=6667,
help="default 6667, but may be 6697 with SSL") help="default 6667, but may be 6697 with SSL")
parser.add_argument('--irc_chan', type=str, default='#tor', parser.add_argument('--irc_chan', type=str, default='#tor',
@ -1019,6 +1097,9 @@ def main(lArgs=None):
if lArgs is None: lArgs = [] if lArgs is None: lArgs = []
global oTOX_OARGS global oTOX_OARGS
oTOX_OARGS = oArgparse(lArgs) oTOX_OARGS = oArgparse(lArgs)
assert oTOX_OARGS.irc_host or oTOX_OARGS.irc_connect
if not oTOX_OARGS.irc_connect:
oTOX_OARGS.irc_connect = oTOX_OARGS.irc_host
global oTOX_OPTIONS global oTOX_OPTIONS
oTOX_OPTIONS = oToxygenToxOptions(oTOX_OARGS) oTOX_OPTIONS = oToxygenToxOptions(oTOX_OARGS)
ts.vSetupLogging(oTOX_OARGS) ts.vSetupLogging(oTOX_OARGS)