Ejemplo n.º 1
0
 def __init__(self, sock):
     self.sock = sock
     self.parser = Parser()
     self.router = Router(_Future)
     self.bus_proxy = Proxy(message_bus, self)
     hello_reply = self.bus_proxy.Hello()
     self.unique_name = hello_reply[0]
Ejemplo n.º 2
0
    def Init(self, *args, **kwargs):
        self.module = self.GetModule()
        self.debug = self.module.debug
        self.logger = get_logger(self.__class__.__name__)
        # Well-known name (org.asamk.Signal) registered with message bus
        self.has_service = False
        #
        self.__dict__.update(kwargs)
        self.unique_name = None
        #
        self.auth_parser = SASLParserAnonAuth()
        self.parser = Parser()
        #
        FakeFuture.fake_loop = FakeLoop(self.module)
        self.router = Router(FakeFuture)
        #
        if self.debug:
            from jeepney.low_level import HeaderFields

            def on_unhandled(msg):
                member = msg.header.fields[HeaderFields.member]
                if member == "NameAcquired":
                    # This fires before the "Hello" reply callback
                    log_msg = f"Received routine opening signal: {member!r}; "
                else:
                    log_msg = "See 'data_received' entry above for contents"
                self.logger.debug(log_msg)

            self.router.on_unhandled = on_unhandled
        self.authentication = FakeFuture()
        # FIXME explain why this appears twice (see above)
        self.unique_name = None
Ejemplo n.º 3
0
 def __init__(self, socket, enable_fds=False):
     self.socket = socket
     self.enable_fds = enable_fds
     self.parser = Parser()
     self.outgoing_serial = count(start=1)
     self.unique_name = None
     self.send_lock = trio.Lock()
     self.recv_lock = trio.Lock()
     self._leftover_to_send = None  # type: Optional[memoryview]
Ejemplo n.º 4
0
class DBusConnection(Channel):
    """A plain D-Bus connection with no matching of replies.

    This doesn't run any separate tasks: sending and receiving are done in
    the task that calls those methods. It's suitable for implementing servers:
    several worker tasks can receive requests and send replies.
    For a typical client pattern, see :class:`DBusRouter`.

    Implements trio's channel interface for Message objects.
    """
    def __init__(self, socket: trio.SocketStream):
        self.socket = socket
        self.parser = Parser()
        self.outgoing_serial = count(start=1)
        self.unique_name = None
        self.send_lock = trio.Lock()

    async def send(self, message: Message, *, serial=None):
        """Serialise and send a :class:`~.Message` object"""
        async with self.send_lock:
            if serial is None:
                serial = next(self.outgoing_serial)
            await self.socket.send_all(message.serialise(serial))

    async def receive(self) -> Message:
        """Return the next available message from the connection"""
        while True:
            msg = self.parser.get_next_message()
            if msg is not None:
                return msg

            b = await self.socket.receive_some()
            if not b:
                raise trio.EndOfChannel("Socket closed at the other end")
            self.parser.add_data(b)

    async def aclose(self):
        """Close the D-Bus connection"""
        await self.socket.aclose()

    def router(self):
        """Temporarily wrap this connection as a :class:`DBusRouter`

        To be used like::

            async with conn.router() as req:
                reply = await req.send_and_get_reply(msg)

        While the router is running, you shouldn't use :meth:`receive`.
        Once the router is closed, you can use the plain connection again.
        """
        return DBusRouter(self)
Ejemplo n.º 5
0
    def __init__(self, bus_addr):
        self.auth_parser = SASLParser()
        self.parser = Parser()
        self.router = Router(Future)
        self.authentication = Future()
        self.unique_name = None

        self._sock = socket.socket(family=socket.AF_UNIX)
        self.stream = IOStream(self._sock, read_chunk_size=4096)

        def connected():
            self.stream.write(b'\0' + make_auth_external())

        self.stream.connect(bus_addr, connected)
        self.stream.read_until_close(streaming_callback=self.data_received)
Ejemplo n.º 6
0
class DBusConnection:
    def __init__(self, stream: IOStream):
        self.stream = stream
        self.parser = Parser()
        self.outgoing_serial = count(start=1)
        self.unique_name = None

    async def send(self, message: Message, *, serial=None):
        if serial is None:
            serial = next(self.outgoing_serial)
        # .write() immediately adds all the data to a buffer, so no lock needed
        await self.stream.write(message.serialise(serial))

    async def receive(self) -> Message:
        while True:
            msg = self.parser.get_next_message()
            if msg is not None:
                return msg

            b = await self.stream.read_bytes(4096, partial=True)
            self.parser.add_data(b)

    def close(self):
        self.stream.close()
