summaryrefslogtreecommitdiff
path: root/kvmd/apps/vnc/rfb/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'kvmd/apps/vnc/rfb/__init__.py')
-rw-r--r--kvmd/apps/vnc/rfb/__init__.py132
1 files changed, 86 insertions, 46 deletions
diff --git a/kvmd/apps/vnc/rfb/__init__.py b/kvmd/apps/vnc/rfb/__init__.py
index 8dfb6fe2..698b5084 100644
--- a/kvmd/apps/vnc/rfb/__init__.py
+++ b/kvmd/apps/vnc/rfb/__init__.py
@@ -22,7 +22,10 @@
import asyncio
+from typing import Tuple
+from typing import List
from typing import Dict
+from typing import Callable
from typing import Coroutine
from ....logging import get_logger
@@ -35,6 +38,9 @@ from .errors import RfbConnectionError
from .encodings import RfbEncodings
from .encodings import RfbClientEncodings
+from .crypto import rfb_make_challenge
+from .crypto import rfb_encrypt_challenge
+
from .stream import RfbClientStream
@@ -52,14 +58,17 @@ class RfbClient(RfbClientStream):
width: int,
height: int,
name: str,
+ vnc_passwds: List[str],
) -> None:
super().__init__(reader, writer)
self._width = width
self._height = height
- self._name = name
+ self.__name = name
+ self.__vnc_passwds = vnc_passwds
+ self.__rfb_version = 0
self._encodings = RfbClientEncodings(frozenset())
self._lock = asyncio.Lock()
@@ -90,14 +99,14 @@ class RfbClient(RfbClientStream):
except RfbConnectionError as err:
logger.info("[%s] Client %s: Gone (%s): Disconnected", name, self._remote, str(err))
except RfbError as err:
- logger.info("[%s] Client %s: %s: Disconnected", name, self._remote, str(err))
+ logger.error("[%s] Client %s: %s: Disconnected", name, self._remote, str(err))
except Exception:
logger.exception("[%s] Unhandled exception with client %s: Disconnected", name, self._remote)
async def __main_task_loop(self) -> None:
try:
- rfb_version = await self.__handshake_version()
- await self.__handshake_security(rfb_version)
+ await self.__handshake_version()
+ await self.__handshake_security()
await self.__handshake_init()
await self.__main_loop()
finally:
@@ -105,7 +114,10 @@ class RfbClient(RfbClientStream):
# =====
- async def _authorize(self, user: str, passwd: str) -> bool:
+ async def _authorize_userpass(self, user: str, passwd: str) -> bool:
+ raise NotImplementedError
+
+ async def _on_authorized_vnc_passwd(self, passwd: str) -> str:
raise NotImplementedError
async def _on_key_event(self, code: int, state: bool) -> None:
@@ -148,7 +160,7 @@ class RfbClient(RfbClientStream):
assert self._encodings.has_rename
await self._write_fb_update(0, 0, RfbEncodings.RENAME, drain=False)
await self._write_reason(name)
- self._name = name
+ self.__name = name
async def _send_leds_state(self, caps: bool, scroll: bool, num: bool) -> None:
assert self._encodings.has_leds_state
@@ -157,7 +169,7 @@ class RfbClient(RfbClientStream):
# =====
- async def __handshake_version(self) -> int:
+ async def __handshake_version(self) -> None:
# The only published protocol versions at this time are 3.3, 3.7, 3.8.
# Version 3.5 was wrongly reported by some clients, but it should be
# interpreted by all servers as 3.3
@@ -176,36 +188,34 @@ class RfbClient(RfbClientStream):
version = int(response[-2])
except ValueError:
raise RfbError(f"Invalid version response: {response!r}")
- return (3 if version == 5 else version)
+ self.__rfb_version = (3 if version == 5 else version)
+ get_logger(0).info("[main] Client %s: Using RFB version 3.%d", self._remote, self.__rfb_version)
# =====
- async def __handshake_security(self, rfb_version: int) -> None:
- if rfb_version == 3:
- await self.__handshake_security_v3(rfb_version)
- else:
- await self.__handshake_security_v7_plus(rfb_version)
-
- async def __handshake_security_v3(self, rfb_version: int) -> None:
- assert rfb_version == 3
-
- await self._write_struct("L", 0, drain=False) # Refuse old clients using the invalid security type
- msg = "The client uses a very old protocol 3.3; required 3.7 at least"
- await self._write_reason(msg)
- raise RfbError(msg)
-
- async def __handshake_security_v7_plus(self, rfb_version: int) -> None:
- assert rfb_version >= 7
-
- vencrypt = 19
- await self._write_struct("B B", 1, vencrypt) # One security type, VeNCrypt
-
- security_type = await self._read_number("B")
- if security_type != vencrypt:
- raise RfbError(f"Invalid security type: {security_type}; expected VeNCrypt({vencrypt})")
-
- # -----
-
+ async def __handshake_security(self) -> None:
+ sec_types: Dict[int, Tuple[str, Callable]] = {}
+ if self.__rfb_version > 3:
+ sec_types[19] = ("VeNCrypt", self.__handshake_security_vencrypt)
+ if self.__vnc_passwds:
+ sec_types[2] = ("VNCAuth", self.__handshake_security_vnc_auth)
+ if not sec_types:
+ msg = "The client uses a very old protocol 3.3 and VNCAuth is disabled"
+ await self._write_struct("L", 0, drain=False) # Refuse old clients using the invalid security type
+ await self._write_reason(msg)
+ raise RfbError(msg)
+
+ await self._write_struct("B" + "B" * len(sec_types), len(sec_types), *sec_types) # Keep dict priority
+
+ sec_type = await self._read_number("B")
+ if sec_type not in sec_types:
+ raise RfbError(f"Invalid security type: {sec_type}")
+
+ (sec_name, handler) = sec_types[sec_type]
+ get_logger(0).info("[main] Client %s: Using %s security type", self._remote, sec_name)
+ await handler()
+
+ async def __handshake_security_vencrypt(self) -> None:
await self._write_struct("BB", 0, 2) # VeNCrypt 0.2
vencrypt_version = "%d.%d" % (await self._read_struct("BB"))
@@ -215,29 +225,59 @@ class RfbClient(RfbClientStream):
await self._write_struct("B", 0)
- # -----
+ auth_types = {256: ("VeNCrypt/Plain", self.__handshake_security_vencrypt_userpass)}
+ if self.__vnc_passwds:
+ # Vinagre не умеет работать с VNC Auth через VeNCrypt, но это его проблемы,
+ # так как он своеобразно трактует рекомендации VeNCrypt.
+ # Подробнее: https://bugzilla.redhat.com/show_bug.cgi?id=692048
+ # Hint: используйте любой другой нормальный VNC-клиент.
+ auth_types[2] = ("VeNCrypt/VNCAuth", self.__handshake_security_vnc_auth)
- plain = 256
- await self._write_struct("B L", 1, plain) # One auth subtype, plain
+ await self._write_struct("B" + "L" * len(auth_types), len(auth_types), *auth_types)
auth_type = await self._read_number("L")
- if auth_type != plain:
- raise RfbError(f"Invalid auth type: {auth_type}; expected Plain({plain})")
+ if auth_type not in auth_types:
+ raise RfbError(f"Invalid VeNCrypt auth type: {auth_type}")
- # -----
+ (auth_name, handler) = auth_types[auth_type]
+ get_logger(0).info("[main] Client %s: Using %s auth type", self._remote, auth_name)
+ await handler()
+ async def __handshake_security_vencrypt_userpass(self) -> None:
(user_length, passwd_length) = await self._read_struct("LL")
user = await self._read_text(user_length)
passwd = await self._read_text(passwd_length)
- if (await self._authorize(user, passwd)):
+ ok = await self._authorize_userpass(user, passwd)
+
+ await self.__handshake_security_send_result(ok, user)
+
+ async def __handshake_security_vnc_auth(self) -> None:
+ challenge = rfb_make_challenge()
+ await self._write_struct("", challenge)
+
+ (ok, user) = (False, "")
+ response = (await self._read_struct("16s"))[0]
+ for passwd in self.__vnc_passwds:
+ passwd_bytes = passwd.encode("utf-8", errors="ignore")
+ if rfb_encrypt_challenge(challenge, passwd_bytes) == response:
+ user = await self._on_authorized_vnc_passwd(passwd)
+ if user:
+ ok = True
+ break
+
+ await self.__handshake_security_send_result(ok, user)
+
+ async def __handshake_security_send_result(self, ok: bool, user: str) -> None:
+ if ok:
+ assert user
get_logger(0).info("[main] Client %s: Access granted for user %r", self._remote, user)
await self._write_struct("L", 0)
else:
- await self._write_struct("L", 1, drain=(rfb_version < 8))
- if rfb_version >= 8:
- await self._write_reason("Invalid username or password")
- raise RfbError(f"Access denied for user {user!r}")
+ await self._write_struct("L", 1, drain=(self.__rfb_version < 8))
+ if self.__rfb_version >= 8:
+ await self._write_reason("Invalid username or password" if user else "Invalid password")
+ raise RfbError(f"Access denied for user {user!r}" if user else "Access denied")
# =====
@@ -259,7 +299,7 @@ class RfbClient(RfbClientStream):
0, # Blue shift
drain=False,
)
- await self._write_reason(self._name)
+ await self._write_reason(self.__name)
# =====