Exemplo n.º 1
0
    def pan_decrypt_event(self, event_dict, room_id=None, ignore_failures=True):
        # type: (Dict[Any, Any], Optional[str], bool) -> (bool)
        event = Event.parse_encrypted_event(event_dict)

        if not isinstance(event, MegolmEvent):
            logger.warn(
                "Encrypted event is not a megolm event:"
                "\n{}".format(pformat(event_dict))
            )
            return False

        if not event.room_id:
            event.room_id = room_id

        try:
            decrypted_event = self.decrypt_event(event)
            logger.info("Decrypted event: {}".format(decrypted_event))

            event_dict.update(decrypted_event.source)
            event_dict["decrypted"] = True
            event_dict["verified"] = decrypted_event.verified

            return True

        except EncryptionError as error:
            logger.warn(error)

            if ignore_failures:
                event_dict.update(self.unable_to_decrypt)
            else:
                raise

            return False
Exemplo n.º 2
0
async def message_router(receive_queue, send_queue, proxies):
    """Find the recipient of a message and forward it to the right proxy."""
    def find_proxy_by_user(user):
        # type: (str) -> Optional[ProxyDaemon]
        for proxy in proxies:
            if user in proxy.pan_clients:
                return proxy

        return None

    async def send_info(message_id, pan_user, code, string):
        message = DaemonResponse(message_id, pan_user, code, string)
        await send_queue.put(message)

    while True:
        message = await receive_queue.get()
        logger.debug(f"Router got message {message}")

        proxy = find_proxy_by_user(message.pan_user)

        if not proxy:
            msg = f"No pan client found for {message.pan_user}."
            logger.warn(msg)
            await send_info(message.message_id, message.pan_user,
                            "m.unknown_client", msg)

        await proxy.receive_message(message)
Exemplo n.º 3
0
    def decrypt_sync_body(self, body):
        # type: (Dict[Any, Any]) -> Dict[Any, Any]
        """Go through a json sync response and decrypt megolm encrypted events.

        Args:
            body (Dict[Any, Any]): The dictionary of a Sync response.

            Returns the json response with decrypted events.
        """
        for room_id, room_dict in body["rooms"]["join"].items():
            try:
                if not self.rooms[room_id].encrypted:
                    logger.info("Room {} is not encrypted skipping...".format(
                        self.rooms[room_id].display_name))
                    continue
            except KeyError:
                logger.info("Unknown room {} skipping...".format(room_id))
                continue

            for event in room_dict["timeline"]["events"]:
                if event["type"] != "m.room.encrypted":
                    logger.info("Event is not encrypted: "
                                "\n{}".format(pformat(event)))
                    continue

                parsed_event = RoomEncryptedEvent.parse_event(event)
                parsed_event.room_id = room_id

                if not isinstance(parsed_event, MegolmEvent):
                    logger.warn("Encrypted event is not a megolm event:"
                                "\n{}".format(pformat(event)))
                    continue

                try:
                    decrypted_event = self.decrypt_event(parsed_event)
                    logger.info("Decrypted event: {}".format(decrypted_event))
                    event["type"] = "m.room.message"

                    # TODO support other event types
                    # This should be best done in nio, modify events so they
                    # keep the dictionary from which they are built in a source
                    # attribute.
                    event["content"] = {
                        "msgtype": "m.text",
                        "body": decrypted_event.body
                    }

                    if decrypted_event.formatted_body:
                        event["content"]["formatted_body"] = (
                            decrypted_event.formatted_body)
                        event["content"]["format"] = decrypted_event.format

                    event["decrypted"] = True
                    event["verified"] = decrypted_event.verified

                except EncryptionError as error:
                    logger.warn(error)
                    continue

        return body
Exemplo n.º 4
0
    async def download(self, request):
        server_name = request.match_info["server_name"]
        media_id = request.match_info["media_id"]
        file_name = request.match_info.get("file_name")

        try:
            media_info = self.media_info[(server_name, media_id)]
        except KeyError:
            media_info = self.store.load_media(self.name, server_name,
                                               media_id)

            if not media_info:
                logger.info(
                    f"No media info found for {server_name}/{media_id}")
                return await self.forward_to_web(request)

            self.media_info[(server_name, media_id)] = media_info

        try:
            key = media_info.key["k"]
            hash = media_info.hashes["sha256"]
        except KeyError:
            logger.warn(
                f"Media info for {server_name}/{media_id} doesn't contain a key or hash."
            )
            return await self.forward_to_web(request)

        if not self.pan_clients:
            return await self.forward_to_web(request)

        client = next(iter(self.pan_clients.values()))

        try:
            response = await client.download(server_name, media_id, file_name)
        except ClientConnectionError as e:
            return web.Response(status=500, text=str(e))

        if not isinstance(response, DownloadResponse):
            return web.Response(
                status=response.transport_response.status,
                content_type=response.transport_response.content_type,
                headers=CORS_HEADERS,
                body=await response.transport_response.read(),
            )

        logger.info(f"Decrypting media {server_name}/{media_id}")

        loop = asyncio.get_running_loop()
        with concurrent.futures.ProcessPoolExecutor() as pool:
            decrypted_file = await loop.run_in_executor(
                pool, decrypt_attachment, response.body, key, hash,
                media_info.iv)

        return web.Response(
            status=response.transport_response.status,
            content_type=response.transport_response.content_type,
            headers=CORS_HEADERS,
            body=decrypted_file,
        )
Exemplo n.º 5
0
    def __attrs_post_init__(self):
        loop = asyncio.get_event_loop()

        self.homeserver_url = self.homeserver.geturl()
        self.hostname = self.homeserver.hostname
        self.store = PanStore(self.data_dir)
        accounts = self.store.load_users(self.name)
        self.media_info = self.store.load_media(self.name)

        for user_id, device_id in accounts:
            if self.conf.keyring:
                try:
                    token = keyring.get_password(
                        "pantalaimon", f"{user_id}-{device_id}-token"
                    )
                except RuntimeError as e:
                    logger.error(e)
            else:
                token = self.store.load_access_token(user_id, device_id)

            if not token:
                logger.warn(
                    f"Not restoring client for {user_id} {device_id}, "
                    f"missing access token."
                )
                continue

            logger.info(f"Restoring client for {user_id} {device_id}")

            pan_client = PanClient(
                self.name,
                self.store,
                self.conf,
                self.homeserver_url,
                self.send_queue,
                user_id,
                device_id,
                store_path=self.data_dir,
                ssl=self.ssl,
                proxy=self.proxy,
                store_class=self.client_store_class,
                media_info=self.media_info,
            )
            pan_client.user_id = user_id
            pan_client.access_token = token
            pan_client.load_store()
            self.pan_clients[user_id] = pan_client

            loop.create_task(
                self.send_ui_message(
                    UpdateUsersMessage(self.name, user_id, pan_client.device_id)
                )
            )

            loop.create_task(pan_client.send_update_devices(pan_client.device_store))

            pan_client.start_loop()
Exemplo n.º 6
0
    def pan_decrypt_event(self,
                          event_dict,
                          room_id=None,
                          ignore_failures=True):
        # type: (Dict[Any, Any], Optional[str], bool) -> (bool)
        event = Event.parse_encrypted_event(event_dict)

        if not isinstance(event, MegolmEvent):
            logger.warn("Encrypted event is not a megolm event:"
                        "\n{}".format(pformat(event_dict)))
            return False

        if not event.room_id:
            event.room_id = room_id

        try:
            decrypted_event = self.decrypt_event(event)
            logger.debug("Decrypted event: {}".format(decrypted_event))
            logger.info("Decrypted event from {} in {}, event id: {}".format(
                decrypted_event.sender,
                decrypted_event.room_id,
                decrypted_event.event_id,
            ))

            if isinstance(decrypted_event, RoomEncryptedMedia):
                self.store_event_media(decrypted_event)

                decrypted_event.source["content"]["url"] = decrypted_event.url

                if decrypted_event.thumbnail_url:
                    decrypted_event.source["content"]["info"][
                        "thumbnail_url"] = decrypted_event.thumbnail_url

            event_dict.update(decrypted_event.source)
            event_dict["decrypted"] = True
            event_dict["verified"] = decrypted_event.verified

            return True

        except EncryptionError as error:
            logger.warn(error)

            if ignore_failures:
                event_dict.update(self.unable_to_decrypt)
            else:
                raise

            return False
Exemplo n.º 7
0
    async def _find_client(self, access_token):
        client_info = self.client_info.get(access_token, None)

        if not client_info:
            async with aiohttp.ClientSession() as session:
                try:
                    method, path = Api.whoami(access_token)
                    resp = await session.request(
                        method,
                        self.homeserver_url + path,
                        proxy=self.proxy,
                        ssl=self.ssl,
                    )
                except ClientConnectionError:
                    return None

                if resp.status != 200:
                    return None

                try:
                    body = await resp.json()
                except (JSONDecodeError, ContentTypeError):
                    return None

                try:
                    user_id = body["user_id"]
                except KeyError:
                    return None

                if user_id not in self.pan_clients:
                    logger.warn(
                        f"User {user_id} doesn't have a matching pan " f"client."
                    )
                    return None

                logger.info(
                    f"Homeserver confirmed valid access token "
                    f"for user {user_id}, caching info."
                )

                client_info = ClientInfo(user_id, access_token)
                self.client_info[access_token] = client_info

        client = self.pan_clients.get(client_info.user_id, None)

        return client
Exemplo n.º 8
0
    async def _load_decrypted_file(self, server_name, media_id, file_name):
        try:
            media_info = self.media_info[(server_name, media_id)]
        except KeyError:
            media_info = self.store.load_media(self.name, server_name,
                                               media_id)

            if not media_info:
                logger.info(
                    f"No media info found for {server_name}/{media_id}")
                return None, None

            self.media_info[(server_name, media_id)] = media_info

        try:
            key = media_info.key["k"]
            hash = media_info.hashes["sha256"]
        except KeyError as e:
            logger.warn(
                f"Media info for {server_name}/{media_id} doesn't contain a key or hash."
            )
            raise e
        if not self.pan_clients:
            return None, None

        client = next(iter(self.pan_clients.values()))

        try:
            response = await client.download(server_name, media_id, file_name)
        except ClientConnectionError as e:
            raise e

        if not isinstance(response, DownloadResponse):
            return response, None

        logger.info(f"Decrypting media {server_name}/{media_id}")

        loop = asyncio.get_running_loop()
        with concurrent.futures.ProcessPoolExecutor() as pool:
            decrypted_file = await loop.run_in_executor(
                pool, decrypt_attachment, response.body, key, hash,
                media_info.iv)

        return response, decrypted_file
Exemplo n.º 9
0
    async def start_pan_client(self,
                               access_token,
                               user,
                               user_id,
                               password,
                               device_id=None):
        client = ClientInfo(user_id, access_token)
        self.client_info[access_token] = client
        self.store.save_server_user(self.name, user_id)

        if user_id in self.pan_clients:
            logger.info(f"Background sync client already exists for {user_id},"
                        f" not starting new one")
            return

        pan_client = PanClient(
            self.name,
            self.store,
            self.conf,
            self.homeserver_url,
            self.send_queue,
            user_id,
            store_path=self.data_dir,
            ssl=self.ssl,
            proxy=self.proxy,
            store_class=self.client_store_class,
            media_info=self.media_info,
        )

        if password == "":
            if device_id is None:
                logger.warn(
                    "Empty password provided and device_id was also None, not "
                    "starting background sync client ")
                return
            # If password is blank, we cannot login normally and must
            # fall back to using the provided device_id.
            pan_client.restore_login(user_id, device_id, access_token)
        else:
            response = await pan_client.login(password, "pantalaimon")

            if not isinstance(response, LoginResponse):
                await pan_client.close()
                return

        logger.info(f"Succesfully started new background sync client for "
                    f"{user_id}")

        await self.send_ui_message(
            UpdateUsersMessage(self.name, user_id, pan_client.device_id))

        self.pan_clients[user_id] = pan_client

        if self.conf.keyring:
            try:
                keyring.set_password(
                    "pantalaimon",
                    f"{user_id}-{pan_client.device_id}-token",
                    pan_client.access_token,
                )
            except RuntimeError as e:
                logger.error(e)
        else:
            self.store.save_access_token(user_id, pan_client.device_id,
                                         pan_client.access_token)

        pan_client.start_loop()