Ejemplo n.º 7
0
class DBusConnection:
    def __init__(self, sock):
        self.sock = sock
        self.parser = Parser()
        self.router = Router(_Future)
        self.bus_proxy = Proxy(message_bus, self)
        hello_reply = self.bus_proxy.Hello()
        self.unique_name = hello_reply[0]

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        return False

    def send_message(self, message):
        future = self.router.outgoing(message)
        data = message.serialise()
        self.sock.sendall(data)
        return future

    def recv_messages(self):
        """Read data from the socket and handle incoming messages.
        
        Blocks until at least one message has been read.
        """
        while True:
            b = self.sock.recv(4096)
            msgs = self.parser.feed(b)
            if msgs:
                for msg in msgs:
                    self.router.incoming(msg)
                return

    def send_and_get_reply(self, message):
        """Send a message, wait for the reply and return it.
        """
        future = self.send_message(message)
        while not future.done():
            self.recv_messages()

        return future.result()

    def close(self):
        self.sock.close()
Ejemplo n.º 8
0
class DBusConnection:
    def __init__(self, bus_addr):
        self.auth_parser = SASLParser()
        self.parser = Parser()
        self.router = Router(Future)
        self.authentication = Future()
        self.unique_name = None

        self._sock = socket.socket(family=socket.AF_UNIX)
        self.stream = IOStream(self._sock, read_chunk_size=4096)

        def connected():
            self.stream.write(b'\0' + make_auth_external())

        self.stream.connect(bus_addr, connected)
        self.stream.read_until_close(streaming_callback=self.data_received)

    def _authenticated(self):
        self.stream.write(BEGIN)
        self.authentication.set_result(True)
        self.data_received_post_auth(self.auth_parser.buffer)

    def data_received(self, data):
        if self.authentication.done():
            return self.data_received_post_auth(data)

        self.auth_parser.feed(data)
        if self.auth_parser.authenticated:
            self._authenticated()
        elif self.auth_parser.error:
            self.authentication.set_exception(AuthenticationError(self.auth_parser.error))

    def data_received_post_auth(self, data):
        for msg in self.parser.feed(data):
            self.router.incoming(msg)

    def send_message(self, message):
        if not self.authentication.done():
            raise RuntimeError("Wait for authentication before sending messages")

        future = self.router.outgoing(message)
        data = message.serialise()
        self.stream.write(data)
        return future
Ejemplo n.º 9
0
class DBusProtocol(asyncio.Protocol):
    def __init__(self):
        self.auth_parser = SASLParser()
        self.parser = Parser()
        self.router = Router(asyncio.Future)
        self.authentication = asyncio.Future()
        self.unique_name = None

    def connection_made(self, transport):
        self.transport = transport
        self.transport.write(b'\0' + make_auth_external())

    def _authenticated(self):
        self.transport.write(BEGIN)
        self.authentication.set_result(True)
        self.data_received = self.data_received_post_auth
        self.data_received(self.auth_parser.buffer)

    def data_received(self, data):
        self.auth_parser.feed(data)
        if self.auth_parser.authenticated:
            self._authenticated()
        elif self.auth_parser.error:
            self.authentication.set_exception(
                ValueError(self.auth_parser.error))

    def data_received_post_auth(self, data):
        for msg in self.parser.feed(data):
            self.router.incoming(msg)

    def send_message(self, message):
        if not self.authentication.done():
            raise RuntimeError(
                "Wait for authentication before sending messages")

        future = self.router.outgoing(message)
        data = message.serialise()
        self.transport.write(data)
        return future
Ejemplo n.º 10
0
 def __init__(self, stream: IOStream):
     self.stream = stream
     self.parser = Parser()
     self.outgoing_serial = count(start=1)
     self.unique_name = None
