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()
async def start_pan_client(self, access_token, user, user_id, password): 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, ) 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()
def run(self): self.loop = GLib.MainLoop() if self.config.notifications: try: notify2.init("pantalaimon", mainloop=self.loop) self.notifications = True except dbus.DBusException: logger.error( "Notifications are enabled but no notification " "server could be found, disabling notifications.") self.notifications = False GLib.timeout_add(100, self.message_callback) self.loop.run()
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()
async def send_message(self, request): access_token = self.get_access_token(request) if not access_token: return self._missing_token client = await self._find_client(access_token) if not client: return self._unknown_token room_id = request.match_info["room_id"] try: room = client.rooms[room_id] encrypt = room.encrypted except KeyError: # The room is not in the joined rooms list, either the pan client # didn't manage to sync the state or we're not joined, in either # case send an error response. if client.has_been_synced: return web.json_response( { "errcode": "M_FORBIDDEN", "error": "You do not have permission to send the event." }, headers=CORS_HEADERS, status=403, ) else: logger.error("The internal Pantalaimon client did not manage " "to sync with the server.") return web.json_response( { "errcode": "M_UNKNOWN", "error": "The pantalaimon client did not manage to sync with " "the server", }, headers=CORS_HEADERS, status=500, ) # Don't encrypt reactions for now - they are weird and clients # need to support them like this. # TODO: Fix when MSC1849 is fully supported by clients. if request.match_info["event_type"] == "m.reaction": encrypt = False msgtype = request.match_info["event_type"] try: content = await request.json() except (JSONDecodeError, ContentTypeError): return self._not_json # The room isn't encrypted just forward the message. if not encrypt: content_msgtype = content.get("msgtype") if (content_msgtype in ["m.image", "m.video", "m.audio", "m.file"] or msgtype == "m.room.avatar"): try: content["url"] = await self._decrypt_uri( content["url"], client) if ("info" in content and "thumbnail_url" in content["info"] and not content["info"]["thumbnail_url"] == None): content["info"][ "thumbnail_url"] = await self._decrypt_uri( content["info"]["thumbnail_url"], client) return await self.forward_to_web(request, data=json.dumps(content), token=client.access_token) except ClientConnectionError as e: return web.Response(status=500, text=str(e)) except (KeyError, NotDecryptedAvailableError): return await self.forward_to_web(request, token=client.access_token) return await self.forward_to_web(request, token=client.access_token) txnid = request.match_info.get("txnid", uuid4()) async def _send(ignore_unverified=False): try: content_msgtype = content.get("msgtype") if (content_msgtype in ["m.image", "m.video", "m.audio", "m.file"] or msgtype == "m.room.avatar"): upload_info, media_info = self._get_upload_and_media_info( content["url"]) if not upload_info or not media_info: response = await client.room_send( room_id, msgtype, content, txnid, ignore_unverified) return web.Response( status=response.transport_response.status, content_type=response.transport_response. content_type, headers=CORS_HEADERS, body=await response.transport_response.read(), ) media_info.to_content(content, upload_info.mimetype) if content["info"].get("thumbnail_url", False): ( thumb_upload_info, thumb_media_info, ) = self._get_upload_and_media_info( content["info"]["thumbnail_url"]) if thumb_upload_info and thumb_media_info: thumb_media_info.to_thumbnail( content, thumb_upload_info.mimetype) response = await client.room_send(room_id, msgtype, content, txnid, ignore_unverified) else: response = await client.room_send(room_id, msgtype, content, txnid, ignore_unverified) return web.Response( status=response.transport_response.status, content_type=response.transport_response.content_type, headers=CORS_HEADERS, body=await response.transport_response.read(), ) except ClientConnectionError as e: return web.Response(status=500, text=str(e)) except SendRetryError as e: return web.Response(status=503, text=str(e)) # Aquire a semaphore here so we only send out one # UnverifiedDevicesSignal sem = client.send_semaphores[room_id] async with sem: # Even though we request the full state we don't receive room # members since we're using lazy loading. The summary is for some # reason empty so nio can't know if room members are missing from # our state. Fetch the room members here instead. if not client.room_members_fetched[room_id]: try: await client.joined_members(room_id) client.room_members_fetched[room_id] = True except ClientConnectionError as e: return web.Response(status=500, text=str(e)) try: return await _send(self.conf.ignore_verification) except OlmTrustError as e: # There are unverified/unblocked devices in the room, notify # the UI thread about this and wait for a response. queue = asyncio.Queue() client.send_decision_queues[room_id] = queue message = UnverifiedDevicesSignal(client.user_id, room_id, room.display_name) await self.send_ui_message(message) try: response = await asyncio.wait_for( queue.get(), self.unverified_send_timeout) if isinstance(response, CancelSendingMessage): # The send was canceled notify the client that sent the # request about this. info_msg = (f"Canceled message sending for room " f"{room.display_name} ({room_id}).") logger.info(info_msg) await self.send_response(response.message_id, client.user_id, "m.ok", info_msg) return web.Response(status=503, text=str(e)) elif isinstance(response, SendAnywaysMessage): # We are sending and ignoring devices along the way. info_msg = (f"Ignoring unverified devices and sending " f"message to room " f"{room.display_name} ({room_id}).") logger.info(info_msg) await self.send_response(response.message_id, client.user_id, "m.ok", info_msg) ret = await _send(True) await client.send_update_devices( client.room_devices(room_id)) return ret except asyncio.TimeoutError: # We didn't get a response to our signal, send out an error # response. return web.Response( status=503, text=(f"Room contains unverified devices and no " f"action was taken for " f"{self.unverified_send_timeout} seconds, " f"request timed out"), ) finally: client.send_decision_queues.pop(room_id)