diff --git a/moonraker/components/authorization.py b/moonraker/components/authorization.py index 7a52e43..73090ba 100644 --- a/moonraker/components/authorization.py +++ b/moonraker/components/authorization.py @@ -182,6 +182,7 @@ class Authorization: self.trusted_ips: List[IPAddr] = [] self.trusted_ranges: List[IPNetwork] = [] self.trusted_domains: List[str] = [] + self.trusted_mqtt_clients: List[str] = [] # MQTT client id for val in config.getlist('trusted_clients', []): # Check IP address try: @@ -719,6 +720,22 @@ class Authorization: return self.users[API_USER] raise self.server.error("Invalid API Key", 401) + def validate_mqtt(self, uuid: str, data: Dict) -> bool: + username: str = data.get("username") + password: str = data.get("password") + if username != SUPER_USER: + return False + user_info = self.users[username] + salt = bytes.fromhex(user_info.salt) + hashed_pass = hashlib.pbkdf2_hmac( + 'sha256', password.encode(), salt, HASH_ITER).hex() + if (valid := hashed_pass == user_info.password): + self.trusted_mqtt_clients.append(uuid) + return valid + + def check_mqtt(self, uuid: str) -> bool: + return uuid in self.trusted_mqtt_clients + def _load_private_key(self, secret: str) -> Signer: try: key = Signer(bytes.fromhex(secret)) diff --git a/moonraker/components/mqtt.py b/moonraker/components/mqtt.py index 2d3c0e4..0ae87c4 100644 --- a/moonraker/components/mqtt.py +++ b/moonraker/components/mqtt.py @@ -43,8 +43,10 @@ if TYPE_CHECKING: from ..eventloop import FlexTimer from .klippy_apis import KlippyAPI from .machine import Machine + from .authorization import Authorization FlexCallback = Callable[[bytes], Optional[Coroutine]] RPCCallback = Callable[..., Coroutine] + AuthComp = Optional[Authorization] PAHO_MQTT_VERSION = tuple([int(p) for p in paho.mqtt.__version__.split(".")]) DUP_API_REQ_CODE = -10000 @@ -844,23 +846,33 @@ class MQTTClient(APITransport): response = request.copy() if msgVer == 3: # msg version is 3 or 3.0 msgIMEI = request.get("imei") + msgUUID = request.get("uuid") msgCmd = request.get("cmd") msgData = request.get("data") response["data"] = "" if msgIMEI == self.client_id: - if msgCmd == 'API': - rpc: JsonRPC = self.server.lookup_component("jsonrpc") - result = await rpc.dispatch(jsonw.dumps(msgData), self) - response["data"] = jsonw.loads(result) - elif msgCmd == 'SDP': - webrtc_bridge = self.server.lookup_component("webrtc_bridge", None) - if webrtc_bridge: - response["data"] = await webrtc_bridge.handle_sdp(msgData, topic) + auth: AuthComp = self.server.lookup_component('authorization', None) + if auth is None or auth.check_mqtt(msgUUID) or msgCmd == 'PWD': + if msgCmd == 'PWD': + if auth is not None: + response['data'] = 'OK' if auth.validate_mqtt(msgUUID, msgData) else 'INCORRECT' + else: + response['data'] = 'IGNORE' + elif msgCmd == 'API': + rpc: JsonRPC = self.server.lookup_component("jsonrpc") + result = await rpc.dispatch(jsonw.dumps(msgData), self) + response["data"] = jsonw.loads(result) + elif msgCmd == 'SDP': + webrtc_bridge = self.server.lookup_component("webrtc_bridge", None) + if webrtc_bridge: + response["data"] = await webrtc_bridge.handle_sdp(msgData, topic) + else: + response["data"] = {"type": "error", "message": "WebRTC Bridge component not available"} else: - response["data"] = {"type": "error", "message": "WebRTC Bridge component not available"} + response["data"] = f"error: Unknown MQTT message cmd: {msgCmd}" else: - response["data"] = f"error: Unknown MQTT message cmd: {msgCmd}" + response['data'] = f"error: MQTT UserID [{msgUUID}] needs authentication" else: response["data"] = f"error: MQTT client_id [{msgIMEI}] does not match" else: