diff --git a/bin/nc.traditional b/bin/nc.traditional
new file mode 100755
index 0000000..a5c443b
Binary files /dev/null and b/bin/nc.traditional differ
diff --git a/resources.qrc b/resources.qrc
index 801adef..c2b92f7 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -5,6 +5,7 @@
assets/dead.svg
assets/problem.svg
assets/connecting.png
+ bin/nc.traditional
bin/rM1-vnc-server-standalone
bin/rM2-vnc-server-standalone
diff --git a/src/rmview/screenstream/common.py b/src/rmview/screenstream/common.py
index e13d1e3..2fd9e7d 100644
--- a/src/rmview/screenstream/common.py
+++ b/src/rmview/screenstream/common.py
@@ -81,6 +81,7 @@ def clientConnectionLost(self, connector, reason):
reactor.callFromThread(reactor.stop)
def clientConnectionFailed(self, connector, reason):
+ log.warning("Connection failed")
if reason.check(ConnectionRefusedError):
self.signals.onFatalError.emit(Exception("It seems the tablet is refusing to connect.\nIf you are using the ScreenShare backend please make sure you enabled it on the tablet, before running rmview."))
else:
diff --git a/src/rmview/screenstream/screenshare.py b/src/rmview/screenstream/screenshare.py
index f284af7..8827efd 100644
--- a/src/rmview/screenstream/screenshare.py
+++ b/src/rmview/screenstream/screenshare.py
@@ -25,45 +25,6 @@
log = logging.getLogger('rmview')
-# the screenshare vnc auth uses udp broadcasts
-class ChallengeReaderProtocol(DatagramProtocol):
- clients = {}
-
- def __init__(self, callback):
- self.callback = callback
-
- def datagramReceived(self, datagram, host):
- reader = io.BytesIO(datagram)
-
- # the timestamp is needed for the challenge
- timestamp = reader.read(8)
- tounx, = unpack("!Q", timestamp)
- if timestamp in self.clients:
- log.debug(f"skipping challenge {tounx}")
- return
- log.info(f"received timestamp challenge {tounx}")
-
- if not self.callback(timestamp):
- log.debug("Stopping listening for timestamps")
- self.transport.stopListening()
-
- self.clients[timestamp] = addresses = []
-
- ### The rest of the message is ignored for now
- # (hashlength,) = unpack("!I", reader.read(4))
- # hash = reader.read(hashlength)
- # strhash = hash.hex()
- # #TODO: the email hash could be used to filter the broadcasts when multiple devices are on the network
- # log.info(f"email hash: {strhash}")
-
- # #read tablet's listening addresses
- # while reader.read(1) == b'\00':
- # ip = socket.inet_ntoa(reader.read(4))
- # port, = unpack("!H", reader.read(2))
- # addresses.append(f"{ip}:{port}")
-
- # log.info(addresses)
-
class ScreenShareStream(QRunnable):
@@ -76,9 +37,17 @@ def __init__(self, ssh):
self.signals = ScreenStreamSignals()
def needsDependencies(self):
- return False
+ _, out, _ = self.ssh.exec_command("[ -x /usr/bin/nc.traditional ]")
+ return out.channel.recv_exit_status() != 0
def installDependencies(self):
+ sftp = self.ssh.open_sftp()
+ from stat import S_IXUSR
+ fo = QFile(':bin/nc.traditional')
+ fo.open(QIODevice.ReadOnly)
+ sftp.putfo(fo, '/usr/bin/nc.traditional')
+ fo.close()
+ sftp.chmod('/usr/bin/nc.traditional', S_IXUSR)
pass
def stop(self):
@@ -96,6 +65,7 @@ def stop(self):
reads the usedId from deviceToken from the config file on the rm
"""
def get_userid(self):
+ log.info("Getting userid from device for challenge computation...")
with self.ssh.open_sftp() as sftp:
with sftp.file('/etc/remarkable.conf') as f:
file_content = f.read().decode()
@@ -109,20 +79,13 @@ def get_userid(self):
return(d["auth0-userid"])
def computeChallenge(self, userId, timestamp):
+ log.info("Computing challenge from userId and timestamp...")
userBytes = userId.encode()
userIdHash = hashlib.sha256(userBytes).digest()
return hashlib.sha256(timestamp + userIdHash).digest()
- #Hack to run the vnc with the challenge
- def runVnc(self, timestamp):
- if not self.factory:
- userId = self.get_userid()
- challenge = self.computeChallenge(userId, timestamp)
- log.info(f"Challenge: {challenge.hex()}, connecting to vnc")
- self.startVncClient(challenge)
- return False
-
def startVncClient(self, challenge=None):
+ log.info("Starting vncClient...")
self.factory = VncFactory(self.signals)
self.factory.setChallenge(challenge)
@@ -130,16 +93,57 @@ def startVncClient(self, challenge=None):
#self.vncClient = internet.TCPClient("localhost", 31337, self.factory)
self.vncClient = internet.SSLClient(self.ssh.hostname, 5900, self.factory, ssl.ClientContextFactory())
self.vncClient.startService()
+ log.info("vncClient Started.")
+
+ def readChallengeOverTunnel(self):
+ log.info("Listening for device broadcast through ssh tunnel...")
+ stdin, stdout, stderr = self.ssh.exec_command('/usr/bin/nc.traditional -l -u -p 5901 -w 6')
+ # nc needs its stdin closed before it will return!
+ stdin.writelines("\n")
+ stdin.close()
+ stdout.channel.recv_exit_status()
+ timestamp = stdout.read(8)
+ tounx, = unpack("!Q", timestamp)
+ log.info(f"Timestamp challenge {tounx}")
+
+ ### The rest of the message is ignored for now
+ # (hashlength,) = unpack("!I", reader.read(4))
+ # hash = reader.read(hashlength)
+ # strhash = hash.hex()
+ # #TODO: the email hash could be used to filter the broadcasts when multiple devices are on the network
+ # log.info(f"email hash: {strhash}")
+
+ # #read tablet's listening addresses
+ # while reader.read(1) == b'\00':
+ # ip = socket.inet_ntoa(reader.read(4))
+ # port, = unpack("!H", reader.read(2))
+ # addresses.append(f"{ip}:{port}")
+
+ stderr.close()
+ stdout.close()
+
+ # TODO: We should check the IP from the packet to ensure it's not a broadcast
+ # from another rM on the same network, right?
+
+ # compute challenge and start client
+ userId = self.get_userid()
+ challenge = self.computeChallenge(userId, timestamp)
+ log.info(f"Challenge: {challenge.hex()}, connecting to vnc")
+
+ # start the actual vnc client
+ # this call needs to be done from the main reactor thread (for reasons I don't understand)
+ reactor.callFromThread(self.startVncClient, challenge)
def run(self):
log.info("Connecting to ScreenShare, make sure you enable it")
try:
if self.ssh.softwareVersion > SW_VER_TIMESTAMPS['2.9.1.236']:
- log.warning("Authenticating, please wait...")
- challengeReader = ChallengeReaderProtocol(self.runVnc)
- reactor.listenUDP(5901, challengeReader)
+ log.info("Authenticating, please wait...")
+ # do this blocking io in a background thread
+ reactor.callInThread(self.readChallengeOverTunnel)
else:
log.warning("Skipping authentication")
+ # does this work without reactor having been started?
self.startVncClient()
reactor.run(installSignalHandlers=0)