Ejemplo n.º 11
0
class DBusConnection(Channel):
    """A plain D-Bus connection with no matching of replies.

    This doesn't run any separate tasks: sending and receiving are done in
    the task that calls those methods. It's suitable for implementing servers:
    several worker tasks can receive requests and send replies.
    For a typical client pattern, see :class:`DBusRouter`.

    Implements trio's channel interface for Message objects.
    """
    def __init__(self, socket, enable_fds=False):
        self.socket = socket
        self.enable_fds = enable_fds
        self.parser = Parser()
        self.outgoing_serial = count(start=1)
        self.unique_name = None
        self.send_lock = trio.Lock()
        self.recv_lock = trio.Lock()
        self._leftover_to_send = None  # type: Optional[memoryview]

    async def send(self, message: Message, *, serial=None):
        """Serialise and send a :class:`~.Message` object"""
        async with self.send_lock:
            if serial is None:
                serial = next(self.outgoing_serial)
            fds = array.array('i') if self.enable_fds else None
            data = message.serialise(serial, fds=fds)
            await self._send_data(data, fds)

    # _send_data is copied & modified from trio's SocketStream.send_all() .
    # See above for the MIT license.
    async def _send_data(self, data: bytes, fds):
        if self.socket.did_shutdown_SHUT_WR:
            raise trio.ClosedResourceError("can't send data after sending EOF")

        with _translate_socket_errors_to_stream_errors():
            if self._leftover_to_send:
                # A previous message was partly sent - finish sending it now.
                await self._send_remainder(self._leftover_to_send)

            with memoryview(data) as data:
                if fds:
                    sent = await self.socket.sendmsg([data], [
                        (trio.socket.SOL_SOCKET, trio.socket.SCM_RIGHTS, fds)
                    ])
                else:
                    sent = await self.socket.send(data)

                await self._send_remainder(data, sent)

    async def _send_remainder(self, data: memoryview, already_sent=0):
        try:
            while already_sent < len(data):
                with data[already_sent:] as remaining:
                    sent = await self.socket.send(remaining)
                already_sent += sent
            self._leftover_to_send = None
        except trio.Cancelled:
            # Sending cancelled mid-message. Keep track of the remaining data
            # so it can be sent before the next message, otherwise the next
            # message won't be recognised.
            self._leftover_to_send = data[already_sent:]
            raise

    async def receive(self) -> Message:
        """Return the next available message from the connection"""
        async with self.recv_lock:
            while True:
                msg = self.parser.get_next_message()
                if msg is not None:
                    return msg

                # Once data is read, it must be given to the parser with no
                # checkpoints (where the task could be cancelled).
                b, fds = await self._read_data()
                if not b:
                    raise trio.EndOfChannel("Socket closed at the other end")
                self.parser.add_data(b, fds)

    async def _read_data(self):
        if self.enable_fds:
            nbytes = self.parser.bytes_desired()
            with _translate_socket_errors_to_stream_errors():
                data, ancdata, flags, _ = await self.socket.recvmsg(
                    nbytes, fds_buf_size())
            if flags & getattr(trio.socket, 'MSG_CTRUNC', 0):
                self._close()
                raise RuntimeError("Unable to receive all file descriptors")
            return data, FileDescriptor.from_ancdata(ancdata)

        else:  # not self.enable_fds
            with _translate_socket_errors_to_stream_errors():
                data = await self.socket.recv(4096)
            return data, []

    def _close(self):
        self.socket.close()
        self._leftover_to_send = None

    # Our closing is currently sync, but AsyncResource objects must have aclose
    async def aclose(self):
        """Close the D-Bus connection"""
        self._close()

    @asynccontextmanager
    async def router(self):
        """Temporarily wrap this connection as a :class:`DBusRouter`

        To be used like::

            async with conn.router() as req:
                reply = await req.send_and_get_reply(msg)

        While the router is running, you shouldn't use :meth:`receive`.
        Once the router is closed, you can use the plain connection again.
        """
        async with trio.open_nursery() as nursery:
            router = DBusRouter(self)
            await router.start(nursery)
            try:
                yield router
            finally:
                await router.aclose()
Ejemplo n.º 12
0
 def __init__(self):
     self.auth_parser = SASLParser()
     self.parser = Parser()
     self.router = Router(asyncio.Future)
     self.authentication = asyncio.Future()
     self.unique_name = None
Ejemplo n.º 13
0
 def __init__(self, socket: trio.SocketStream):
     self.socket = socket
     self.parser = Parser()
     self.outgoing_serial = count(start=1)
     self.unique_name = None
     self.send_lock = trio.Lock()
Ejemplo n.º 14
0
class DBusConnection(znc.Socket):
    """Connection to the Signal host's message bus

    Currently, only TCP is supported.
    """
    from .commonweal import put_issuer

    def Init(self, *args, **kwargs):
        self.module = self.GetModule()
        self.debug = self.module.debug
        self.logger = get_logger(self.__class__.__name__)
        # Well-known name (org.asamk.Signal) registered with message bus
        self.has_service = False
        #
        self.__dict__.update(kwargs)
        self.unique_name = None
        #
        self.auth_parser = SASLParserAnonAuth()
        self.parser = Parser()
        #
        FakeFuture.fake_loop = FakeLoop(self.module)
        self.router = Router(FakeFuture)
        #
        if self.debug:
            from jeepney.low_level import HeaderFields

            def on_unhandled(msg):
                member = msg.header.fields[HeaderFields.member]
                if member == "NameAcquired":
                    # This fires before the "Hello" reply callback
                    log_msg = f"Received routine opening signal: {member!r}; "
                else:
                    log_msg = "See 'data_received' entry above for contents"
                self.logger.debug(log_msg)

            self.router.on_unhandled = on_unhandled
        self.authentication = FakeFuture()
        # FIXME explain why this appears twice (see above)
        self.unique_name = None

    def check_subscription(self, service_name=None, member=None):
        """Check if a 'signal-received' callback has been registered

        Without ``member``, return True if any subscriptions exist for
        object described by ``service_name``.
        """
        if service_name is None:
            return bool(self.router.signal_callbacks)
        service = get_msggen(service_name)
        if member:
            key = (service.object_path, service.interface, member)
            return key in self.router.signal_callbacks
        else:
            key = (service.object_path, service.interface)
            return any(k[:-1] == key for k in self.router.signal_callbacks)

    def remove_subscription(self, service_name=None, member=None):
        """Remove a 'signal-received' callback

        Without ``member``, remove all subscriptions registered to
        object described by ``service_name``.
        """
        if service_name is None:
            self.router.signal_callbacks.clear()
            return
        service = get_msggen(service_name)
        if member:
            key = (service.object_path, service.interface, member)
            if key in self.router.signal_callbacks:
                del self.router.signal_callbacks[key]
        else:
            key = (service.object_path, service.interface)
            for k in tuple(self.router.signal_callbacks):
                if k[:-1] == key:
                    del self.router.signal_callbacks[k]

    def add_subscription(self, service_name, member, callback):
        """Add a 'signal-received' callback"""
        service = get_msggen(service_name)
        self.router.subscribe_signal(callback=callback,
                                     path=service.object_path,
                                     interface=service.interface,
                                     member=member)

    def subscribe_incoming(self):
        """Register handler for incoming Signal messages"""
        self.put_issuer("Signal service found")
        self.has_service = True
        #
        if not self.module.config or not self.module.config.settings["obey"]:
            return
        #
        def watch_message_received_cb(msg_body):  # noqa: E306
            if self.debug:
                assert isinstance(msg_body, tuple)
            try:
                self.module.handle_incoming(incoming_NT(*msg_body))
            except Exception:
                self.module.print_traceback()

        #
        def add_message_received_cb():  # noqa: E306
            self.put_issuer("Subscribed to incoming Signal messages")
            if self.debug:
                self.logger.debug("Registering signal callback for "
                                  "'MessageReceived' on 'Signal'")
            self.add_subscription("Signal", "MessageReceived",
                                  watch_message_received_cb)

        #
        if self.debug:
            self.logger.debug("Adding match rule for 'MessageReceived'")
        try:
            self.module.do_subscribe("Signal", "MessageReceived",
                                     add_message_received_cb)
        except Exception:
            self.module.print_traceback()

    def ensure_service(self):
        """Query message bus for Signal service, act accordingly

        For now, this just waits for an announcement of `name
        acquisition`__, then resumes the normal subscription sequence.

        .. __: https://dbus.freedesktop.org/doc/dbus-specification.html
           #bus-messages-name-owner-changed
        """
        service_name = get_msggen("Signal").bus_name
        member = "NameOwnerChanged"

        #
        def watch_name_acquired_cb(msg_body):  # noqa: E306
            if self.debug:
                assert type(msg_body) is tuple
                assert len(msg_body) == 3
                assert all(type(s) is str for s in msg_body)
            if msg_body[0] == service_name:
                self.remove_subscription("DBus", member)
                self.module.do_subscribe("DBus",
                                         member,
                                         remove_name_owner_changed_cb,
                                         remove=True)

        #
        def remove_name_owner_changed_cb():  # noqa: E306
            if self.debug:
                self.logger.debug("Cancelled subscription for "
                                  f"{member} on 'DBus'")
            self.subscribe_incoming()

        #
        def add_name_owner_changed_cb():  # noqa: E306
            if self.debug:
                self.logger.debug("Registering signal callback for "
                                  f"{member} on 'DBus'")
            self.add_subscription("DBus", member, watch_name_acquired_cb)

        #
        def name_has_owner_cb(result):  # noqa: E306
            if self.debug:
                assert type(result) is int
            if result:
                self.subscribe_incoming()
            else:
                self.put_issuer("Waiting for Signal service...")
                self.module.do_subscribe("DBus", member,
                                         add_name_owner_changed_cb)

        #
        wrapped = self.module.make_generic_callback(name_has_owner_cb)
        self.module.do_send("DBus",
                            "NameHasOwner",
                            wrapped,
                            args=(service_name, ))

    def _open_session(self):
        bus = Proxy(get_msggen("DBus"), self)
        hello_reply = bus.Hello()

        def hello_cb(fut):
            if self.debug:
                self.logger.debug("Got hello reply: %r" % fut)
            self.unique_name = fut.result()[0]
            self.put_issuer("Registered with message bus; session id is: %r" %
                            self.unique_name)
            self.ensure_service()

        if self.debug:
            self.logger.debug("Waiting for hello reply: %r" % hello_reply)
        #
        hello_reply.add_done_callback(hello_cb)

    def _authenticated(self):
        self.WriteBytes(BEGIN)
        self.authentication.set_result(True)
        self.data_received = self.data_received_post_auth
        self.data_received(self.auth_parser.buffer)
        if self.debug:
            self.logger.debug("D-Bus connection authenticated")
        self._open_session()

    def data_received(self, data):
        self.auth_parser.feed(data)
        if self.auth_parser.authenticated:
            self._authenticated()
        elif self.auth_parser.error:
            self.authentication.set_exception(
                ValueError(self.auth_parser.error))
        elif self.auth_parser.rejected is not None:
            if b"ANONYMOUS" in self.auth_parser.rejected:
                self.WriteBytes(make_auth_anonymous())
                self.auth_parser.rejected = None
            else:
                self.auth_parser.error = self.auth_parser.rejected

    def format_debug_msg(self, msg):
        from .ootil import OrderedPrettyPrinter as OrdPP
        from jeepney.low_level import Message
        if not hasattr(self, "_opp"):
            self._opp = OrdPP(width=72)
        if not isinstance(msg, Message):
            return self._opp.pformat(msg)
        header = msg.header
        data = {
            "header": (header.endianness, header.message_type,
                       dict(
                           flags=header.flags,
                           version=header.protocol_version,
                           length=header.body_length,
                           serial=header.serial,
                       ), {
                           "fields":
                           {k.name: v
                            for k, v in header.fields.items()}
                       }),
            "body":
            msg.body
        }
        return self._opp.pformat(data)

    def data_received_post_auth(self, data):
        if self.debug:
            num_futs = len(self.router.awaiting_reply)
            log_msg = [f"Futures awaiting reply: {num_futs}"]
        for msg in self.parser.feed(data):
            self.router.incoming(msg)
            if self.debug:
                log_msg.append(self.format_debug_msg(msg))
        if self.debug:
            self.logger.debug("\n".join(log_msg))

    def send_message(self, message):
        if not self.authentication.done():
            # TODO remove if unable to trigger this
            raise RuntimeError("Wait for authentication before sending "
                               "messages")
        future = self.router.outgoing(message)
        data = message.serialise()
        # Logging must happen here, after:
        #   1. Router increments serial cookie
        #   2. Message.serialize() updates the header w. correct body length
        if self.debug:
            num_futs = len(self.router.awaiting_reply)
            log_msg = self.format_debug_msg(message)
            self.logger.debug(f"Futures awaiting reply: {num_futs}\n{log_msg}")
        self.WriteBytes(data)
        return future

    def OnConnected(self):
        self.WriteBytes(b'\0' + make_auth_external())
        self.put_issuer("Connected to: %s:%s" % self.bus_addr)
        self.SetSockName("DBus proxy to signal server")
        self.SetTimeout(0)

    def OnReadData(self, data):
        self.data_received(data)

    def OnDisconnected(self):
        self.put_issuer("Disconnected from %s:%s for session %r" %
                        (*self.bus_addr, self.unique_name))

    def OnShutdown(self):
        name = self.GetSockName()
        if self.debug:
            try:
                self.logger.debug("%r shutting down" % name)
            except ValueError as exc:
                # Only occurs when disconnect teardown is interrupted
                if "operation on closed file" not in repr(exc):
                    raise