tox_irc_sync/tox-irc-sync.py

1274 lines
50 KiB
Python
Raw Normal View History

2022-10-26 08:44:57 +00:00
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
2022-11-17 12:08:38 +00:00
import ctypes
import logging
2022-10-23 22:11:12 +00:00
import os
2014-02-20 11:38:20 +00:00
import pickle
2022-11-17 12:08:38 +00:00
import re
import select
import socket
import sys
2022-11-02 08:18:52 +00:00
import traceback
2022-11-03 02:51:14 +00:00
from errno import errorcode
2022-11-17 12:08:38 +00:00
from random import shuffle
from time import sleep
2013-12-01 15:09:54 +00:00
2022-11-17 12:08:38 +00:00
from OpenSSL import SSL
2022-10-29 18:44:37 +00:00
2022-10-23 22:11:12 +00:00
import wrapper
2022-11-17 12:08:38 +00:00
import wrapper.toxcore_enums_and_consts as enums
2022-11-02 08:18:52 +00:00
import wrapper_tests
2022-10-23 22:11:12 +00:00
from wrapper.tox import Tox
from wrapper.toxav import ToxAV
2022-11-17 12:08:38 +00:00
from wrapper.toxcore_enums_and_consts import (TOX_ADDRESS_SIZE, TOX_CONNECTION,
TOX_FILE_CONTROL,
TOX_GROUP_PRIVACY_STATE,
TOX_GROUP_ROLE, TOX_MESSAGE_TYPE,
TOX_SECRET_KEY_SIZE,
TOX_USER_STATUS)
2022-10-26 08:44:57 +00:00
from wrapper_tests import socks
2022-10-23 22:11:12 +00:00
try:
import support_testing as ts
except ImportError:
import wrapper_tests.support_testing as ts
2022-10-26 08:44:57 +00:00
2022-11-17 12:08:38 +00:00
import wrapper.toxencryptsave as tox_encrypt_save
2013-12-01 15:09:54 +00:00
2022-10-23 22:11:12 +00:00
global LOG
LOG = logging.getLogger('app.'+'ts')
2022-11-17 12:08:38 +00:00
import warnings
warnings.filterwarnings('ignore')
2022-11-03 02:51:14 +00:00
class SyniToxError(BaseException): pass
2014-04-09 20:48:24 +00:00
2022-10-26 08:44:57 +00:00
NAME = 'SyniTox'
2022-11-06 03:57:13 +00:00
sMSG = 'MSG'
2022-11-17 12:08:38 +00:00
sMSG = 'PRIVMSG'
2022-11-03 02:51:14 +00:00
SSL_TOR_RANGE = '172.'
2022-10-26 08:44:57 +00:00
# possible CA locations picks the first one
2022-11-03 02:51:14 +00:00
lCAs = [# debian and gentoo
'/etc/ssl/certs/',
]
lCAfs = SSL._CERTIFICATE_FILE_LOCATIONS
# openssl ciphers -s -v|grep 1.3 > /tmp/v1.3
lOPENSSL_13_CIPHERS = ['TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256']
lOPENSSL_12_CIPHERS = ['ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES256-GCM-SHA384',
'DHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-CHACHA20-POLY1305',
'ECDHE-RSA-CHACHA20-POLY1305',
'DHE-RSA-CHACHA20-POLY1305',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-GCM-SHA256',
'DHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-SHA384',
'ECDHE-RSA-AES256-SHA384',
'DHE-RSA-AES256-SHA256',
'ECDHE-ECDSA-AES128-SHA256',
'ECDHE-RSA-AES128-SHA256',
'DHE-RSA-AES128-SHA256',
'AES256-GCM-SHA384',
'AES128-GCM-SHA256',
'AES256-SHA256',
'AES128-SHA256'
]
2022-10-23 22:11:12 +00:00
bot_toxname = 'SyniTox'
2022-11-03 02:51:14 +00:00
iSocks5ErrorMax = 5
iSocks5Error = 0
2022-10-23 22:11:12 +00:00
2022-10-26 08:44:57 +00:00
# tox.py can be called by callbacks
def LOG_ERROR(a): print('EROR> '+a)
def LOG_WARN(a): print('WARN> '+a)
def LOG_INFO(a):
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20
if bVERBOSE: print('INFO> '+a)
def LOG_DEBUG(a):
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10-1
if bVERBOSE: print('DBUG> '+a)
def LOG_TRACE(a):
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10
if bVERBOSE: print('TRAC> '+a)
# https://wiki.python.org/moin/SSL
2022-11-03 02:51:14 +00:00
def ssl_verify_cb(host, override=False):
assert host
2022-10-26 08:44:57 +00:00
# wrapps host
def ssl_verify(*args):
"""
callback for certificate validation
should return true if verification passes and false otherwise
"""
LOG.debug(f"ssl_verify {len(args)} {args}")
2022-11-03 02:51:14 +00:00
# app.ts WARNING SSL error: ([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],) # on .onion - fair enough
2022-11-02 08:18:52 +00:00
if override: return True
2022-11-03 02:51:14 +00:00
2022-10-26 08:44:57 +00:00
ssl_conn, x509, error_num, depth, return_code = args
if error_num != 0:
2022-11-03 02:51:14 +00:00
LOG.warn(f"ssl_verify error_num={error_num} {errorcode.get(error_num)}")
2022-10-26 08:44:57 +00:00
return False
if depth != 0:
# don't validate names of root certificates
return True
2022-11-03 02:51:14 +00:00
if x509.get_subject().commonName == host:
2022-10-26 08:44:57 +00:00
return True
# allow matching subdomains
2022-11-03 02:51:14 +00:00
have , want = x509.get_subject().commonName, host
2022-10-26 08:44:57 +00:00
if len(have.split('.')) == len(want.split('.')) and len(want.split('.')) > 2:
if have.split('.')[1:] == want.split('.')[1:]:
2022-11-03 02:51:14 +00:00
LOG.warn(f"ssl_verify accepting {x509.get_subject().commonName} for {host}")
2022-10-26 08:44:57 +00:00
return True
return False
return ssl_verify
2022-11-03 02:51:14 +00:00
2022-10-23 22:11:12 +00:00
class SyniTox(Tox):
2022-10-26 08:44:57 +00:00
def __init__(self,
oArgs,
oOpts,
2022-10-23 22:11:12 +00:00
GROUP_BOT_PK = '',
sMEMORY_DB = ''
):
2022-10-26 08:44:57 +00:00
opts = oTOX_OPTIONS
2022-10-23 22:11:12 +00:00
self._opts = opts
2022-11-17 12:08:38 +00:00
self._oargs = oArgs
2022-10-26 08:44:57 +00:00
2022-11-17 12:08:38 +00:00
# self._oargs.profile
self.load_profile(self._opts, self._oargs, self._oargs.password)
2022-10-26 08:44:57 +00:00
Tox.__init__(self, tox_options=self._opts)
self._address = self.self_get_address()
2022-10-23 22:11:12 +00:00
self._app = None
self._settings = {}
self.av = self.AV
self.irc = None
self.bid = -1
self._bRouted = None
2022-10-26 08:44:57 +00:00
self._ssl_context = None
self._irc_id = ''
self._toxes = None
self.joined = None
self.request = None
self.memory = {}
self.readbuffer = b''
#? tox_group_id
self._peers = []
self._groups = {}
2022-10-23 22:11:12 +00:00
2022-10-26 08:44:57 +00:00
self.sMEMORY_DB = sMEMORY_DB
self.sGROUP_BOT_PK = GROUP_BOT_PK
self.sGROUP_BOT_NUM = -1
def load_profile(self, tox_options, oArgs, password=''):
if oArgs.profile and os.path.exists(oArgs.profile):
data = open(oArgs.profile, 'rb').read()
else:
data = None
if data and self.has_password():
data = self.pass_decrypt(data)
if data: # load existing profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
tox_options.contents.savedata_data = ctypes.c_char_p(data)
tox_options.contents.savedata_length = len(data)
else: # create new profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
tox_options.contents.savedata_data = None
tox_options.contents.savedata_length = 0
def _save_profile(self, data=None):
LOG.debug("_save_profile")
data = data or self.get_savedata()
if self.has_password():
data = self.pass_encrypt(data)
try:
suf = f"{os.getpid()}"
2022-11-17 12:08:38 +00:00
with open(self._oargs.profile+suf, 'wb') as fl:
2022-10-26 08:44:57 +00:00
fl.write(data)
2022-11-17 12:08:38 +00:00
stat = os.stat(self._oargs.profile+suf)
2022-10-26 08:44:57 +00:00
if hasattr(stat, 'st_blocks'):
2022-11-17 12:08:38 +00:00
assert stat.st_blocks > 0, f"Zero length file {self._oargs.profile+suf}"
os.rename(self._oargs.profile+suf, self._oargs.profile)
LOG.info('Profile saved successfully to' +self._oargs.profile)
2022-10-26 08:44:57 +00:00
except Exception as e:
2022-11-17 12:08:38 +00:00
LOG.warn(f"Profile save failed to {self._oargs.profile}\n{e}")
2022-10-26 08:44:57 +00:00
2022-10-23 22:11:12 +00:00
def start(self):
2022-10-26 08:44:57 +00:00
self._tox = self
self._toxes = tox_encrypt_save.ToxEncryptSave()
2022-11-17 12:08:38 +00:00
self.self_set_name(self._oargs.bot_name)
2022-10-23 22:11:12 +00:00
self.self_set_status_message("Send me a message with the word 'invite'")
LOG.info('Our ToxID: %s' % self.self_get_toxid())
2013-12-01 15:09:54 +00:00
self.tox_group_id = None
2022-10-26 08:44:57 +00:00
self.init_callbacks()
2022-10-23 22:11:12 +00:00
if os.path.exists(self.sMEMORY_DB):
with open(self.sMEMORY_DB, 'r') as f:
2014-02-20 11:38:20 +00:00
self.memory = pickle.load(f)
2022-10-26 08:44:57 +00:00
def start_ssl(self, HOST):
if not self._ssl_context:
2022-11-03 02:51:14 +00:00
try:
OP_NO_TLSv1_3 = SSL._lib.SSL_OP_NO_TLSv1_3
except AttributeError:
2022-11-17 12:08:38 +00:00
if self._oargs.irc_ssl == 'tlsv1.3':
2022-11-03 02:51:14 +00:00
LOG.warning("SSL._lib.SSL_OP_NO_TLSv1_3 is not supported")
LOG.warning("Downgrading SSL to tlsv1.2 ")
2022-11-17 12:08:38 +00:00
self._oargs.irc_ssl = 'tlsv1.2'
2022-11-03 02:51:14 +00:00
else:
LOG.debug("SSL._lib.SSL_OP_NO_TLSv1_3 is not supported")
else:
LOG.debug("SSL._lib.SSL_OP_NO_TLSv1_3 is supported")
2022-11-17 12:08:38 +00:00
if self._oargs.irc_connect.endswith('.onion') or \
self._oargs.irc_connect.startswith(SSL_TOR_RANGE):
2022-11-02 08:18:52 +00:00
override = True
else:
override = False
2022-10-26 08:44:57 +00:00
# TLSv1_3_METHOD does not exist
2022-11-03 05:31:50 +00:00
context = SSL.Context(SSL.TLS_CLIENT_METHOD) # TLSv1_2_METHOD
2022-11-03 02:51:14 +00:00
# SSL.OP_NO_TLSv1_1 is allowed
2022-10-26 08:44:57 +00:00
context.set_options(SSL.OP_NO_SSLv2|SSL.OP_NO_SSLv3|SSL.OP_NO_TLSv1)
2022-11-03 02:51:14 +00:00
2022-11-17 12:08:38 +00:00
if self._oargs.irc_crt and self._oargs.irc_key:
2022-10-26 08:44:57 +00:00
val = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
2022-11-06 03:57:13 +00:00
if True: # required!
2022-11-17 12:08:38 +00:00
key = self._oargs.irc_crt
2022-11-03 05:31:50 +00:00
assert os.path.exists(key), key
2022-11-17 12:08:38 +00:00
LOG.info('Using keyfile: %s' % key)
2022-11-02 08:18:52 +00:00
context.use_certificate_file(key, filetype=SSL.FILETYPE_PEM)
2022-11-06 03:57:13 +00:00
if True: # required!
2022-11-17 12:08:38 +00:00
key = self._oargs.irc_key
2022-11-02 08:18:52 +00:00
assert os.path.exists(key), key
2022-11-17 12:08:38 +00:00
LOG.info('Using keyfile: %s' % key)
2022-11-02 08:18:52 +00:00
context.use_privatekey_file(key, filetype=SSL.FILETYPE_PEM)
2022-11-03 05:31:50 +00:00
#? load_client_ca
def SSL_hands_cb(oConn,iLine,iRet):
# where in the SSL handshake the function was called, and
# the return code from a internal function call
print(f"iLine={iLine}, iRet={iRet}")
2022-11-06 03:57:13 +00:00
context.set_info_callback(SSL_hands_cb)
2022-11-03 05:31:50 +00:00
def keylog_callback(oConn,s):
print(s)
2022-11-06 03:57:13 +00:00
# context.set_keylog_callback(keylog_callback)
2022-10-26 08:44:57 +00:00
else:
val = SSL.VERIFY_PEER
2022-11-02 08:18:52 +00:00
context.set_verify(val, ssl_verify_cb(HOST, override))
2022-10-26 08:44:57 +00:00
2022-11-17 12:08:38 +00:00
if self._oargs.irc_cafile:
# context.load_verify_locations(capath=self._oargs.irc_ca)
context.load_verify_locations(self._oargs.irc_cafile, capath=self._oargs.irc_cadir)
elif self._oargs.irc_cadir:
context.load_verify_locations(None, capath=self._oargs.irc_cadir)
if self._oargs.irc_ssl == 'tlsv1.1':
2022-11-03 02:51:14 +00:00
context.set_min_proto_version(SSL.TLS1_1_VERSION)
2022-11-17 12:08:38 +00:00
elif self._oargs.irc_ssl == 'tlsv1.2':
2022-11-03 05:31:50 +00:00
context.set_cipher_list(bytes(':'.join(['DEFAULT@SECLEVEL=1']+lOPENSSL_12_CIPHERS), 'UTF-8'))
2022-10-26 08:44:57 +00:00
context.set_min_proto_version(SSL.TLS1_2_VERSION)
2022-11-17 12:08:38 +00:00
elif self._oargs.irc_ssl == 'tlsv1.3':
2022-11-03 05:31:50 +00:00
context.set_cipher_list(bytes(':'.join(['DEFAULT@SECLEVEL=1']+lOPENSSL_13_CIPHERS), 'UTF-8'))
2022-10-26 08:44:57 +00:00
context.set_min_proto_version(SSL.TLS1_3_VERSION)
self._ssl_context = context
return self._ssl_context
2022-10-23 22:11:12 +00:00
def bRouted(self):
2022-11-17 12:08:38 +00:00
if self._oargs.network in ['local']:
2022-10-26 08:44:57 +00:00
return True
b = ts.bAreWeConnected()
if b is None:
i = os.system('ip route|grep ^def')
if i > 0:
b = False
else:
b = True
self._bRouted = b
return b
2022-10-23 22:11:12 +00:00
def test_net(self, lElts=None, oThread=None, iMax=4):
2022-11-17 12:08:38 +00:00
LOG.debug("test_net network=" +self._oargs.network )
2022-10-23 22:11:12 +00:00
# bootstrap
2022-11-17 12:08:38 +00:00
lNodes = ts.generate_nodes(oArgs=self._oargs,
2022-10-23 22:11:12 +00:00
ipv='ipv4',
udp_not_tcp=True)
2022-11-06 03:57:13 +00:00
self._settings['current_nodes_udp'] = ts.lDNSClean(lNodes)
2022-10-23 22:11:12 +00:00
if not lNodes:
LOG.warn('empty generate_nodes udp')
else:
2022-10-26 08:44:57 +00:00
LOG.info(f'Called generate_nodes: udp {len(lNodes)}')
2022-10-23 22:11:12 +00:00
2022-11-17 12:08:38 +00:00
lNodes = ts.generate_nodes(oArgs=self._oargs,
2022-10-23 22:11:12 +00:00
ipv='ipv4',
udp_not_tcp=False)
2022-11-06 03:57:13 +00:00
self._settings['current_nodes_tcp'] = ts.lDNSClean(lNodes)
2022-10-23 22:11:12 +00:00
if not lNodes:
LOG.warn('empty generate_nodes tcp')
else:
2022-10-26 08:44:57 +00:00
LOG.info(f'Called generate_nodes: tcp {len(lNodes)}')
2022-10-23 22:11:12 +00:00
# if oThread and oThread._stop_thread: return
return True
2022-10-26 08:44:57 +00:00
def add_friend(self, pk):
self.friend_add_norequest(pk)
assert self.friend_exists(pk)
assert pk in self.self_get_friend_list()
friend_number = self.friend_by_public_key(pk)
return friend_number
def start_groups(self):
if not self.bRouted(): return False
if not self.group_is_connected(self.sGROUP_BOT_NUM):
self.group_reconnect(self.sGROUP_BOT_NUM)
if not self.group_is_connected(self.sGROUP_BOT_NUM):
return False
assert self.sGROUP_BOT_NUM
num = self.sGROUP_BOT_NUM
self.group_self_set_status(num, TOX_USER_STATUS['NONE'])
# add myself as a peer in the group or am I in as founder?
2022-10-23 22:11:12 +00:00
self.group_send_message(num, TOX_MESSAGE_TYPE['NORMAL'], "hi")
2022-10-26 08:44:57 +00:00
# The code in tests_wrapper need extending and then
# wiring up to here.
#
2022-11-17 12:08:38 +00:00
if self._oargs.group_invite:
pk = self._oargs.group_invite
2022-10-26 08:44:57 +00:00
if pk not in self.self_get_friend_list():
friend_number = self.add_friend(pk)
else:
friend_number = self.friend_by_public_key(pk)
b = self.group_invite_friend(num, friend_number)
LOG.info(f"A PK to invite to the group {b}")
return True
2022-11-17 12:08:38 +00:00
if self._oargs.group_moderator:
pk = self._oargs.group_moderator
2022-10-26 08:44:57 +00:00
if pk not in self.self_get_friend_list():
friend_number = self.add_friend(pk)
else:
friend_number = self.friend_by_public_key(pk)
role = TOX_GROUP_ROLE['MODERATOR']
# dunno
peer_id = friend_number
b = self.group_mod_set_role(num, peer_id, role)
LOG.info("A PK to invite to the group as moderator {b}")
return True
2022-11-17 12:08:38 +00:00
if self._oargs.group_ignore:
pk = self._oargs.group_ignore
2022-10-26 08:44:57 +00:00
if pk not in self.self_get_friend_list():
friend_number = self.add_friend(pk)
else:
friend_number = self.friend_by_public_key(pk)
# dunno
peer_id = friend_number
b = self.group_toggle_set_ignore(num, peer_id, True)
LOG.info("A PK to ignore in the group {b}")
return True
return None
def create_group(self):
2022-11-17 12:08:38 +00:00
privacy_state = TOX_GROUP_PRIVACY_STATE[self._oargs.group_state.upper()]
nick = self._oargs.group_nick
2022-10-26 08:44:57 +00:00
status = TOX_USER_STATUS['NONE']
2022-11-17 12:08:38 +00:00
group_name = self._oargs.group_name
2022-10-26 08:44:57 +00:00
num = self.group_new(privacy_state, group_name, nick, status)
assert num >= 0, num
2022-11-17 12:08:38 +00:00
self.group_set_topic(num, f"{group_name} IRC on {self._oargs.irc_host}" )
2022-10-26 08:44:57 +00:00
# self.tox_group_id = self.group_invite_accept(b'', friendid, nick)
chat_id = self.group_get_chat_id(num)
2022-11-17 12:08:38 +00:00
if self._oargs.profile and os.path.exists(os.path.dirname(self._oargs.profile)):
f = os.path.splitext(self._oargs.profile)[0] +'.chatid'
2022-10-26 08:44:57 +00:00
open(f, 'rt').write(chat_id)
LOG.info(f"Chat Id: {chat_id} written to {f}")
else:
LOG.info(f"Chat Id: {chat_id}")
# dunno
if self.self_get_friend_list():
friendid = self.self_get_friend_list()[0]
2022-11-17 12:08:38 +00:00
i = self.on_group_invite(friendid, b'', 0)
2022-10-26 08:44:57 +00:00
assert i
self.tox_group_id = i
return num
def join_group(self):
2022-11-17 12:08:38 +00:00
password = self._oargs.group_pass
nick = self._oargs.group_nick
2022-10-26 08:44:57 +00:00
# is the chat_id the pk?
2022-11-17 12:08:38 +00:00
chat_id = self._oargs.group_chatid
2022-10-26 09:13:01 +00:00
if not chat_id: return -1
2022-10-26 08:44:57 +00:00
num = self.group_join(chat_id, password, nick, status='')
self.sGROUP_BOT_NUM = num
self.group_self_set_status(num, TOX_USER_STATUS['NONE'])
return num
def init_groups(self):
2022-11-17 12:08:38 +00:00
LOG.debug(f"init_groups proxy={self._oargs.proxy_type}")
2022-11-02 08:18:52 +00:00
if not self.bRouted(): return
try:
if self.sGROUP_BOT_NUM < 0:
# ToDo: look for the first group of the profile
i = self.group_get_number_groups()
if i == 0:
if not self.bRouted(): return False
num = self.create_group()
self.sGROUP_BOT_NUM = num
elif i > 1:
LOG.error('There are more than one groups in this profile')
for ig in range(i):
LOG.warn(f"group #{ig} {self.group_self_get_name(ig)}")
raise RuntimeError("select one of the groups at the cmdline")
else:
if not self.bRouted(): return False
num = self.join_group()
LOG.info(f"init_groups GROUP_BOT_PK={self.sGROUP_BOT_PK}")
self.start_groups()
except Exception as e:
LOG.warn(f"init_groups self.start_groups {e}")
return False
2022-10-26 08:44:57 +00:00
# TOX_GROUP_ROLE['FOUNDER']
return True
2022-10-23 22:11:12 +00:00
def init_callbacks(self):
2022-10-29 18:44:37 +00:00
return
2022-10-26 08:44:57 +00:00
# wraps self with
LOG.info("Adding Tox init_callbacks")
2022-10-23 22:11:12 +00:00
def gi_wrapped(iTox, friendid, invite_data, invite_len, *args):
invite_data = str(invite_data, 'UTF-8')
2022-10-26 08:44:57 +00:00
LOG.debug(f'on_group_invite {friendid} {invite_data}')
self.on_group_invite(friendid, invite_data, 0)
2022-10-23 22:11:12 +00:00
self.callback_group_invite(gi_wrapped, 0)
2022-10-26 08:44:57 +00:00
def scs_wrapped(iTox, friendid, status, *args):
2022-11-17 12:08:38 +00:00
LOG.debug(f'on_connection_status {friendid} {status}.')
2022-10-26 08:44:57 +00:00
self.on_connection_status(friendid, status)
2022-10-23 22:11:12 +00:00
self.callback_self_connection_status(scs_wrapped)
2022-10-26 08:44:57 +00:00
2022-10-23 22:11:12 +00:00
def gm_wrapped(iTox, groupnumber, peer_id, type_, message, mlen, *args):
message = str(message, 'UTF-8')
2022-10-26 08:44:57 +00:00
LOG.debug(f'on_group_message {groupnumber} {peer_id} {message}')
2022-10-23 22:11:12 +00:00
self.on_group_message(groupnumber, peer_id, message)
self.callback_group_message(gm_wrapped, 0)
2022-10-26 08:44:57 +00:00
2022-10-23 22:11:12 +00:00
def ga_wrapped(iTox, groupnumber, peer_id, type_, action, mlen, *args):
2022-10-26 08:44:57 +00:00
LOG.debug(f'on_group_action(groupnumber, peer_id, action)')
2022-10-23 22:11:12 +00:00
self.on_group_action(groupnumber, peer_id, action)
2022-10-26 08:44:57 +00:00
2022-10-23 22:11:12 +00:00
#? self.callback_group_action(ga_wrapped, 0)
def fr_wrapped(iTox, pk, message, mlen, *args):
message = str(message, 'UTF-8')
2022-10-26 08:44:57 +00:00
LOG.debug(f'on_friend_request(pk, message)')
self.on_friend_request(pk, message)
2022-10-23 22:11:12 +00:00
self.callback_friend_request(fr_wrapped)
2022-10-26 08:44:57 +00:00
2022-10-23 22:11:12 +00:00
def fm_wrapped(iTox, peer_id, message, mlen, *args):
message = str(message, 'UTF-8')
2022-10-26 08:44:57 +00:00
LOG.debug(f'on_friend_request(peer_id, message)')
self.on_friend_request(peer_id, message)
2022-10-23 22:11:12 +00:00
self.callback_friend_request(fm_wrapped)
def del_callbacks(self):
self.callback_group_invite(None, 0)
self.callback_self_connection_status(None)
self.callback_group_message(None, 0)
# self.callback_group_action(None, 0)
self.callback_friend_request(None)
self.callback_friend_request(None)
2022-11-03 02:51:14 +00:00
def diagnose_ciphers(self, irc):
cipher_name = irc.get_cipher_name()
2022-11-06 03:57:13 +00:00
LOG.info(f"diagnose_ciphers cipher_name={irc.get_cipher_name()}")
LOG.debug(f"diagnose_ciphers get_cipher_list={irc.get_cipher_list()}")
2022-11-03 02:51:14 +00:00
cipher_list=irc.get_cipher_list()
for ci in lOPENSSL_13_CIPHERS:
2022-11-06 03:57:13 +00:00
if ci in cipher_list: LOG.debug(f"server supports v1.3 cipher {ci}")
for cert in irc.get_peer_cert_chain():
# x509 objects - just want the /CN
LOG.debug(f"{cert.get_subject().CN} {cert.get_issuer()}")
2022-11-03 02:51:14 +00:00
cipher_name = irc.get_cipher_name()
2022-11-17 12:08:38 +00:00
if self._oargs.irc_ssl == 'tlsv1.2':
2022-11-06 03:57:13 +00:00
assert cipher_name in lOPENSSL_12_CIPHERS or \
cipher_name in lOPENSSL_13_CIPHERS, cipher_name
2022-11-17 12:08:38 +00:00
elif self._oargs.irc_ssl == 'tlsv1.3':
2022-11-03 02:51:14 +00:00
assert cipher_name in lOPENSSL_13_CIPHERS, cipher_name
2022-11-06 03:57:13 +00:00
got = irc.get_protocol_version_name().lower()
2022-11-17 12:08:38 +00:00
if got > self._oargs.irc_ssl:
LOG.debug(f"Got: {irc.get_protocol_version_name().lower()} asked for {self._oargs.irc_ssl}")
elif got < self._oargs.irc_ssl:
LOG.warn(f"Got: {irc.get_protocol_version_name().lower()} asked for {self._oargs.irc_ssl}")
2022-11-06 03:57:13 +00:00
LOG.info(f"diagnose_ciphers {str(irc.get_state_string(), 'UTF-8')}")
def irc_init(self):
2022-11-03 02:51:14 +00:00
global iSocks5Error
2022-10-23 22:11:12 +00:00
if not self.bRouted(): return
2022-11-17 12:08:38 +00:00
nick = self._oargs.irc_nick
realname = self._oargs.irc_name
ident = self._oargs.irc_ident
LOG.info(f"irc_init proxy={self._oargs.proxy_type} SSL={self._oargs.irc_ssl}")
2022-10-23 22:11:12 +00:00
try:
2022-11-17 12:08:38 +00:00
if self._oargs.proxy_type == 2:
2022-10-26 08:44:57 +00:00
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,
2022-11-17 12:08:38 +00:00
self._oargs.proxy_host,
self._oargs.proxy_port)
2022-10-26 08:44:57 +00:00
irc = socks.socksocket()
2022-11-02 08:18:52 +00:00
iTIMEOUT = 15
2022-11-17 12:08:38 +00:00
elif self._oargs.proxy_type == 1:
2022-10-26 08:44:57 +00:00
socks.setdefaultproxy(socks.PROXY_TYPE_HTTP,
2022-11-17 12:08:38 +00:00
self._oargs.proxy_host,
self._oargs.proxy_port)
2022-10-26 08:44:57 +00:00
irc = socks.socksocket()
2022-11-02 08:18:52 +00:00
iTIMEOUT = 15
2022-10-26 08:44:57 +00:00
else:
irc = socket.socket()
2022-11-02 08:18:52 +00:00
iTIMEOUT = 10
try:
2022-11-17 12:08:38 +00:00
ip = ts.sDNSLookup(self._oargs.irc_connect)
2022-11-02 08:18:52 +00:00
except Exception as e:
2022-11-17 12:08:38 +00:00
LOG.warn(f"{self._oargs.irc_host} errored in resolve {e}")
ip = self._oargs.irc_connect
2022-11-02 08:18:52 +00:00
else:
if not ip:
2022-11-17 12:08:38 +00:00
LOG.warn(f"{self._oargs.irc_host} did not resolve.")
ip = self._oargs.irc_connect
2022-11-02 08:18:52 +00:00
# https://github.com/pyca/pyopenssl/issues/168
2022-11-17 12:08:38 +00:00
if self._oargs.irc_ssl:
2022-10-26 08:44:57 +00:00
if not self._ssl_context:
2022-11-17 12:08:38 +00:00
self.start_ssl(self._oargs.irc_connect)
2022-10-26 08:44:57 +00:00
irc = SSL.Connection(self._ssl_context, irc)
2022-11-17 12:08:38 +00:00
irc.connect((ip, self._oargs.irc_port))
irc.set_tlsext_host_name(bytes(self._oargs.irc_host, 'UTF-8'))
2022-11-02 08:18:52 +00:00
while True:
try:
irc.do_handshake()
2022-11-03 02:51:14 +00:00
except SSL.WantReadError:
2022-11-02 08:18:52 +00:00
rd,_,_ = select.select([irc], [], [], irc.gettimeout())
if not rd:
raise socket.timeout('timeout')
continue
2022-11-17 12:08:38 +00:00
except SSL.Error as e: # noqa
2022-11-02 08:18:52 +00:00
raise
break
2022-11-03 02:51:14 +00:00
self.diagnose_ciphers(irc)
2022-10-26 08:44:57 +00:00
else:
2022-11-17 12:08:38 +00:00
irc.connect((ip, self._oargs.irc_port))
LOG.info(f"IRC SSL={self._oargs.irc_ssl} connected ")
2022-11-02 08:18:52 +00:00
2022-11-17 12:08:38 +00:00
except (wrapper_tests.socks.GeneralProxyError, wrapper_tests.socks.Socks5Error) as e: # noqa
2022-11-03 02:51:14 +00:00
iSocks5Error += 1
if iSocks5Error >= iSocks5ErrorMax:
raise SyniToxError(f"{e.args}")
2022-11-06 03:57:13 +00:00
if len(e.args[0]) == 2:
if e.args[0][0] == 2:
LOG.warn(f"Socks5Error: do you have Tor SafeSocks set? {e.args[0]}")
elif e.args[0][0] == 5:
# (5, 'Connection refused')
LOG.warn(f"Socks5Error: do you have Tor running? {e.args[0]}")
raise SyniToxError(f"{e.args}")
elif e.args[0][0] in [1, 6, 0]:
# (0, "connection closed unexpectedly")
# (6, 'TTL expired'),
# 1, ('general SOCKS server failure')
# Missing mapping for virtual address '172.17.140.117'. Refusing.
LOG.warn(f"Socks5Error: {e.args[0]}")
return
2022-11-02 08:18:52 +00:00
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
2022-11-03 02:51:14 +00:00
except ( ConnectionRefusedError) as e:
raise SyniToxError(f"{e.args}")
2022-10-26 08:44:57 +00:00
except ( SSL.Error, ) as e:
2022-11-03 02:51:14 +00:00
iSocks5Error += 1
if iSocks5Error >= iSocks5ErrorMax:
raise SyniToxError(f"{e.args}")
2022-10-26 08:44:57 +00:00
LOG.warn(f"SSL error: {e.args}")
return
except (SSL.SysCallError, ) as e:
2022-11-02 08:18:52 +00:00
LOG.warn(f"SSLSyscall error: {e.args}")
LOG.warn(traceback.format_exc())
2022-10-26 08:44:57 +00:00
return
2022-10-23 22:11:12 +00:00
except Exception as e:
2022-10-26 08:44:57 +00:00
LOG.warn(f"Error: {e}")
2022-11-02 08:18:52 +00:00
LOG.warn(traceback.format_exc())
2022-10-26 08:44:57 +00:00
return
2022-10-29 18:44:37 +00:00
self.irc = irc
2022-11-06 03:57:13 +00:00
self.irc.send(bytes('CAP ' + 'LS' + '\r\n', 'UTF-8' ))
self.irc.send(bytes('CAP ' + 'REQ :multi-prefix' + '\r\n', 'UTF-8'))
self.irc.send(bytes('CAP ' + 'END' + '\r\n', 'UTF-8' ))
2022-11-17 12:08:38 +00:00
# withh or without self._oargs.irc_pem:
2022-11-06 03:57:13 +00:00
LOG.info("Sent CAP sending NICK and USER")
self.irc.send(bytes('NICK ' + nick + '\r\n', 'UTF-8' ))
self.irc.send(bytes('USER %s %s bla :%s\r\n' % (
2022-11-17 12:08:38 +00:00
self._oargs.irc_ident,
self._oargs.irc_host,
self._oargs.irc_name), 'UTF-8'))
2022-11-03 05:31:50 +00:00
2022-11-03 02:51:14 +00:00
# OSError: [Errno 9] Bad file descriptor
2022-10-23 22:11:12 +00:00
def dht_init(self):
if not self.bRouted(): return
if 'current_nodes_udp' not in self._settings:
self.test_net()
lNodes = self._settings['current_nodes_udp']
shuffle(lNodes)
2022-11-17 12:08:38 +00:00
if self._oargs.proxy_type == 0:
2022-10-26 08:44:57 +00:00
ts.bootstrap_udp(lNodes[:6], [self])
2022-10-23 22:11:12 +00:00
else:
2022-10-26 08:44:57 +00:00
if self._bRouted is None:
LOG.info(f'UDP bootstapping 1')
ts.bootstrap_udp([lNodes[0]], [self])
2022-10-23 22:11:12 +00:00
if 'current_nodes_tcp' not in self._settings:
self.test_net()
lNodes = self._settings['current_nodes_tcp']
shuffle(lNodes)
2022-11-06 03:57:13 +00:00
LOG.info(f'TCP bootstapping 6')
2022-10-26 08:44:57 +00:00
ts.bootstrap_tcp(lNodes[:6], [self])
def get_all_groups(self):
try:
group_numbers = range(self._tox.group_get_number_groups())
2022-11-17 12:08:38 +00:00
except Exception as e: # noqa
2022-10-26 08:44:57 +00:00
return None
groups = map(lambda n: self.get_group_by_number(n), group_numbers)
return list(groups)
def get_group_by_number(self, group_number):
try:
public_key = self._tox.group_get_chat_id(group_number)
# LOG.info(f"group_get_chat_id {group_number} {public_key}")
return self.get_group_by_public_key(public_key)
except Exception as e:
LOG.warn(f"group_get_chat_id {group_number} {e}")
return None
def get_group_by_public_key(self, public_key, group):
self._groups[public_key] = group
# -----------------------------------------------------------------------------------------------------------------
# Group peers
# -----------------------------------------------------------------------------------------------------------------
def get_all_group_peers(self):
return list()
def get_group_peer_by_public_key(self, group, public_key):
peer = group.get_peer_by_public_key(public_key)
return self._get_group_peer(group, peer)
def get_peer_by_id(self, peer_id):
peers = list(filter(lambda p: p.id == peer_id, self._peers))
if peers:
return peers[0]
else:
LOG_WARN(f"get_peer_by_id empty peers for {peer_id}")
return []
2022-10-23 22:11:12 +00:00
def ensure_exe(self, func, *args):
2013-12-11 15:55:40 +00:00
count = 0
2013-12-11 16:02:01 +00:00
THRESHOLD = 50
2013-12-11 15:55:40 +00:00
while True:
try:
return func(*args)
except:
assert count < THRESHOLD
count += 1
2022-10-23 22:11:12 +00:00
self.do()
2013-12-01 15:09:54 +00:00
2022-10-23 22:11:12 +00:00
def do(self, n=50):
interval = self.iteration_interval()
for i in range(n):
self.iterate()
sleep(interval / 1000.0 *10)
def unroute(self):
if self.irc:
2022-10-26 08:44:57 +00:00
try: self.irc.close()
2022-10-23 22:11:12 +00:00
except: pass
self.irc = None
def irc_check(self, lines):
if b'NOTICE AUTH' in lines[0]:
for line in lines[:99]:
if b'NOTICE AUTH' not in line: return
2022-11-06 03:57:13 +00:00
lines = str(line, 'UTF-8').strip().split()
print(' '.join(lines[1:]))
2022-10-23 22:11:12 +00:00
else:
for line in lines[:5]:
line = str(line, 'UTF-8').strip().lower()
if 'banned' in line:
2022-11-02 08:18:52 +00:00
raise SyniToxError(line)
2022-10-23 22:11:12 +00:00
if 'error' in line and 'closing' in line:
2022-11-02 08:18:52 +00:00
raise SyniToxError(line)
2022-10-26 08:44:57 +00:00
def irc_readlines(self):
2022-11-17 12:08:38 +00:00
nick = self._oargs.irc_nick
pwd = self._oargs.irc_pass
fp = self._oargs.irc_fp
email = self._oargs.irc_email
2022-10-26 08:44:57 +00:00
self.readbuffer += self.irc.recv(4096)
lines = self.readbuffer.split(b'\n')
self.irc_check(lines)
LOG.debug(f'Waited on IRC and got {len(lines)} lines.')
self.readbuffer = lines.pop()
for line in lines:
line = str(line, 'UTF-8')
l = line.rstrip().split()
if len(l) < 2:
print(line)
2022-11-06 03:57:13 +00:00
elif l[1] in ['PING']:
print(line)
elif l[1] in ['372']:
LOG.info('MOTD')
elif l[1] not in ['372', '353']:
2022-10-26 08:44:57 +00:00
i = line.find(' ')
print(line[i+1:])
2022-11-06 03:57:13 +00:00
2022-10-26 08:44:57 +00:00
rx = re.match(r':(.*?)!.*? PRIVMSG %s :(.*?)\r' %
2022-11-17 12:08:38 +00:00
self._oargs.irc_chan, line, re.S)
2022-11-06 03:57:13 +00:00
if l[0] == 'QUIT':
LOG.info('QUIT')
return
if len(l) == 1:
self.irc_send('PING %s\r\n' % '#tor')
2022-10-26 08:44:57 +00:00
elif l[0] == 'PING':
self.irc_send('PONG %s\r\n' % l[1])
2022-11-06 03:57:13 +00:00
elif rx:
self.relay_message(rx)
2022-10-26 08:44:57 +00:00
elif len(l) < 2:
pass
2022-11-02 08:18:52 +00:00
elif l[1] in ['461', '431']:
pass
2022-11-06 03:57:13 +00:00
elif l[1] in ['433']:
# maybe should be an outright fail
2022-11-17 12:08:38 +00:00
if self._oargs.irc_ssl:
2022-11-02 08:18:52 +00:00
LOG.warn("Maybe the certificate was not received")
2022-11-06 03:57:13 +00:00
#? raise SyniToxError(line)
# sometimes but not always:
# 433 * SyniTox :Nickname is already in use.
# app.ts ERROR SSL error: (32, 'EPIPE')
# or instead
# 451 * :Register first.
# error :closing link: 185.38.175.131 (registration timed out)
# or instead: just
# app.ts ERROR SSL error: (32, 'EPIPE')
pass
elif l[1] in ['451', '462', '477']:
2022-11-17 12:08:38 +00:00
if self._oargs.irc_crt and self._oargs.irc_key:
2022-11-06 03:57:13 +00:00
LOG.warn("Maybe the certificate was not received")
2022-11-02 08:18:52 +00:00
raise SyniToxError(line)
elif l[1] in ['376']:
2022-10-26 08:44:57 +00:00
# :End of /MOTD command
2022-11-17 12:08:38 +00:00
if self._oargs.irc_crt and self._oargs.irc_key:
LOG.info(bytes(sMSG+' NickServ IDENTIFY %s %s\r\n'
% (nick, pwd,), 'UTF-8'))
2022-11-02 08:18:52 +00:00
elif email == '' and pwd:
2022-11-06 03:57:13 +00:00
LOG.info(bytes(sMSG+' NickServ IDENTIFY %s %s\r\n'
2022-10-26 08:44:57 +00:00
% (nick, pwd,), 'UTF-8'))
2022-11-06 03:57:13 +00:00
self.irc.send(bytes(sMSG+' NickServ IDENTIFY %s %s\r\n'
% (pwd,nick, ), 'UTF-8'))
2022-11-02 08:18:52 +00:00
elif email != '' and pwd:
2022-11-06 03:57:13 +00:00
LOG.info(bytes(sMSG+' NickServ REGISTER %s %s\r\n'
2022-11-02 08:18:52 +00:00
% (pwd, email,), 'UTF-8'))
2022-11-06 03:57:13 +00:00
self.irc.send(bytes(sMSG+' NickServ REGISTER %s %s\r\n'
2022-10-26 08:44:57 +00:00
% (pwd, email,), 'UTF-8'))
2022-11-02 08:18:52 +00:00
else:
LOG.error("you must provide a password to register")
raise RuntimeError("you must provide a password to register")
2022-11-03 02:51:14 +00:00
try:
2022-11-06 03:57:13 +00:00
self.irc.send(bytes(sMSG+' NickServ set cloak on\r\n', 'UTF-8'))
2022-11-17 12:08:38 +00:00
if self._oargs.irc_chan:
self.irc.send(bytes('JOIN %s\r\n' % self._oargs.irc_chan, 'UTF-8'))
2022-11-03 02:51:14 +00:00
except BrokenPipeError:
raise SyniToxError('BrokenPipeError')
2022-10-26 08:44:57 +00:00
# put off init_groups until you have joined IRC
self.init_groups()
# Make sure we are in
elif l[1] == '042':
# 042 SyniTox 8VQAADOD0 :your unique ID
self._irc_id = line.replace(' :your unique ID',''). \
replace('042 '+nick +' ', '')
elif l[1] == '421':
# 421 SyniTox .PRIVMSG :Unknown command
pass
elif l[1] == '477':
#477 SyniTox #tor :Cannot join channel (Need to be identified and verified to join this channel, '/msg NickServ help' to learn how to register and verify.)
LOG.info(f"PRIVMSG NickServ STATUS {nick}")
i = line.find("'/msg NickServ help'")
if i > 0:
line = line[:i]
raise RuntimeError(line)
def relay_message(self, rx):
print('IRC> %s: %s' % rx.groups())
msg = '[%s]: %s' % rx.groups()
content = rx.group(2)
if self.sGROUP_BOT_NUM >= 0:
if content[1:].startswith('ACTION '):
action = '[%s]: %s' % (rx.group(1),
rx.group(2)[8:-1])
type_ = TOX_MESSAGE_TYPE['ACTION']
self.ensure_exe(self.group_send_message,
self.sGROUP_BOT_NUM, type_, action)
else:
type_ = TOX_MESSAGE_TYPE['NORMAL']
self.ensure_exe(self.group_send_message,
self.sGROUP_BOT_NUM, type_, msg)
if content.startswith('^'):
self.handle_command(content)
2022-11-02 08:18:52 +00:00
def spin(self, n=20, iMax=1000):
2022-10-26 08:44:57 +00:00
readable = False
waiti = 0
while not readable:
waiti += 1
2022-11-02 08:18:52 +00:00
readable, _, _ = select.select([self.irc], [], [], n/100.0 )
if readable and len(readable) and readable[0]: return readable
2022-10-26 08:44:57 +00:00
self.do(n)
2022-11-02 08:18:52 +00:00
if waiti > iMax: break
2022-10-26 08:44:57 +00:00
return readable
2022-10-23 22:11:12 +00:00
def iLoop(self):
2022-10-26 08:44:57 +00:00
group_connected = False
routed = None
2013-12-01 15:09:54 +00:00
self.joined = False
2013-12-11 16:02:01 +00:00
self.request = False
2022-10-26 08:44:57 +00:00
iCount = 0
iDelay = 10
2022-11-17 12:08:38 +00:00
nick = self._oargs.irc_nick
realname = self._oargs.irc_name
ident = self._oargs.irc_ident
pwd = self._oargs.irc_pass
email = self._oargs.irc_email
2022-10-26 08:44:57 +00:00
LOG.info(f"Looping for Tox and IRC connections")
2022-11-17 12:08:38 +00:00
if iCount < self._oargs.max_sleep:
2013-12-01 15:09:54 +00:00
while True:
2022-10-26 08:44:57 +00:00
iCount += 1
# LOG.debug(f"Looping {iCount}")
2022-10-23 22:11:12 +00:00
b = self.bRouted()
if not b:
self.unroute()
2022-10-26 08:44:57 +00:00
group_connected = False
iDelay = iDelay + iDelay // 10
if routed != b:
if iCount % 10 == 1:
LOG.info(f'Not routed {iCount} sleeping {iDelay} seconds')
sleep(iDelay)
2022-10-23 22:11:12 +00:00
continue
2022-10-26 08:44:57 +00:00
elif b != routed or routed is None:
LOG.debug(f'Routed {iCount} - resetting count')
iDelay = 10
routed = b
dht_conneted = self.self_get_connection_status()
if not dht_conneted:
self.dht_init()
2022-11-06 03:57:13 +00:00
LOG.info(f'Not DHT connected {iCount} iterating {iDelay} seconds')
2022-10-29 18:44:37 +00:00
iDelay = iDelay + iDelay // 10
2022-11-02 08:18:52 +00:00
self.do(iDelay)
2022-10-26 08:44:57 +00:00
#drop through
if not group_connected and dht_conneted:
2022-10-23 22:11:12 +00:00
LOG.info('Connected to DHT.')
2022-10-26 08:44:57 +00:00
group_connected = True
2013-12-11 16:02:01 +00:00
try:
2022-10-26 08:44:57 +00:00
#? self.bid = self.friend_by_public_key(self.sGROUP_BOT_PK)
r = self.group_reconnect(self.sGROUP_BOT_NUM)
LOG.info(f'Connected to group {r}')
2022-11-17 12:08:38 +00:00
except ctypes.ArgumentError as e: # noqa
2022-10-23 22:11:12 +00:00
self.bid = None
2022-10-26 08:44:57 +00:00
2022-10-23 22:11:12 +00:00
if self.bid == None:
self.ensure_exe(self.friend_add_norequest, self.sGROUP_BOT_PK)
LOG.info(f'friend_add_n to group {self.sGROUP_BOT_PK[:8]}')
self.bid = self.friend_by_public_key(self.sGROUP_BOT_PK)
LOG.info(f'Added to group {self.bid}')
2022-10-26 08:44:57 +00:00
num = self.sGROUP_BOT_NUM
2022-10-23 22:11:12 +00:00
my_pk = self.group_self_get_public_key(num)
LOG.info(f'Connected to group as {my_pk[:8]}')
2022-10-26 08:44:57 +00:00
if group_connected and not dht_conneted:
2022-10-23 22:11:12 +00:00
LOG.info('Disconnected from DHT.')
self.dht_init()
2022-10-26 08:44:57 +00:00
group_connected = False
2022-10-23 22:11:12 +00:00
if not self.irc:
2022-10-26 08:44:57 +00:00
self.irc_init()
2022-10-23 22:11:12 +00:00
if not self.irc:
2022-10-26 08:44:57 +00:00
self.do(20)
2022-10-23 22:11:12 +00:00
continue
2022-10-26 08:44:57 +00:00
2022-11-02 08:18:52 +00:00
2022-11-17 12:08:38 +00:00
LOG.info(f'Waiting on IRC to {self._oargs.irc_host} on {self._oargs.irc_port}')
2022-10-23 22:11:12 +00:00
2022-10-26 08:44:57 +00:00
readable = self.spin(20)
2022-11-02 08:18:52 +00:00
if not readable or not readable[0]:
2022-10-23 22:11:12 +00:00
LOG.info('Waited on IRC but nothing to read.')
2022-10-29 18:44:37 +00:00
iDelay = iDelay + iDelay // 10
2022-10-26 08:44:57 +00:00
continue
try:
2022-11-02 08:18:52 +00:00
pass
2022-10-26 08:44:57 +00:00
except Exception as e:
2022-11-02 08:18:52 +00:00
if len(e.args) > 1 and e.args[0] == 32:
raise
elif f"{e}" != "2":
LOG.warn(f'IRC Error during read: {e}')
# close irc?
try:
self.irc.close()
self.irc = None
except: pass
continue
else:
iDelay = 10
else:
iDelay = 10
2022-10-29 18:44:37 +00:00
2022-11-02 08:18:52 +00:00
self.irc_readlines()
2022-11-06 03:57:13 +00:00
self.do(iDelay)
2022-10-26 08:44:57 +00:00
return 0
2013-12-01 15:24:33 +00:00
2022-10-23 22:11:12 +00:00
def quit(self):
self.del_callbacks()
self.save_to_file()
def save_to_file(self):
pass
2013-12-01 15:09:54 +00:00
def irc_send(self, msg):
success = False
while not success:
try:
2022-10-23 22:11:12 +00:00
self.irc.send(bytes(msg, 'UTF-8'))
success = True
break
except socket.error:
sleep(1)
2013-12-11 15:55:40 +00:00
def on_connection_status(self, friendId, status):
2022-10-26 08:44:57 +00:00
# scs_wrapped
2013-12-11 16:02:01 +00:00
if not self.request and not self.joined \
and friendId == self.bid and status:
2022-10-26 08:44:57 +00:00
LOG.info('Groupbot online, trying to get invited to group chat.')
2013-12-11 16:02:01 +00:00
self.request = True
2022-10-26 08:44:57 +00:00
type_ = TOX_MESSAGE_TYPE['NORMAL']
# the bot is sending a message to myself self.bid
self.ensure_exe(self.friend_send_message, self.bid, type_, 'invite')
2022-10-26 08:44:57 +00:00
# gi_wrapped
2022-10-23 22:11:12 +00:00
def on_group_invite(self, friendid, invite_data, user_data):
2013-12-01 15:09:54 +00:00
if not self.joined:
self.joined = True
2022-11-17 12:08:38 +00:00
nick = self._oargs.group_nick
2022-10-26 08:44:57 +00:00
self.tox_group_id = self.group_invite_accept(invite_data, friendid, nick)
2022-10-23 22:11:12 +00:00
LOG.info('Joined groupchat.')
2013-12-01 15:09:54 +00:00
2022-10-26 08:44:57 +00:00
def group_peername(self, groupnumber, peer_id):
#dunno
return ''
2022-10-23 22:11:12 +00:00
def on_group_message(self, groupnumber, peer_id, message):
name = self.group_peername(groupnumber, peer_id)
2014-04-06 00:12:34 +00:00
if len(name) and name != NAME:
2013-12-01 15:09:54 +00:00
print('TOX> %s: %s' % (name, message))
2014-02-20 18:28:35 +00:00
if message.startswith('>'):
2014-02-20 18:34:59 +00:00
message = '\x0309%s\x03' % message
2022-11-17 12:08:38 +00:00
self.irc_send(sMSG+' %s :[%s]: %s\r\n' %
(self._oargs.irc_chan, name, message))
if message.startswith('^'):
2014-02-20 11:04:54 +00:00
self.handle_command(message)
2013-12-01 15:09:54 +00:00
2022-10-23 22:11:12 +00:00
def on_group_action(self, groupnumber, peer_id, action):
"""old? message type action?"""
name = self.group_peername(groupnumber, peer_id)
2022-10-26 08:44:57 +00:00
if name and name != NAME:
2013-12-22 16:33:15 +00:00
print('TOX> %s: %s' % (name, action))
2014-02-20 18:28:35 +00:00
if action.startswith('>'):
2014-02-20 18:34:59 +00:00
action = '\x0309%s\x03' % action
2022-11-17 12:08:38 +00:00
self.irc_send(bytes(sMSG' %s :\x01ACTION [%s]: %s\x01\r\n' %
(self._oargs.irc_chan, name, action), 'UTF-8'))
2013-12-22 16:33:15 +00:00
def on_friend_request(self, pk, message):
2022-10-23 22:11:12 +00:00
LOG.info('Friend request from %s: %s' % (pk, message))
2022-10-26 08:44:57 +00:00
self.friend_add_norequest(pk)
2022-10-23 22:11:12 +00:00
LOG.info('Accepted.')
def on_friend_message(self, friendid, message):
2022-10-26 08:44:57 +00:00
if message.startswith('invite'):
2014-03-13 19:33:34 +00:00
if not self.tox_group_id is None:
2022-10-26 08:44:57 +00:00
LOG.info('Inviting %s' % self.friend_get_name(friendid))
self.group_invite_friend(self.sGROUP_BOT_NUM, friendid)
2014-03-13 19:33:34 +00:00
return
else:
message = 'Waiting for GroupBot, please try again in 1 min.'
2022-10-26 08:44:57 +00:00
type_ = TOX_MESSAGE_TYPE['NORMAL']
self.ensure_exe(self.friend_send_message, friendid, type_, message)
2014-02-20 11:27:49 +00:00
def send_both(self, content):
2022-10-26 08:44:57 +00:00
type_ = TOX_MESSAGE_TYPE['NORMAL']
self.ensure_exe(self.group_send_message, self.sGROUP_BOT_NUM, type_, content)
2022-11-17 12:08:38 +00:00
self.irc_send(bytes(sMSG+' %s :%s\r\n' % (self._oargs.irc_chan, content), 'UTF-8'))
2014-02-20 11:27:49 +00:00
2014-02-20 11:02:07 +00:00
def handle_command(self, cmd):
2014-02-20 11:27:49 +00:00
cmd = cmd[1:]
if cmd in ['syncbot', 'echobot']:
2022-10-26 08:44:57 +00:00
self.send_both(self.self_get_address())
2014-03-13 19:33:34 +00:00
elif cmd == 'resync':
sys.exit(0)
2014-02-20 11:27:49 +00:00
elif cmd.startswith('remember '):
args = cmd[9:].split(' ')
subject = args[0]
desc = ' '.join(args[1:])
self.memory[subject] = desc
2022-10-23 22:11:12 +00:00
if self.sMEMORY_DB:
with open(self.sMEMORY_DB, 'w') as f:
pickle.dump(self.memory, f)
2014-02-20 11:27:49 +00:00
self.send_both('Remembering ^%s: %s' % (subject, desc))
elif self.memory.has_key(cmd):
self.send_both(self.memory[cmd])
2022-10-26 08:44:57 +00:00
def is_data_encrypted(self, data):
return len(data) > 0 and self._toxes.is_data_encrypted(data)
def pass_encrypt(self, data):
2022-11-17 12:08:38 +00:00
return self._toxes.pass_encrypt(data, self._oargs.password)
2022-10-26 08:44:57 +00:00
def has_password(self):
2022-11-17 12:08:38 +00:00
return self._oargs.password
2022-10-26 08:44:57 +00:00
def pass_decrypt(self, data):
2022-11-17 12:08:38 +00:00
return self._toxes.pass_decrypt(data, self._oargs.password)
2022-10-26 08:44:57 +00:00
2014-02-20 11:02:07 +00:00
2022-10-26 08:44:57 +00:00
def iMain(oArgs, oOpts):
2022-10-23 22:11:12 +00:00
assert oTOX_OPTIONS
assert oTOX_OARGS
2022-10-26 08:44:57 +00:00
try:
o = SyniTox(oArgs, oOpts)
__builtins__.app = o
o.start()
ret = o.iLoop()
2022-10-29 18:44:37 +00:00
o.quit()
2022-10-26 08:44:57 +00:00
except KeyboardInterrupt:
ret = 0
except ( SSL.Error, ) as e:
LOG.error(f"SSL error: {e.args}")
ret = 1
2022-11-17 12:08:38 +00:00
except (SSL.SysCallError,) as e:
2022-10-26 08:44:57 +00:00
# OpenSSL.SSL.SysCallError: (9, 'EBADF')
LOG.error(f"SSL error: {e.args}")
ret = 1
2022-11-02 08:18:52 +00:00
except SyniToxError as e:
LOG.error(f'Error running program:\n{e}')
ret = 2
2022-10-26 08:44:57 +00:00
except Exception as e:
LOG.exception(f'Error running program:\n{e}')
2022-11-02 08:18:52 +00:00
ret = 3
2022-10-26 08:44:57 +00:00
else:
ret = 0
2022-10-23 22:11:12 +00:00
return ret
def oToxygenToxOptions(oArgs):
tox_options = wrapper.tox.Tox.options_new()
if oArgs.proxy_type:
tox_options.contents.proxy_type = int(oArgs.proxy_type)
tox_options.contents.proxy_host = bytes(oArgs.proxy_host, 'UTF-8')
tox_options.contents.proxy_port = int(oArgs.proxy_port)
tox_options.contents.udp_enabled = False
else:
tox_options.contents.udp_enabled = oArgs.udp_enabled
if not os.path.exists('/proc/sys/net/ipv6'):
oArgs.ipv6_enabled = False
tox_options.contents.tcp_port = int(oArgs.tcp_port)
# overrides
tox_options.contents.local_discovery_enabled = False
tox_options.contents.dht_announcements_enabled = True
tox_options.contents.hole_punching_enabled = False
tox_options.contents.experimental_thread_safety = False
# REQUIRED!!
if oArgs.ipv6_enabled and not os.path.exists('/proc/sys/net/ipv6'):
LOG.warn('Disabling IPV6 because /proc/sys/net/ipv6 does not exist' + repr(oArgs.ipv6_enabled))
tox_options.contents.ipv6_enabled = False
else:
tox_options.contents.ipv6_enabled = bool(oArgs.ipv6_enabled)
#? tox_options.contents.log_callback = LOG
2022-10-26 08:44:57 +00:00
if oArgs.trace_enabled and tox_options._options_pointer:
2022-10-23 22:11:12 +00:00
# LOG.debug("Adding logging to tox_options._options_pointer ")
ts.vAddLoggerCallback(tox_options, ts.on_log)
else:
LOG.warn("No tox_options._options_pointer " +repr(tox_options._options_pointer))
return tox_options
2022-11-17 12:08:38 +00:00
def vInitializeOargs():
global oTOX_OARGS
assert oTOX_OARGS.irc_host or oTOX_OARGS.irc_connect
if not oTOX_OARGS.irc_connect:
oTOX_OARGS.irc_connect = oTOX_OARGS.irc_host
if oTOX_OARGS.irc_cadir:
assert os.path.isdir(oTOX_OARGS.irc_cadir)
if oTOX_OARGS.irc_cafile:
assert os.path.isfile(oTOX_OARGS.irc_cafile)
if oTOX_OARGS.irc_crt:
assert os.path.isfile(oTOX_OARGS.irc_crt)
assert oTOX_OARGS.irc_key
if oTOX_OARGS.irc_key:
assert os.path.isfile(oTOX_OARGS.irc_key)
assert oTOX_OARGS.irc_crt
if not oTOX_OARGS.group_name:
group_name = oTOX_OARGS.bot_name +oTOX_OARGS.irc_chan
oTOX_OARGS.group_name = group_name
2022-10-23 22:11:12 +00:00
def oArgparse(lArgv):
parser = ts.oMainArgparser()
parser.add_argument('profile', type=str, nargs='?', default=None,
2022-10-26 08:44:57 +00:00
help='Path to Tox profile - new groups will be saved there')
CAcs = []
for elt in lCAs:
if os.path.exists(elt):
CAcs.append(elt)
2022-11-03 02:51:14 +00:00
CAfs = []
for elt in lCAfs:
if os.path.exists(elt):
CAfs.append(elt)
2022-10-26 08:44:57 +00:00
2022-10-29 18:44:37 +00:00
parser.add_argument('--log_level', type=int, default=10)
2022-10-26 08:44:57 +00:00
parser.add_argument('--bot_name', type=str, default=bot_toxname)
parser.add_argument('--max_sleep', type=int, default=3600,
help="max time to sleep waiting for routing before exiting")
parser.add_argument('--password', type=str, default='',
help="password for the profile if encrypted")
# parser.add_argument('--irc_type', type=str, default='',
# choices=['', 'startls', 'direct')
# does host == connect ?
2022-10-29 18:44:37 +00:00
# oftcnet6xg6roj6d7id4y4cu6dchysacqj2ldgea73qzdagufflqxrid.onion:6697
2022-11-02 08:18:52 +00:00
# irc.oftc.net
parser.add_argument('--irc_host', type=str, default='',
2022-10-26 08:44:57 +00:00
help="irc.libera.chat will not work over Tor")
2022-11-02 08:18:52 +00:00
parser.add_argument('--irc_connect', type=str, default='',
help="defaults to irc_host")
2022-10-26 08:44:57 +00:00
parser.add_argument('--irc_port', type=int, default=6667,
help="default 6667, but may be 6697 with SSL")
parser.add_argument('--irc_chan', type=str, default='#tor',
help="IRC channel to join - include the #")
#
parser.add_argument('--irc_ssl', type=str, default='',
help="TLS version; empty is no SSL",
2022-11-03 02:51:14 +00:00
choices=['', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'])
parser.add_argument('--irc_cafile', type=str,
2022-11-17 12:08:38 +00:00
help="Certificate Authority file (in PEM)",
2022-11-03 02:51:14 +00:00
default=CAfs[0])
parser.add_argument('--irc_cadir', type=str,
help="Certificate Authority directory",
2022-10-26 08:44:57 +00:00
default=CAcs[0])
2022-11-06 03:57:13 +00:00
parser.add_argument('--irc_crt', type=str, default='',
help="Certificate as pem; use openssl req -x509 -nodes -newkey rsa:2048")
parser.add_argument('--irc_key', type=str, default='',
help="Key as pem; use openssl req -x509 -nodes -newkey rsa:2048")
2022-10-26 08:44:57 +00:00
parser.add_argument('--irc_fp', type=str, default='',
help="fingerprint of the pem added with CERT ADD; use openssl x509 -noout -fingerprint -SHA1 -text")
parser.add_argument('--irc_nick', type=str, default='',
help="IRC Nickname")
parser.add_argument('--irc_name', type=str, default='',
help="Third field in USER")
parser.add_argument('--irc_ident', type=str, default='',
help="First field in USER")
parser.add_argument('--irc_pass', type=str, default='',
help="password for INDENTIFY or REGISTER")
parser.add_argument('--irc_email', type=str, default='',
help="Use email to REGISTER with _pass")
#
parser.add_argument('--group_pass', type=str, default='',
help="password for the group - optional")
parser.add_argument('--group_state', type=str, default='public',
choices=['public','private'],
help="state for the group - default public")
parser.add_argument('--group_chatid', type=str, default='',
2022-10-26 09:13:01 +00:00
help="chat_id of the group - leave empty and will be created on first use")
2022-10-26 08:44:57 +00:00
parser.add_argument('--group_name', type=str, default='',
help="name for the group")
parser.add_argument('--group_nick', type=str, default='',
help="Nickname of the group founder")
parser.add_argument('--group_invite', type=str, default='',
help="A PK to invite to the group")
parser.add_argument('--group_moderator', type=str, default='',
help="A PK to invite to the group as moderator")
parser.add_argument('--group_ignore', type=str, default='',
help="A PK to ignore by the group")
2022-10-23 22:11:12 +00:00
oArgs = parser.parse_args(lArgv)
for key in ts.lBOOLEANS:
if key not in oArgs: continue
val = getattr(oArgs, key)
setattr(oArgs, key, bool(val))
if hasattr(oArgs, 'sleep'):
if oArgs.sleep == 'qt':
pass # broken or gevent.sleep(idle_period)
elif oArgs.sleep == 'gevent':
pass # broken or gevent.sleep(idle_period)
else:
oArgs.sleep = 'time'
return oArgs
def main(lArgs=None):
2022-10-26 08:44:57 +00:00
2022-10-23 22:11:12 +00:00
if lArgs is None: lArgs = []
2022-10-26 08:44:57 +00:00
global oTOX_OARGS
oTOX_OARGS = oArgparse(lArgs)
2022-11-06 03:57:13 +00:00
ts.clean_booleans(oTOX_OARGS)
2022-11-17 12:08:38 +00:00
vInitializeOargs()
2022-11-06 03:57:13 +00:00
2022-10-23 22:11:12 +00:00
global oTOX_OPTIONS
2022-10-26 08:44:57 +00:00
oTOX_OPTIONS = oToxygenToxOptions(oTOX_OARGS)
2022-11-06 03:57:13 +00:00
2022-10-26 08:44:57 +00:00
ts.vSetupLogging(oTOX_OARGS)
2022-10-23 22:11:12 +00:00
# ts.setup_logging(oArgs)
2022-10-26 08:44:57 +00:00
return iMain(oTOX_OARGS, oTOX_OPTIONS)
2022-10-23 22:11:12 +00:00
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
# Ran 34 tests in 86.589s OK (skipped=12)