mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
vncauth
This commit is contained in:
parent
94b779c586
commit
8fd2a597bb
2
PKGBUILD
2
PKGBUILD
@ -57,7 +57,7 @@ source=("$url/archive/v$pkgver.tar.gz")
|
||||
md5sums=(SKIP)
|
||||
backup=(
|
||||
etc/kvmd/{override,logging,auth,meta}.yaml
|
||||
etc/kvmd/{ht,ipmi}passwd
|
||||
etc/kvmd/{ht,ipmi,vnc}passwd
|
||||
etc/kvmd/nginx/{kvmd.ctx-{http,server},loc-{login,nocache,proxy,websocket},mime-types,ssl,nginx}.conf
|
||||
)
|
||||
|
||||
|
||||
12
configs/kvmd/vncpasswd
Normal file
12
configs/kvmd/vncpasswd
Normal file
@ -0,0 +1,12 @@
|
||||
# This file describes the credentials for VNCAuth. The left part before arrow is a passphrase
|
||||
# for VNCAuth. The right part is username and password with which the user can access to KVMD API.
|
||||
# The arrow is used as a separator and shows the relationship of user registrations on the system.
|
||||
#
|
||||
# Never use the same passwords for VNC and IPMI users. This default configuration is shown here
|
||||
# for example only.
|
||||
#
|
||||
# If this file does not contain any entries, VNCAuth will be disabled and you will only be able
|
||||
# to login in using your KVMD username and password using VeNCrypt methods.
|
||||
|
||||
# pa$$phr@se -> admin:password
|
||||
admin -> admin:admin
|
||||
@ -345,5 +345,12 @@ def _get_config_scheme() -> Dict:
|
||||
"unix": Option("", type=valid_abs_path, only_if="!port", unpack_as="unix_path"),
|
||||
"timeout": Option(5.0, type=valid_float_f01),
|
||||
},
|
||||
|
||||
"auth": {
|
||||
"vncauth": {
|
||||
"enabled": Option(False, type=valid_bool),
|
||||
"file": Option("/etc/kvmd/vncpasswd", type=valid_abs_file, unpack_as="path"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ from .. import init
|
||||
|
||||
from .kvmd import KvmdClient
|
||||
from .streamer import StreamerClient
|
||||
from .vncauth import VncAuthManager
|
||||
from .server import VncServer
|
||||
from .keysym import build_symmap
|
||||
|
||||
@ -43,6 +44,7 @@ def main(argv: Optional[List[str]]=None) -> None:
|
||||
VncServer(
|
||||
kvmd=KvmdClient(**config.kvmd._unpack()),
|
||||
streamer=StreamerClient(**config.streamer._unpack()),
|
||||
vnc_auth_manager=VncAuthManager(**config.auth.vncauth._unpack()),
|
||||
desired_fps=config.desired_fps,
|
||||
symmap=build_symmap(config.keymap),
|
||||
**config.server._unpack(),
|
||||
|
||||
@ -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(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)
|
||||
|
||||
async def __handshake_security_v3(self, rfb_version: int) -> None:
|
||||
assert rfb_version == 3
|
||||
await self._write_struct("B" + "B" * len(sec_types), len(sec_types), *sec_types) # Keep dict priority
|
||||
|
||||
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)
|
||||
sec_type = await self._read_number("B")
|
||||
if sec_type not in sec_types:
|
||||
raise RfbError(f"Invalid security type: {sec_type}")
|
||||
|
||||
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})")
|
||||
|
||||
# -----
|
||||
(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)
|
||||
|
||||
# =====
|
||||
|
||||
|
||||
53
kvmd/apps/vnc/rfb/crypto.py
Normal file
53
kvmd/apps/vnc/rfb/crypto.py
Normal file
@ -0,0 +1,53 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2020 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from typing import List
|
||||
|
||||
import passlib.crypto.des
|
||||
|
||||
|
||||
# =====
|
||||
def rfb_make_challenge() -> bytes:
|
||||
return os.urandom(16)
|
||||
|
||||
|
||||
def rfb_encrypt_challenge(challenge: bytes, passwd: bytes) -> bytes:
|
||||
assert len(challenge) == 16
|
||||
key = _make_key(passwd)
|
||||
return (
|
||||
passlib.crypto.des.des_encrypt_block(key, challenge[:8])
|
||||
+ passlib.crypto.des.des_encrypt_block(key, challenge[8:])
|
||||
)
|
||||
|
||||
|
||||
def _make_key(passwd: bytes) -> bytes:
|
||||
passwd = (passwd + b"\0" * 8)[:8]
|
||||
key: List[int] = []
|
||||
for ch in passwd:
|
||||
btgt = 0
|
||||
for index in range(8):
|
||||
if ch & (1 << index):
|
||||
btgt = btgt | (1 << 7 - index)
|
||||
key.append(btgt)
|
||||
return bytes(key)
|
||||
@ -39,6 +39,9 @@ from ... import aiotools
|
||||
from .rfb import RfbClient
|
||||
from .rfb.errors import RfbError
|
||||
|
||||
from .vncauth import VncAuthKvmdCredentials
|
||||
from .vncauth import VncAuthManager
|
||||
|
||||
from .kvmd import KvmdClient
|
||||
|
||||
from .streamer import StreamerError
|
||||
@ -66,11 +69,14 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
desired_fps: int,
|
||||
symmap: Dict[int, str],
|
||||
vnc_credentials: Dict[str, VncAuthKvmdCredentials],
|
||||
|
||||
shared_params: _SharedParams,
|
||||
) -> None:
|
||||
|
||||
super().__init__(reader, writer, **dataclasses.asdict(shared_params))
|
||||
self.__vnc_credentials = vnc_credentials
|
||||
|
||||
super().__init__(reader, writer, vnc_passwds=list(vnc_credentials), **dataclasses.asdict(shared_params))
|
||||
|
||||
self.__kvmd = kvmd
|
||||
self.__streamer = streamer
|
||||
@ -208,12 +214,18 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
# =====
|
||||
|
||||
async def _authorize(self, user: str, passwd: str) -> bool:
|
||||
async def _authorize_userpass(self, user: str, passwd: str) -> bool:
|
||||
if (await self.__kvmd.authorize(user, passwd)):
|
||||
self.__authorized.set_result((user, passwd))
|
||||
return True
|
||||
return False
|
||||
|
||||
async def _on_authorized_vnc_passwd(self, passwd: str) -> str:
|
||||
kc = self.__vnc_credentials[passwd]
|
||||
if (await self._authorize_userpass(kc.user, kc.passwd)):
|
||||
return kc.user
|
||||
return ""
|
||||
|
||||
async def _on_key_event(self, code: int, state: bool) -> None:
|
||||
if (web_name := self.__symmap.get(code)) is not None: # noqa: E203,E231
|
||||
await self.__ws_writer_queue.put({
|
||||
@ -258,7 +270,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
|
||||
# =====
|
||||
class VncServer:
|
||||
class VncServer: # pylint: disable=too-many-instance-attributes
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
@ -267,6 +279,7 @@ class VncServer:
|
||||
|
||||
kvmd: KvmdClient,
|
||||
streamer: StreamerClient,
|
||||
vnc_auth_manager: VncAuthManager,
|
||||
|
||||
desired_fps: int,
|
||||
symmap: Dict[int, str],
|
||||
@ -276,43 +289,58 @@ class VncServer:
|
||||
self.__port = port
|
||||
self.__max_clients = max_clients
|
||||
|
||||
self.__client_kwargs = {
|
||||
"kvmd": kvmd,
|
||||
"streamer": streamer,
|
||||
"desired_fps": desired_fps,
|
||||
"symmap": symmap,
|
||||
"shared_params": _SharedParams(),
|
||||
}
|
||||
self.__kvmd = kvmd
|
||||
self.__streamer = streamer
|
||||
self.__vnc_auth_manager = vnc_auth_manager
|
||||
|
||||
self.__desired_fps = desired_fps
|
||||
self.__symmap = symmap
|
||||
|
||||
self.__shared_params = _SharedParams()
|
||||
|
||||
def run(self) -> None:
|
||||
logger = get_logger(0)
|
||||
logger.info("Listening VNC on TCP [%s]:%d ...", self.__host, self.__port)
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
if not loop.run_until_complete(self.__vnc_auth_manager.read_credentials())[1]:
|
||||
raise SystemExit(1)
|
||||
|
||||
with contextlib.closing(socket.socket(socket.AF_INET6, socket.SOCK_STREAM)) as sock:
|
||||
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
|
||||
sock.bind((self.__host, self.__port))
|
||||
logger.info("Listening VNC on TCP [%s]:%d ...", self.__host, self.__port)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
server = loop.run_until_complete(asyncio.start_server(
|
||||
client_connected_cb=self.__handle_client,
|
||||
sock=sock,
|
||||
backlog=self.__max_clients,
|
||||
loop=loop,
|
||||
))
|
||||
with contextlib.closing(socket.socket(socket.AF_INET6, socket.SOCK_STREAM)) as sock:
|
||||
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
|
||||
sock.bind((self.__host, self.__port))
|
||||
|
||||
try:
|
||||
loop.run_forever()
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
pass
|
||||
finally:
|
||||
server.close()
|
||||
loop.run_until_complete(server.wait_closed())
|
||||
tasks = asyncio.Task.all_tasks()
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
|
||||
loop.close()
|
||||
logger.info("Bye-bye")
|
||||
server = loop.run_until_complete(asyncio.start_server(
|
||||
client_connected_cb=self.__handle_client,
|
||||
sock=sock,
|
||||
backlog=self.__max_clients,
|
||||
loop=loop,
|
||||
))
|
||||
|
||||
try:
|
||||
loop.run_forever()
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
pass
|
||||
finally:
|
||||
server.close()
|
||||
loop.run_until_complete(server.wait_closed())
|
||||
finally:
|
||||
tasks = asyncio.Task.all_tasks()
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
|
||||
loop.close()
|
||||
logger.info("Bye-bye")
|
||||
|
||||
async def __handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
|
||||
await _Client(reader, writer, **self.__client_kwargs).run() # type: ignore
|
||||
await _Client(
|
||||
reader=reader,
|
||||
writer=writer,
|
||||
kvmd=self.__kvmd,
|
||||
streamer=self.__streamer,
|
||||
desired_fps=self.__desired_fps,
|
||||
symmap=self.__symmap,
|
||||
vnc_credentials=(await self.__vnc_auth_manager.read_credentials())[0],
|
||||
shared_params=self.__shared_params,
|
||||
).run() # type: ignore
|
||||
|
||||
89
kvmd/apps/vnc/vncauth.py
Normal file
89
kvmd/apps/vnc/vncauth.py
Normal file
@ -0,0 +1,89 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2020 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import dataclasses
|
||||
|
||||
from typing import Tuple
|
||||
from typing import Dict
|
||||
|
||||
import aiofiles
|
||||
|
||||
from ...logging import get_logger
|
||||
|
||||
|
||||
# =====
|
||||
class VncAuthError(Exception):
|
||||
def __init__(self, msg: str) -> None:
|
||||
super().__init__(f"Incorrect VNCAuth passwd file: {msg}")
|
||||
|
||||
|
||||
# =====
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class VncAuthKvmdCredentials:
|
||||
user: str
|
||||
passwd: str
|
||||
|
||||
|
||||
class VncAuthManager:
|
||||
def __init__(
|
||||
self,
|
||||
path: str,
|
||||
enabled: bool,
|
||||
) -> None:
|
||||
|
||||
self.__path = path
|
||||
self.__enabled = enabled
|
||||
|
||||
async def read_credentials(self) -> Tuple[Dict[str, VncAuthKvmdCredentials], bool]:
|
||||
if self.__enabled:
|
||||
try:
|
||||
return (await self.__inner_read_credentials(), True)
|
||||
except VncAuthError as err:
|
||||
get_logger(0).error(str(err))
|
||||
except Exception:
|
||||
get_logger(0).exception("Unhandled exception while reading VNCAuth passwd file")
|
||||
return ({}, (not self.__enabled))
|
||||
|
||||
async def __inner_read_credentials(self) -> Dict[str, VncAuthKvmdCredentials]:
|
||||
async with aiofiles.open(self.__path) as vc_file:
|
||||
lines = (await vc_file.read()).split("\n")
|
||||
|
||||
credentials: Dict[str, VncAuthKvmdCredentials] = {}
|
||||
for (number, line) in enumerate(lines):
|
||||
if len(line.strip()) == 0 or line.lstrip().startswith("#"):
|
||||
continue
|
||||
|
||||
if " -> " not in line:
|
||||
raise VncAuthError(f"Missing ' -> ' operator at line #{number}")
|
||||
|
||||
(vnc_passwd, kvmd_userpass) = map(str.lstrip, line.split(" -> ", 1))
|
||||
if ":" not in kvmd_userpass:
|
||||
raise VncAuthError(f"Missing ':' operator in KVMD credentials (right part) at line #{number}")
|
||||
|
||||
(kvmd_user, kvmd_passwd) = kvmd_userpass.split(":")
|
||||
kvmd_user = kvmd_user.strip()
|
||||
|
||||
if vnc_passwd in credentials:
|
||||
raise VncAuthError(f"Found duplicating VNC password (left part) at line #{number}")
|
||||
|
||||
credentials[vnc_passwd] = VncAuthKvmdCredentials(kvmd_user, kvmd_passwd)
|
||||
return credentials
|
||||
@ -26,3 +26,7 @@ kvmd:
|
||||
|
||||
vnc:
|
||||
keymap: /usr/share/kvmd/keymaps/ru
|
||||
|
||||
auth:
|
||||
vncauth:
|
||||
enabled: true
|
||||
|
||||
@ -34,3 +34,7 @@ kvmd:
|
||||
|
||||
vnc:
|
||||
keymap: /usr/share/kvmd/keymaps/ru
|
||||
|
||||
auth:
|
||||
vncauth:
|
||||
enabled: true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user