diff --git a/.gitignore b/.gitignore
index 3595135..ebef324 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ __pycache__
.noseids
nosetests.xml
tests/report
-tests/tmp.db
\ No newline at end of file
+tests/tmp.db
+logs/
\ No newline at end of file
diff --git a/bumper/__init__.py b/bumper/__init__.py
index 8501819..6e53947 100644
--- a/bumper/__init__.py
+++ b/bumper/__init__.py
@@ -5,21 +5,18 @@ from .mqttserver import MQTTServer
from .mqttserver import MQTTHelperBot
from .xmppserver import XMPPServer
import asyncio
-import contextvars
import json
import time
from datetime import datetime, timedelta
import platform
-import os
+import os, sys
import logging
+from logging.handlers import RotatingFileHandler
from base64 import b64decode, b64encode
from tinydb import TinyDB, Query
+import json
from tinydb.storages import MemoryStorage
-bumper_users_var = contextvars.ContextVar("bumper_users", default=[])
-bumper_clients_var = contextvars.ContextVar("bumper_clients", default=[])
-bumper_bots_var = contextvars.ContextVar("bumper_bots", default=[])
-
ca_cert = "./certs/CA/cacert.pem"
server_cert = "./certs/cert.pem"
server_key = "./certs/key.pem"
@@ -29,17 +26,42 @@ token_validity_seconds = 3600 # 1 hour
db = None
# Logs
+os.makedirs("logs", exist_ok=True) #Ensure logs directory exists or create
+# Set format for all logs
+logformat = logging.Formatter("[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s")
+
bumperlog = logging.getLogger("bumper")
+bumper_rotate = RotatingFileHandler("logs/bumper.log", maxBytes=5000000, backupCount=5)
+bumper_rotate.setFormatter(logformat)
+bumperlog.addHandler(bumper_rotate)
+# Override the logging level
+# bumperlog.setLevel(logging.INFO)
+
confserverlog = logging.getLogger("confserver")
+conf_rotate = RotatingFileHandler("logs/confserver.log", maxBytes=5000000, backupCount=5)
+conf_rotate.setFormatter(logformat)
+confserverlog.addHandler(conf_rotate)
# Override the logging level
# confserverlog.setLevel(logging.INFO)
+
mqttserverlog = logging.getLogger("mqttserver")
+mqtt_rotate = RotatingFileHandler("logs/mqttserver.log", maxBytes=5000000, backupCount=5)
+mqtt_rotate.setFormatter(logformat)
+mqttserverlog.addHandler(mqtt_rotate)
# Override the logging level
# mqttserverlog.setLevel(logging.INFO)
+
helperbotlog = logging.getLogger("helperbot")
+helperbot_rotate = RotatingFileHandler("logs/helperbot.log", maxBytes=5000000, backupCount=5)
+helperbot_rotate.setFormatter(logformat)
+helperbotlog.addHandler(helperbot_rotate)
# Override the logging level
# helperbotlog.setLevel(logging.INFO)
+
xmppserverlog = logging.getLogger("xmppserver")
+xmpp_rotate = RotatingFileHandler("logs/xmppserver.log", maxBytes=5000000, backupCount=5)
+xmpp_rotate.setFormatter(logformat)
+xmppserverlog.addHandler(xmpp_rotate)
# Override the logging level
# xmppserverlog.setLevel(logging.INFO)
@@ -56,23 +78,32 @@ def db_file():
def os_db_path():
- if platform.system() == "Windows":
- return os.path.join(os.getenv("APPDATA"), "bumper.db")
+ if platform.system() == "Windows":
+ os.makedirs(os.getenv("APPDATA"), exist_ok=True) #Ensure db_path directory exists or create
+ return os.path.join(os.getenv("APPDATA"), "bumper.db")
else:
+ os.makedirs(os.path.expanduser("~/.config"), exist_ok=True) #Ensure db_path directory exists or create
return os.path.expanduser("~/.config/bumper.db")
-
def db_get():
- # Will create the database if it doesn't exist
- db = TinyDB(db_file())
+ try:
+ # Will create the database if it doesn't exist
+ db = TinyDB(db_file())
- # Will create the tables if they don't exist
- db.table("users", cache_size=0)
- db.table("clients", cache_size=0)
- db.table("bots", cache_size=0)
- db.table("tokens", cache_size=0)
+ # Will create the tables if they don't exist
+ db.table("users", cache_size=0)
+ db.table("clients", cache_size=0)
+ db.table("bots", cache_size=0)
+ db.table("tokens", cache_size=0)
- return db
+ return db
+
+
+ except json.decoder.JSONDecodeError as jerr:
+ bumperlog.error("JsonErr: {} - Doc: {}".format(jerr.msg, jerr.doc))
+
+ except Exception as ex:
+ bumperlog.error(ex)
class BumperUser(object):
diff --git a/bumper/confserver.py b/bumper/confserver.py
index beb25da..07c8590 100644
--- a/bumper/confserver.py
+++ b/bumper/confserver.py
@@ -8,7 +8,6 @@ import bumper
import time
from datetime import datetime, timedelta
import asyncio
-import contextvars
from aiohttp import web
import uuid
import xml.etree.ElementTree as ET
@@ -33,7 +32,7 @@ class aiohttp_filter(logging.Filter):
confserverlog = logging.getLogger("confserver")
-logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger
+#logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger
logging.getLogger("aiohttp.access").addFilter(aiohttp_filter())
class EcoVacs_Login:
diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py
index 115e71d..0458787 100644
--- a/bumper/mqttserver.py
+++ b/bumper/mqttserver.py
@@ -8,7 +8,6 @@ from hbmqtt.broker import Broker
from hbmqtt.client import MQTTClient
from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
import pkg_resources
-import contextvars
import time
from threading import Thread
import ssl
@@ -40,7 +39,7 @@ class MQTTHelperBot:
):
self.address = address
self.client_id = "helper1@bumper/helper1"
- self.command_responses = contextvars.ContextVar("command_responses", default=[])
+ self.command_responses = []
self.helperthread = None
def run(self, run_async=False):
@@ -73,6 +72,10 @@ class MQTTHelperBot:
async def start_helper_bot(self):
try:
+ self.Client = MQTTClient(
+ client_id=self.client_id, config={"check_hostname": False}
+ )
+
await self.Client.connect(
"mqtts://{}:{}/".format(self.address[0], self.address[1]),
cafile=bumper.ca_cert,
@@ -81,9 +84,12 @@ class MQTTHelperBot:
[
("iot/p2p/+/+/+/+/helper1/bumper/helper1/+/+/+", QOS_0),
("iot/p2p/+", QOS_0),
+ ("iot/atr/+", QOS_0),
]
)
+ asyncio.ensure_future(self.get_msg())
+
except Exception as e:
helperbotlog.exception("{}".format(e))
@@ -92,28 +98,34 @@ class MQTTHelperBot:
while True:
message = await self.Client.deliver_message()
- # helperbotlog.debug("HelperBot MQTT Received Message on Topic: {} - Message: {}".format(message.topic, str(message.payload.decode("utf-8"))))
- cresp = self.command_responses.get()
-
if str(message.topic).split("/")[6] == "helper1":
- cresp.append(
+ #Response to command
+ helperbotlog.debug("Received Response - Topic: {} - Message: {}".format(message.topic, str(message.data.decode("utf-8"))))
+ self.command_responses.append(
{
"time": time.time(),
"topic": message.topic,
"payload": str(message.data.decode("utf-8")),
}
)
+ elif str(message.topic).split("/")[3] == "helper1":
+ #Helperbot sending command
+ helperbotlog.debug("Send Command - Topic: {} - Message: {}".format(message.topic, str(message.data.decode("utf-8"))))
+ elif str(message.topic).split("/")[1] == "atr":
+ #Broadcast message received on atr
+ helperbotlog.debug("Received Broadcast - Topic: {} - Message: {}".format(message.topic, str(message.data.decode("utf-8"))))
+ else:
+ helperbotlog.debug("Received Message - Topic: {} - Message: {}".format(message.topic, str(message.data.decode("utf-8"))))
# Cleanup "expired messages" > 60 seconds from time
- for msg in cresp:
+ for msg in self.command_responses:
expire_time = (
datetime.fromtimestamp(msg["time"]) + timedelta(seconds=10)
).timestamp()
if time.time() > expire_time:
- # helperbotlog.debug("Pruning Message Time: {}, MsgTime: {}, MsgTime+60: {}".format(time.time(), msg['time'], expire_time))
- cresp.remove(msg)
+ helperbotlog.debug("Pruning Message Time: {}, MsgTime: {}, MsgTime+60: {}".format(time.time(), msg['time'], expire_time))
+ self.command_responses.remove(msg)
- self.command_responses.set(cresp)
# helperbotlog.debug("MQTT Command Response List Count: %s" %len(cresp))
except Exception as e:
@@ -125,10 +137,9 @@ class MQTTHelperBot:
t_end = (datetime.now() + timedelta(seconds=10)).timestamp()
while time.time() < t_end:
- await asyncio.sleep(0.1)
- responses = self.command_responses.get()
- if len(responses) > 0:
- for msg in responses:
+ await asyncio.sleep(0.1)
+ if len(self.command_responses) > 0:
+ for msg in self.command_responses:
topic = str(msg["topic"]).split("/")
if topic[6] == "helper1" and topic[10] == requestid:
# helperbotlog.debug('VacBot MQTT Response: Topic: %s Payload: %s' % (msg['topic'], msg['payload']))
@@ -136,17 +147,17 @@ class MQTTHelperBot:
resppayload = json.loads(msg["payload"])
else:
resppayload = str(msg["payload"])
- resp = {"id": requestid, "ret": "ok", "resp": resppayload}
- cresp = self.command_responses.get()
- cresp.remove(msg)
- self.command_responses.set(cresp)
+ resp = {"id": requestid, "ret": "ok", "resp": resppayload}
+ self.command_responses.remove(msg)
return resp
return {"id": requestid, "errno": "timeout", "ret": "fail"}
except asyncio.CancelledError as e:
helperbotlog.debug("wait_for_resp cancelled by asyncio")
+ return {"id": requestid, "errno": "timeout", "ret": "fail"}
except Exception as e:
helperbotlog.exception("{}".format(e))
+ return {"id": requestid, "errno": "timeout", "ret": "fail"}
async def send_command(self, cmdjson, requestid):
try:
@@ -171,6 +182,7 @@ class MQTTHelperBot:
except Exception as e:
helperbotlog.exception("{}".format(e))
+ return {}
class MQTTServer:
diff --git a/bumper/xmpp_old_client.py b/bumper/xmpp_old_client.py
new file mode 100644
index 0000000..ea5b24b
--- /dev/null
+++ b/bumper/xmpp_old_client.py
@@ -0,0 +1,769 @@
+class Client(threading.Thread):
+ IDLE = 0
+ CONNECT = 1
+ INIT = 2
+ BIND = 3
+ READY = 4
+ DISCONNECT = 5
+ UNKNOWN = 0
+ BOT = 1
+ CONTROLLER = 2
+
+ def __init__(self, thread_id, connection, client_address):
+ threading.Thread.__init__(self)
+ self.id = thread_id
+ self.name = "XMPP_Client_{}".format(client_address[0])
+ self.type = self.UNKNOWN
+ self.state = self.IDLE
+ self.connection = connection
+ self.address = client_address[0]
+ self.clientresource = ""
+ self.devclass = ""
+ self.bumper_jid = ""
+ self.uid = ""
+ self.log_sent_message = False # Set to true to log sends
+ self.log_incoming_data = True # Set to true to log sends
+
+ xmppserverlog.debug(
+ "new client thread init for client with ip {}".format(self.address)
+ )
+
+ def send(self, command):
+ try:
+ if not self.connection._closed:
+ if self.log_sent_message:
+ xmppserverlog.debug("send {} - {}".format(self.address, command))
+ self.connection.send(command.encode())
+
+ except BrokenPipeError as e:
+ xmppserverlog.debug("{}".format(e))
+ self._set_state("DISCONNECT")
+
+ except ConnectionResetError as e:
+ xmppserverlog.debug("{}".format(e))
+ self._set_state("DISCONNECT")
+
+ except ConnectionAbortedError as e:
+ xmppserverlog.debug("{}".format(e))
+ self._set_state("DISCONNECT")
+
+ except OSError as e:
+ xmppserverlog.debug("{}".format(e))
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _disconnect(self):
+ try:
+
+ bot = bumper.bot_get(self.uid)
+ if bot:
+ bumper.bot_set_xmpp(bot["did"], False)
+
+ client = bumper.client_get(self.clientresource)
+ if client:
+ bumper.client_set_xmpp(client["resource"], False)
+
+ self.connection.close()
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _tag_strip_uri(self, tag):
+ try:
+ if tag[0] == "{":
+ _, _, tag = tag[1:].partition("}")
+ return tag
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _set_state(self, state):
+ try:
+ new_state = getattr(Client, state)
+ if self.state > new_state:
+ raise Exception(
+ "{} illegal state change {}->{}".format(
+ self.address, self.state, new_state
+ )
+ )
+
+ xmppserverlog.debug("{} state: {}".format(self.address, state))
+
+ self.state = new_state
+
+ if new_state == 5:
+ self._disconnect()
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _handle_ctl(self, xml, data):
+ try:
+
+ if "roster" in data:
+ # Return not-implemented for roster
+ self.send(
+ ''.format(
+ xml.get("id")
+ )
+ )
+ return
+
+ if xml.get("type") == "set":
+ if (
+ "com:sf" in data and xml.get("to") == "rl.ecorobot.net"
+ ): # Android bind? Not sure what this does yet.
+ self.send(
+ ''.format(
+ xml.get("id"),
+ self.uid,
+ XMPPServer.server_id,
+ self.clientresource,
+ )
+ )
+
+ if xml[0][0]:
+ ctl = xml[0][0]
+ if ctl.get("admin") and self.type == self.BOT:
+ xmppserverlog.debug(
+ "admin username received from bot: {}".format(ctl.get("admin"))
+ )
+ XMPPServer.client_id = ctl.get("admin")
+ return
+
+ # forward
+ for client in XMPPServer.clients:
+ if (
+ client.bumper_jid != self.bumper_jid
+ and client.state == client.READY
+ ):
+ ctl_to = xml.get("to")
+ xml.attrib["from"] = "{}".format(self.bumper_jid)
+ rxmlstring = ET.tostring(xml).decode("utf-8")
+ # clean up string to remove namespaces added by ET
+ rxmlstring = rxmlstring.replace("xmlns:ns0=", "xmlns=")
+ rxmlstring = rxmlstring.replace("ns0:", "")
+ rxmlstring = rxmlstring.replace('iq xmlns="com:ctl"', "iq")
+ rxmlstring = rxmlstring.replace("'.format(
+ uuid.uuid4(), adminuser, self.bumper_jid, newuser
+ )
+ xmppserverlog.debug("Add User: {}".format(adduser))
+ self.send(adduser)
+
+ # Add user ACs - Manage users, settings, and clean (full access)
+ adduseracs = ''.format(
+ uuid.uuid4(), adminuser, self.bumper_jid, newuser
+ )
+ xmppserverlog.debug("Add User ACs: {}".format(adduseracs))
+ self.send(adduseracs)
+
+ # GetUserInfo - Just to confirm it set correctly
+ self.send(
+ ''.format(
+ uuid.uuid4(), adminuser, self.bumper_jid
+ )
+ )
+
+ else:
+ rxmlstring = ET.tostring(xml).decode("utf-8")
+ # clean up string to remove namespaces added by ET
+ rxmlstring = rxmlstring.replace("xmlns:ns0=", "xmlns=")
+ rxmlstring = rxmlstring.replace("ns0:", "")
+ rxmlstring = rxmlstring.replace('iq xmlns="com:ctl"', "iq")
+ rxmlstring = rxmlstring.replace(" -1:
+ sc = data.decode("utf-8").find("to=")
+ ec = data.decode("utf-8").find(".ecorobot.net")
+ if ec > -1:
+ self.devclass = data.decode("utf-8")[sc + 4 : ec]
+ # ack jabbr:client
+ # no STARTTLS
+ self.send(
+ ''.format(
+ XMPPServer.server_id
+ )
+ )
+ # with STARTTLS
+ # self.send(''.format(XMPPServer.server_id))
+ time.sleep(0.25)
+ # send authentication support for iq-auth (fallback) and SASL
+ self.send(
+ 'PLAIN'
+ )
+ # self.send('')
+
+ else:
+ self.send("")
+
+ else:
+ if "jabber:iq:auth" in xml.tag: # Handle iq-auth
+ self._handle_iq_auth(xml)
+ elif (
+ "urn:ietf:params:xml:ns:xmpp-sasl" in xml.tag
+ ): # Handle SASL Auth
+ self._handle_sasl_auth(xml)
+ else:
+ xmppserverlog.error("Couldn't handle: {}".format(xml))
+
+ elif self.state == self.INIT:
+ if xml == None:
+ # Client getting session after authentication
+ if data.decode("utf-8").find("jabber:client") > -1:
+ # ack jabbr:client
+ self.send(
+ ''.format(
+ XMPPServer.server_id
+ )
+ )
+ time.sleep(0.25)
+ # session
+ self.send(
+ ''
+ )
+
+ else: # Handle init bind
+ if len(xml):
+ child = self._tag_strip_uri(xml[0].tag)
+ else:
+ child = None
+
+ if xml.tag == "iq":
+ if child == "bind":
+ self._handle_bind(xml)
+ else:
+ xmppserverlog.error("Couldn't handle: {}".format(xml))
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _handle_iq_auth(self, data):
+ try:
+ xml = ET.fromstring(data.decode("utf-8"))
+ ctl = xml[0][0]
+ xmppserverlog.info("IQ AUTH XML: {}".format(xml))
+ # Received username and auth tag, send username/password requirement
+ if (
+ xml.get("type") == "get"
+ and "auth}username" in ctl.tag
+ and self.type == self.UNKNOWN
+ ):
+ self.send(
+ ''.format(
+ xml.get("id")
+ )
+ )
+
+ # Received username, password, resource - Handle auth here and return pass or fail
+ if (
+ xml.get("type") == "set"
+ and "auth}username" in ctl.tag
+ and self.type == self.UNKNOWN
+ ):
+ xmlauth = xml[0].getchildren()
+ # uid = ""
+ password = ""
+ resource = ""
+ for aitem in xmlauth:
+ if "username" in aitem.tag:
+ self.uid = aitem.text
+
+ elif "password" in aitem.tag:
+ password = aitem.text.split("/")[2]
+ authcode = password
+
+ elif "resource" in aitem.tag:
+ self.clientresource = aitem.text
+ resource = self.clientresource
+
+ if not self.uid.startswith("fuid"):
+
+ # Need sample data to see details here
+ bumper.bot_add("", self.uid, "", resource, "eco-legacy")
+ xmppserverlog.info("bot authenticated {}".format(self.uid))
+
+ # Client authenticated, move to next state
+ self._set_state("INIT")
+
+ # Successful auth
+ self.send(''.format(xml.get("id")))
+
+ else:
+ auth = False
+ if bumper.check_authcode(self.uid, authcode):
+ auth = True
+ elif bumper.use_auth == False:
+ auth = True
+
+ if auth:
+ bumper.client_add(self.uid, "bumper", self.clientresource)
+ xmppserverlog.debug("client authenticated {}".format(self.uid))
+
+ # Client authenticated, move to next state
+ self._set_state("INIT")
+
+ # Successful auth
+ self.send(''.format(xml.get("id")))
+
+ else:
+ # Failed auth
+ self.send(
+ ''.format(
+ xml.get("id")
+ )
+ )
+
+ except ET.ParseError as e:
+ if "no element found" in e.msg:
+ xmppserverlog.debug(
+ "xml parse error - {} - {}".format(data.decode("utf-8"), e)
+ )
+ elif "not well-formed (invalid token)" in e.msg:
+ xmppserverlog.debug(
+ "xml parse error - {} - {}".format(data.decode("utf-8"), e)
+ )
+ else:
+ xmppserverlog.debug(
+ "xml parse error - {} - {}".format(data.decode("utf-8"), e)
+ )
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _handle_sasl_auth(self, xml):
+ try:
+
+ saslauth = base64.b64decode(xml.text).decode("utf-8").split("/")
+ username = saslauth[0]
+ username = saslauth[0].split("\x00")[1]
+ self.uid = username
+ if len(saslauth) > 1:
+ resource = saslauth[1]
+ self.clientresource = resource
+ elif len(saslauth[0].split("\x00")) > 2:
+ resource = saslauth[0].split("\x00")[2]
+ self.clientresource = resource
+
+ if len(saslauth) > 2:
+ authcode = saslauth[2]
+
+ if not self.uid.startswith("fuid"):
+ # Need sample data to see details here
+ bumper.bot_add(self.uid, self.uid, self.devclass, "atom", "eco-legacy")
+ self.type = self.BOT
+ xmppserverlog.info("bot authenticated {}".format(self.uid))
+ # Send response
+ self.send(
+ ''
+ ) # Success
+
+ # Client authenticated, move to next state
+ self._set_state("INIT")
+
+ else:
+ auth = False
+ if bumper.check_authcode(self.uid, authcode):
+ auth = True
+ elif bumper.use_auth == False:
+ auth = True
+
+ if auth:
+ self.type = self.CONTROLLER
+ bumper.client_add(self.uid, "bumper", self.clientresource)
+ xmppserverlog.debug("client authenticated {}".format(self.uid))
+
+ # Client authenticated, move to next state
+ self._set_state("INIT")
+
+ # Send response
+ self.send(
+ ''
+ ) # Success
+
+ else:
+ # Failed to authenticate
+ self.send(
+ ''
+ ) # Fail
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _handle_bind(self, xml):
+ try:
+
+ bot = bumper.bot_get(self.uid)
+ if bot:
+ bumper.bot_set_xmpp(bot["did"], True)
+
+ client = bumper.client_get(self.clientresource)
+ if client:
+ bumper.client_set_xmpp(client["resource"], True)
+
+ clientbindxml = xml.getchildren()
+ clientresourcexml = clientbindxml[0].getchildren()
+ if self.devclass: # its a bot
+ self.name = "XMPP_Client_{}_{}".format(self.uid, self.devclass)
+ self.bumper_jid = "{}@{}.ecorobot.net/atom".format(
+ self.uid, self.devclass
+ )
+ xmppserverlog.debug("new bot {}".format(self.uid))
+ res = '{}'.format(
+ xml.get("id"), self.bumper_jid
+ )
+ elif len(clientresourcexml) > 0:
+ self.clientresource = clientresourcexml[0].text
+ self.name = "XMPP_Client_{}".format(self.clientresource)
+ self.bumper_jid = "{}@{}/{}".format(
+ self.uid, XMPPServer.server_id, self.clientresource
+ )
+ xmppserverlog.debug(
+ "new client {} using resource {}".format(
+ self.uid, self.clientresource
+ )
+ )
+ res = '{}'.format(
+ xml.get("id"), self.bumper_jid
+ )
+ else:
+ self.name = "XMPP_Client_{}_{}".format(self.uid, self.address)
+ self.bumper_jid = "{}@{}".format(self.uid, XMPPServer.server_id)
+ xmppserverlog.debug("new client {}".format(self.uid))
+ res = '{}'.format(
+ xml.get("id"), self.bumper_jid
+ )
+
+ self._set_state("BIND")
+ self.send(res)
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _handle_session(self, xml):
+ try:
+ res = ''.format(xml.get("id"))
+ self._set_state("READY")
+ self.send(res)
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _handle_presence(self, xml):
+ try:
+
+ if len(xml) and xml[0].tag == "status":
+ xmppserverlog.debug(
+ "bot presence {} ".format(ET.tostring(xml, encoding="utf-8"))
+ )
+ # Most likely a bot, possibly hello world in text
+
+ # Send dummy return
+ self.send(
+ ' dummy '.format(self.bumper_jid)
+ )
+
+ # If it is a BOT, send extras
+ if self.type == self.BOT:
+ # get device info
+ self.send(
+ ''.format(
+ self.bumper_jid, XMPPServer.server_id
+ )
+ )
+
+ else:
+ xmppserverlog.debug(
+ "client presence - {} ".format(ET.tostring(xml, encoding="utf-8"))
+ )
+
+ if xml.get("type") == "available":
+ xmppserverlog.debug(
+ "client presence available - {} ".format(
+ ET.tostring(xml, encoding="utf-8")
+ )
+ )
+ # Send dummy return
+ self.send(
+ ' dummy '.format(self.bumper_jid)
+ )
+ elif xml.get("type") == "unavailable":
+ xmppserverlog.debug(
+ "client presence unavailable (DISCONNECT) - {} ".format(
+ ET.tostring(xml, encoding="utf-8")
+ )
+ )
+
+ self._set_state("DISCONNECT")
+ else:
+ # Sometimes the android app sends these
+ xmppserverlog.debug(
+ "client presence (UNKNOWN) - {} ".format(
+ ET.tostring(xml, encoding="utf-8")
+ )
+ )
+ # Send dummy return
+ self.send(
+ ' dummy '.format(self.bumper_jid)
+ )
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _parse_data(self, data):
+
+ if data.decode("utf-8").startswith(
+ "]+\?>)", r"", data.decode("utf-8")) + ""
+ )
+
+ else:
+ newdata = "{}".format(
+ data.decode("utf-8")
+ ) # Add artificial root
+
+ try:
+ root = ET.fromstring(newdata)
+ for item in root.iter():
+ if item.tag != "root":
+ if item.tag == "iq":
+ if self.log_incoming_data:
+ xmppserverlog.debug(
+ "from {} - {}".format(
+ self.address,
+ str(
+ ET.tostring(item, encoding="utf-8").decode(
+ "utf-8"
+ )
+ ).replace("ns0:", ""),
+ )
+ )
+ self._handle_iq(item, newdata)
+ item.clear()
+
+ elif "auth" in item.tag:
+ if "urn:ietf:params:xml:ns:xmpp-sasl" in item.tag: # SASL Auth
+ self._handle_sasl_auth(item)
+ item.clear()
+
+ elif "presence" in item.tag:
+ self._handle_presence(item)
+ item.clear()
+
+ else:
+ if self.log_incoming_data:
+ xmppserverlog.debug(
+ "Unparsed Item - {}".format(
+ str(
+ ET.tostring(item, encoding="utf-8").decode(
+ "utf-8"
+ )
+ ).replace("ns0:", "")
+ )
+ )
+
+ except ET.ParseError as e:
+ if (
+ "no element found" in e.msg
+ ): # Element not closed or not all bytes received
+ # Happens wth connect stream often
+ if " - client is signalling end of session/disconnect
+ if not "" in newdata:
+ xmppserverlog.error("xml parse error - {} - {}".format(newdata, e))
+ else:
+ self.send("") # Close stream
+
+ else:
+ if "" in newdata:
+ xmppserverlog.error(
+ "xml parse error - {} - {}".format(newdata, e)
+ )
+ else:
+ self.send("") # Close stream
+ self._set_state("DISCONNECT")
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def _handle_iq(self, xml, data):
+ try:
+ if len(xml):
+ child = self._tag_strip_uri(xml[0].tag)
+ else:
+ child = None
+
+ if xml.tag == "iq":
+ if child == "bind":
+ self._handle_bind(xml)
+ elif child == "session":
+ self._handle_session(xml)
+ elif child == "ping":
+ self._handle_ping(xml, data)
+ elif child == "query":
+ if self.type == self.BOT:
+ self._handle_result(xml, data)
+ else:
+ self._handle_ctl(xml, data)
+ elif xml.get("type") == "result":
+ if self.type == self.BOT:
+ self._handle_result(xml, data)
+ else:
+ self._handle_result(xml, data)
+ elif xml.get("type") == "set":
+ if self.type == self.BOT:
+ self._handle_result(xml, data)
+ else:
+ self._handle_result(xml, data)
+
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
+
+ def run(self):
+ # xmppserverlog.info('client connected - {}'.format(self.address))
+ await self._set_state("CONNECT")
+ while not self.state == self.DISCONNECT and not self.connection._closed:
+ data = b""
+ time.sleep(0.1)
+ if not self.connection._closed:
+ try:
+ data = self.connection.recv(4096)
+ if data != b"":
+ self._parse_data(data)
+ except ConnectionResetError as e:
+ xmppserverlog.debug("{}".format(e))
+ except OSError as e:
+ xmppserverlog.debug("{}".format(e))
+ except Exception as e:
+ xmppserverlog.exception("{}".format(e))
diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py
index e044790..5ad8e22 100644
--- a/bumper/xmppserver.py
+++ b/bumper/xmppserver.py
@@ -4,8 +4,8 @@ from threading import Thread
import sys, socket, threading, re, time, logging, uuid, xml.etree.ElementTree as ET
import base64
import ssl
-import contextvars
import bumper
+import asyncio
xmppserverlog = logging.getLogger("xmppserver")
@@ -19,88 +19,116 @@ class XMPPServer:
def __init__(self, address):
# Initialize bot server
self.address = address
+ self.aclients = {} # task -> (reader, writer)
- def run(self, run_async=False):
- if run_async:
- xmppserverlog.debug("Starting XMPPServer Thread: 1")
- self.xmppthread = Thread(name="XMPPServer_Thread", target=self.run_server)
- self.xmppthread.setDaemon(True)
- self.xmppthread.start()
-
- else:
- try:
- self.run_server()
- except KeyboardInterrupt:
- self.disconnect()
-
- def run_server(self):
- logging.info("Starting XMPP Server at {}".format(self.address))
- print("Starting XMPP Server at {}".format(self.address))
-
- # xmppserverlog.setLevel(logging.DEBUG)
-
- # Set SSL Context
- self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
- self.ssl_ctx.load_cert_chain(
- certfile=bumper.server_cert, keyfile=bumper.server_key
+ async def async_server(self):
+ xmppserverlog.debug(
+ "listening on {}:{}".format(self.address[0], self.address[1])
+ )
+ server = await asyncio.start_server(
+ self.accept_client, self.address[0], self.address[1]
)
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ await server.serve_forever()
- try:
- self.socket.bind(self.address)
- self.socket.listen(5)
+ # self.clients = {} # task -> (reader, writer)
- xmppserverlog.debug(
- "listening on {}:{}".format(self.address[0], self.address[1])
- )
- while not self.exit_flag:
- connection, client_address = self.socket.accept()
+ def accept_client(self, client_reader, client_writer):
+ # task = asyncio.Task(self.handle_client(client_reader, client_writer))
+ aclient = XMPPAsyncClient(client_reader, client_writer)
+ task = asyncio.Task(aclient.handle_async_client())
+ self.aclients[task] = (client_reader, client_writer)
- # disconnect any clients with this ip
- for client in self.clients:
- if client.address == client_address[0]:
- xmppserverlog.debug(
- "disconnecting existing client {} with resource {}".format(
- client.address, client.clientresource
- )
- )
- client._disconnect()
- self.remove_client_byip(client.address)
+ def client_done(task):
+ del self.aclients[task]
+ xmppserverlog.info("End Connection for {}".format(client_writer.get_extra_info("peername")))
+ client_writer.close()
- xmppserverlog.debug(
- "starting new client with ip {}".format(client_address[0])
- )
- thread_id = uuid.uuid4()
- client = Client(thread_id, connection, client_address)
- client.setDaemon(True)
- client.start()
- self.clients.append(client)
+ clientaddr = client_writer.get_extra_info("peername")
+ xmppserverlog.info("New Connection from {}".format(clientaddr))
+ task.add_done_callback(client_done)
- except PermissionError as e:
- if "bind" in e.strerror:
- xmppserverlog.exception(
- "Error binding XMPPServer, exiting. Try using a different hostname or IP - {}".format(
- e
- )
- )
- exit(1)
+ # def run(self, run_async=False):
+ # if run_async:
+ # xmppserverlog.debug("Starting XMPPServer Thread: 1")
+ # self.xmppthread = Thread(name="XMPPServer_Thread", target=self.run_server)
+ # self.xmppthread.setDaemon(True)
+ # self.xmppthread.start()
- except Exception as e:
- xmppserverlog.exception("{}".format(e))
- exit(1)
+ # else:
+ # try:
+ # self.run_server()
+ # except KeyboardInterrupt:
+ # self.disconnect()
- except KeyboardInterrupt as e:
- xmppserverlog.exception("{}".format(e))
+ # def run_server(self):
+ # logging.info("Starting XMPP Server at {}".format(self.address))
+ # print("Starting XMPP Server at {}".format(self.address))
- finally:
- connection.shutdown(socket.SHUT_RDWR)
- connection.close()
- self.disconnect()
- xmppserverlog.info("disconnecting")
+ # # xmppserverlog.setLevel(logging.DEBUG)
- self.socket.close()
+ # # Set SSL Context
+ # self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ # self.ssl_ctx.load_cert_chain(
+ # certfile=bumper.server_cert, keyfile=bumper.server_key
+ # )
+
+ # self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ # self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ # try:
+ # self.socket.bind(self.address)
+ # self.socket.listen(5)
+
+ # xmppserverlog.debug(
+ # "listening on {}:{}".format(self.address[0], self.address[1])
+ # )
+ # while not self.exit_flag:
+ # connection, client_address = self.socket.accept()
+
+ # # disconnect any clients with this ip
+ # for client in self.clients:
+ # if client.address == client_address[0]:
+ # xmppserverlog.debug(
+ # "disconnecting existing client {} with resource {}".format(
+ # client.address, client.clientresource
+ # )
+ # )
+ # client._disconnect()
+ # self.remove_client_byip(client.address)
+
+ # xmppserverlog.debug(
+ # "starting new client with ip {}".format(client_address[0])
+ # )
+ # thread_id = uuid.uuid4()
+ # client = XMPPAsyncClient(thread_id, connection, client_address)
+ # client.setDaemon(True)
+ # client.start()
+ # self.clients.append(client)
+
+ # except PermissionError as e:
+ # if "bind" in e.strerror:
+ # xmppserverlog.exception(
+ # "Error binding XMPPServer, exiting. Try using a different hostname or IP - {}".format(
+ # e
+ # )
+ # )
+ # exit(1)
+
+ # except Exception as e:
+ # xmppserverlog.exception("{}".format(e))
+ # exit(1)
+
+ # except KeyboardInterrupt as e:
+ # xmppserverlog.exception("{}".format(e))
+
+ # finally:
+ # connection.shutdown(socket.SHUT_RDWR)
+ # connection.close()
+ # self.disconnect()
+ # xmppserverlog.info("disconnecting")
+
+ # self.socket.close()
def disconnect(self):
try:
@@ -148,7 +176,7 @@ class XMPPServer:
self.clients.remove(client)
-class Client(threading.Thread):
+class XMPPAsyncClient:
IDLE = 0
CONNECT = 1
INIT = 2
@@ -159,43 +187,63 @@ class Client(threading.Thread):
BOT = 1
CONTROLLER = 2
- def __init__(self, thread_id, connection, client_address):
- threading.Thread.__init__(self)
- self.id = thread_id
- self.name = "XMPP_Client_{}".format(client_address[0])
+ def __init__(self, client_reader, client_writer):
self.type = self.UNKNOWN
self.state = self.IDLE
- self.connection = connection
- self.address = client_address[0]
+ self.address = client_writer.get_extra_info("peername")
+ self.client_reader = client_reader
+ self.client_writer = client_writer
self.clientresource = ""
self.devclass = ""
self.bumper_jid = ""
self.uid = ""
- self.log_sent_message = False # Set to true to log sends
+ self.log_sent_message = True # Set to true to log sends
self.log_incoming_data = True # Set to true to log sends
- xmppserverlog.debug(
- "new client thread init for client with ip {}".format(self.address)
- )
+ xmppserverlog.debug("new client with ip {}".format(self.address))
- def send(self, command):
+ async def handle_async_client(self):
+ # xmppserverlog.info('client connected - {}'.format(self.address))
+ #await self._set_state("READY")
+ await self._set_state("CONNECT")
+ #asyncio.Task(self.send_ping(30))
+ while True:
+ await asyncio.sleep(0.1)
+ if not self.state == self.DISCONNECT:
+ data = await self.client_reader.read(4096)
+ # data = await asyncio.wait_for(client_reader.readline(), timeout=10.0)
+ if data is None:
+ xmppserverlog.warning("Received no data")
+ # exit loop and disconnect
+ return
+
+ await self._parse_data(data)
+ else:
+ break
+
+ # exit loop and disconnect
+ return
+
+ async def send(self, command):
try:
- if not self.connection._closed:
- if self.log_sent_message:
- xmppserverlog.debug("send {} - {}".format(self.address, command))
- self.connection.send(command.encode())
+ # if not self.connection._closed:
+ if self.log_sent_message:
+ xmppserverlog.debug("send {} - {}".format(self.address, command))
+ # self.connection.send(command.encode())
+ self.client_writer.write(command.encode())
+ await self.client_writer.drain()
except BrokenPipeError as e:
xmppserverlog.debug("{}".format(e))
- self._set_state("DISCONNECT")
+ await self._set_state("DISCONNECT")
except ConnectionResetError as e:
xmppserverlog.debug("{}".format(e))
- self._set_state("DISCONNECT")
+ await self._set_state("DISCONNECT")
except ConnectionAbortedError as e:
xmppserverlog.debug("{}".format(e))
- self._set_state("DISCONNECT")
+ await self._set_state("DISCONNECT")
except OSError as e:
xmppserverlog.debug("{}".format(e))
@@ -203,7 +251,7 @@ class Client(threading.Thread):
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _disconnect(self):
+ async def _disconnect(self):
try:
bot = bumper.bot_get(self.uid)
@@ -214,12 +262,12 @@ class Client(threading.Thread):
if client:
bumper.client_set_xmpp(client["resource"], False)
- self.connection.close()
+ self.client_writer.close()
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _tag_strip_uri(self, tag):
+ async def _tag_strip_uri(self, tag):
try:
if tag[0] == "{":
_, _, tag = tag[1:].partition("}")
@@ -228,9 +276,9 @@ class Client(threading.Thread):
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _set_state(self, state):
+ async def _set_state(self, state):
try:
- new_state = getattr(Client, state)
+ new_state = getattr(XMPPAsyncClient, state)
if self.state > new_state:
raise Exception(
"{} illegal state change {}->{}".format(
@@ -243,28 +291,46 @@ class Client(threading.Thread):
self.state = new_state
if new_state == 5:
- self._disconnect()
+ await self._disconnect()
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_ctl(self, xml, data):
+ async def _handle_ctl(self, xml, data):
try:
if "roster" in data:
# Return not-implemented for roster
- self.send(
+ await self.send(
''.format(
xml.get("id")
)
)
return
+
+ if "disco#items" in data:
+ # Return not-implemented for disco#items
+ await self.send(
+ ''.format(
+ xml.get("id")
+ ))
+ return
+
+ if "disco#info" in data:
+ # Return not-implemented for disco#info
+ await self.send(
+ ''.format(
+ xml.get("id")
+ )
+ )
+ return
+
if xml.get("type") == "set":
if (
"com:sf" in data and xml.get("to") == "rl.ecorobot.net"
): # Android bind? Not sure what this does yet.
- self.send(
+ await self.send(
''.format(
xml.get("id"),
self.uid,
@@ -273,7 +339,7 @@ class Client(threading.Thread):
)
)
- if xml[0][0]:
+ if len(xml[0]) > 0:
ctl = xml[0][0]
if ctl.get("admin") and self.type == self.BOT:
xmppserverlog.debug(
@@ -307,7 +373,7 @@ class Client(threading.Thread):
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_ping(self, xml, data):
+ async def _handle_ping(self, xml, data):
try:
if xml.get("to").find("@") == -1: # No to address
# Ping to server - respond
@@ -315,7 +381,7 @@ class Client(threading.Thread):
xml.get("id"), xml.get("to")
)
# xmppserverlog.debug("Server Ping resp: {}".format(pingresp))
- self.send(pingresp)
+ await self.send(pingresp)
else:
pingto = xml.get("to")
@@ -346,7 +412,14 @@ class Client(threading.Thread):
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_result(self, xml, data):
+ async def send_ping(self, time):
+ if not self.state == 5: #disconnected
+ pingstring = "".format(XMPPServer.server_id, self.bumper_jid)
+ await self.send(pingstring)
+ await asyncio.sleep(time)
+ asyncio.Task(self.send_ping(time))
+
+ async def _handle_result(self, xml, data):
try:
ctl_to = xml.get("to")
xml.attrib["from"] = self.bumper_jid
@@ -370,17 +443,17 @@ class Client(threading.Thread):
uuid.uuid4(), adminuser, self.bumper_jid, newuser
)
xmppserverlog.debug("Add User: {}".format(adduser))
- self.send(adduser)
+ await self.send(adduser)
# Add user ACs - Manage users, settings, and clean (full access)
adduseracs = ''.format(
uuid.uuid4(), adminuser, self.bumper_jid, newuser
)
xmppserverlog.debug("Add User ACs: {}".format(adduseracs))
- self.send(adduseracs)
+ await self.send(adduseracs)
# GetUserInfo - Just to confirm it set correctly
- self.send(
+ await self.send(
''.format(
uuid.uuid4(), adminuser, self.bumper_jid
)
@@ -430,7 +503,7 @@ class Client(threading.Thread):
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_connect(self, data, xml=None):
+ async def _handle_connect(self, data, xml=None):
try:
if self.state == self.CONNECT:
@@ -443,30 +516,32 @@ class Client(threading.Thread):
self.devclass = data.decode("utf-8")[sc + 4 : ec]
# ack jabbr:client
# no STARTTLS
- self.send(
+ await self.send(
''.format(
XMPPServer.server_id
)
)
# with STARTTLS
- # self.send(''.format(XMPPServer.server_id))
- time.sleep(0.25)
+ # await self.send(''.format(XMPPServer.server_id))
+
+ await asyncio.sleep(0.25)
+ #time.sleep(0.25)
# send authentication support for iq-auth (fallback) and SASL
- self.send(
+ await self.send(
'PLAIN'
)
- # self.send('')
+ # await self.send('')
else:
- self.send("")
+ await self.send("")
else:
if "jabber:iq:auth" in xml.tag: # Handle iq-auth
- self._handle_iq_auth(xml)
+ await self._handle_iq_auth(xml)
elif (
"urn:ietf:params:xml:ns:xmpp-sasl" in xml.tag
): # Handle SASL Auth
- self._handle_sasl_auth(xml)
+ await self._handle_sasl_auth(xml)
else:
xmppserverlog.error("Couldn't handle: {}".format(xml))
@@ -475,33 +550,34 @@ class Client(threading.Thread):
# Client getting session after authentication
if data.decode("utf-8").find("jabber:client") > -1:
# ack jabbr:client
- self.send(
+ await self.send(
''.format(
XMPPServer.server_id
)
)
- time.sleep(0.25)
+ await asyncio.sleep(0.25)
+ #time.sleep(0.25)
# session
- self.send(
+ await self.send(
''
)
else: # Handle init bind
if len(xml):
- child = self._tag_strip_uri(xml[0].tag)
+ child = await self._tag_strip_uri(xml[0].tag)
else:
child = None
if xml.tag == "iq":
if child == "bind":
- self._handle_bind(xml)
+ await self._handle_bind(xml)
else:
xmppserverlog.error("Couldn't handle: {}".format(xml))
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_iq_auth(self, data):
+ async def _handle_iq_auth(self, data):
try:
xml = ET.fromstring(data.decode("utf-8"))
ctl = xml[0][0]
@@ -512,7 +588,7 @@ class Client(threading.Thread):
and "auth}username" in ctl.tag
and self.type == self.UNKNOWN
):
- self.send(
+ await self.send(
''.format(
xml.get("id")
)
@@ -527,6 +603,7 @@ class Client(threading.Thread):
xmlauth = xml[0].getchildren()
# uid = ""
password = ""
+ authcode = ""
resource = ""
for aitem in xmlauth:
if "username" in aitem.tag:
@@ -547,10 +624,10 @@ class Client(threading.Thread):
xmppserverlog.info("bot authenticated {}".format(self.uid))
# Client authenticated, move to next state
- self._set_state("INIT")
+ await self._set_state("INIT")
# Successful auth
- self.send(''.format(xml.get("id")))
+ await self.send(''.format(xml.get("id")))
else:
auth = False
@@ -564,14 +641,16 @@ class Client(threading.Thread):
xmppserverlog.debug("client authenticated {}".format(self.uid))
# Client authenticated, move to next state
- self._set_state("INIT")
+ await self._set_state("INIT")
# Successful auth
- self.send(''.format(xml.get("id")))
+ await self.send(
+ ''.format(xml.get("id"))
+ )
else:
# Failed auth
- self.send(
+ await self.send(
''.format(
xml.get("id")
)
@@ -594,12 +673,13 @@ class Client(threading.Thread):
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_sasl_auth(self, xml):
+ async def _handle_sasl_auth(self, xml):
try:
saslauth = base64.b64decode(xml.text).decode("utf-8").split("/")
username = saslauth[0]
username = saslauth[0].split("\x00")[1]
+ authcode = ""
self.uid = username
if len(saslauth) > 1:
resource = saslauth[1]
@@ -617,12 +697,12 @@ class Client(threading.Thread):
self.type = self.BOT
xmppserverlog.info("bot authenticated {}".format(self.uid))
# Send response
- self.send(
+ await self.send(
''
) # Success
# Client authenticated, move to next state
- self._set_state("INIT")
+ await self._set_state("INIT")
else:
auth = False
@@ -637,23 +717,23 @@ class Client(threading.Thread):
xmppserverlog.debug("client authenticated {}".format(self.uid))
# Client authenticated, move to next state
- self._set_state("INIT")
+ await self._set_state("INIT")
# Send response
- self.send(
+ await self.send(
''
) # Success
else:
# Failed to authenticate
- self.send(
+ await self.send(
''
) # Fail
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_bind(self, xml):
+ async def _handle_bind(self, xml):
try:
bot = bumper.bot_get(self.uid)
@@ -697,22 +777,24 @@ class Client(threading.Thread):
xml.get("id"), self.bumper_jid
)
- self._set_state("BIND")
- self.send(res)
+ await self._set_state("BIND")
+ await self.send(res)
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_session(self, xml):
+ async def _handle_session(self, xml):
try:
res = ''.format(xml.get("id"))
- self._set_state("READY")
- self.send(res)
+ await self._set_state("READY")
+ await self.send(res)
+ asyncio.Task(self.send_ping(30))
+
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_presence(self, xml):
+ async def _handle_presence(self, xml):
try:
if len(xml) and xml[0].tag == "status":
@@ -722,24 +804,28 @@ class Client(threading.Thread):
# Most likely a bot, possibly hello world in text
# Send dummy return
- self.send(
+ await self.send(
' dummy '.format(self.bumper_jid)
)
+
+
# If it is a BOT, send extras
if self.type == self.BOT:
# get device info
- self.send(
+ await self.send(
''.format(
self.bumper_jid, XMPPServer.server_id
)
)
+
+
else:
xmppserverlog.debug(
"client presence - {} ".format(ET.tostring(xml, encoding="utf-8"))
)
-
+
if xml.get("type") == "available":
xmppserverlog.debug(
"client presence available - {} ".format(
@@ -747,7 +833,7 @@ class Client(threading.Thread):
)
)
# Send dummy return
- self.send(
+ await self.send(
' dummy '.format(self.bumper_jid)
)
elif xml.get("type") == "unavailable":
@@ -757,7 +843,7 @@ class Client(threading.Thread):
)
)
- self._set_state("DISCONNECT")
+ await self._set_state("DISCONNECT")
else:
# Sometimes the android app sends these
xmppserverlog.debug(
@@ -766,14 +852,14 @@ class Client(threading.Thread):
)
)
# Send dummy return
- self.send(
+ await self.send(
' dummy '.format(self.bumper_jid)
)
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _parse_data(self, data):
+ async def _parse_data(self, data):
if data.decode("utf-8").startswith(
"" in newdata:
xmppserverlog.error("xml parse error - {} - {}".format(newdata, e))
else:
- self.send("") # Close stream
+ await self.send("") # Close stream
else:
if "" in newdata:
xmppserverlog.error(
"xml parse error - {} - {}".format(newdata, e)
)
else:
- self.send("") # Close stream
- self._set_state("DISCONNECT")
+ await self.send("") # Close stream
+ await self._set_state("DISCONNECT")
except Exception as e:
xmppserverlog.exception("{}".format(e))
- def _handle_iq(self, xml, data):
+ async def _handle_iq(self, xml, data):
try:
if len(xml):
- child = self._tag_strip_uri(xml[0].tag)
+ child = await self._tag_strip_uri(xml[0].tag)
else:
child = None
if xml.tag == "iq":
if child == "bind":
- self._handle_bind(xml)
+ await self._handle_bind(xml)
elif child == "session":
- self._handle_session(xml)
+ await self._handle_session(xml)
elif child == "ping":
- self._handle_ping(xml, data)
+ await self._handle_ping(xml, data)
elif child == "query":
if self.type == self.BOT:
- self._handle_result(xml, data)
+ await self._handle_result(xml, data)
else:
- self._handle_ctl(xml, data)
+ await self._handle_ctl(xml, data)
elif xml.get("type") == "result":
if self.type == self.BOT:
- self._handle_result(xml, data)
+ await self._handle_result(xml, data)
else:
- self._handle_result(xml, data)
+ await self._handle_result(xml, data)
elif xml.get("type") == "set":
if self.type == self.BOT:
- self._handle_result(xml, data)
+ await self._handle_result(xml, data)
else:
- self._handle_result(xml, data)
+ await self._handle_result(xml, data)
except Exception as e:
xmppserverlog.exception("{}".format(e))
-
- def run(self):
- # xmppserverlog.info('client connected - {}'.format(self.address))
- self._set_state("CONNECT")
- while not self.state == self.DISCONNECT and not self.connection._closed:
- data = b""
- time.sleep(0.1)
- if not self.connection._closed:
- try:
- data = self.connection.recv(4096)
- if data != b"":
- self._parse_data(data)
- except ConnectionResetError as e:
- xmppserverlog.debug("{}".format(e))
- except OSError as e:
- xmppserverlog.debug("{}".format(e))
- except Exception as e:
- xmppserverlog.exception("{}".format(e))
-
diff --git a/start_bumper.py b/start_bumper.py
index 1975e80..2503f44 100644
--- a/start_bumper.py
+++ b/start_bumper.py
@@ -5,10 +5,14 @@ import bumper
import sys, socket
import time
import platform
+import os
+os.environ['PYTHONASYNCIODEBUG'] = '1'
+import asyncio
def main():
args = sys.argv
+ listen_host = ""
if len(args) > 0:
if "--debug" in args:
@@ -23,16 +27,14 @@ def main():
)
# format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s")
- listen_host = args.index("--listen")
- if (len(args) - 1) >= (listen_host + 1):
- listen_host = args[listen_host+1]
- else:
+ if "--listen" in args:
+ listen_host = args[args.index("--listen") + 1]
+
+ if listen_host == "":
if platform.system() == "Darwin": # If a Mac, use 0.0.0.0 for listening
listen_host = "0.0.0.0"
else:
- listen_host = socket.gethostbyname(socket.gethostname())
- #listen_host = "localhost" # Try this if the above doesn't work
-
+ listen_host = socket.gethostbyname(socket.gethostname())
conf_address_443 = (listen_host, 443)
conf_address_8007 = (listen_host, 8007)
@@ -51,30 +53,46 @@ def main():
conf_address_8007, usessl=False, helperbot=mqtt_helperbot
)
- # add user
- # users = bumper.bumper_users_var.get()
- # user1 = bumper.BumperUser('user1')
- # user1.add_device('devid')
- # user1.add_bot('bot_did')
- # users.append(user1)
- # bumper.bumper_users_var.set(users)
+ try:
+ loop = asyncio.get_event_loop()
+ except:
+ loop = asyncio.new_event_loop()
+
+ # Start web servers
+ loop.set_debug(True)
+ conf_server.confserver_app()
+ conf_server_2.confserver_app()
+ asyncio.ensure_future(conf_server.start_server(),loop=loop)
+ asyncio.ensure_future(conf_server_2.start_server(),loop=loop)
+
+ # Start MQTT Server
+ asyncio.ensure_future(mqtt_server.broker_coro())
+
+ # Start MQTT Helperbot
+ asyncio.ensure_future(mqtt_helperbot.start_helper_bot())
+
+ # Start XMPP Server
+ asyncio.ensure_future(xmpp_server.async_server())
+
+ loop.run_forever()
+
# start xmpp server on port 5223 (sync)
- xmpp_server.run(run_async=True) # Start in new thread
+ #xmpp_server.run(run_async=True) # Start in new thread
# start mqtt server on port 8883 (async)
- mqtt_server.run(run_async=True) # Start in new thread
+ #mqtt_server.run(run_async=True) # Start in new thread
- time.sleep(1.5) # Wait for broker startup
+ #time.sleep(1.5) # Wait for broker startup
# start mqtt_helperbot (async)
- mqtt_helperbot.run(run_async=True) # Start in new thread
+ #mqtt_helperbot.run(run_async=True) # Start in new thread
# start conf server on port 443 (async) - Used for most https calls
- conf_server.run(run_async=True) # Start in new thread
+ #conf_server.run(run_async=True) # Start in new thread
# start conf server on port 8007 (async) - Used for a load balancer request
- conf_server_2.run(run_async=True) # Start in new thread
+ #conf_server_2.run(run_async=True) # Start in new thread
while True:
try: