Beispiel #1
0
class FrozenURI(BaseURI):
    user = WriteOnceAttribute()
    host = WriteOnceAttribute()
    resource = WriteOnceAttribute()

    def __hash__(self):
        return hash((self.user, self.host, self.resource))
Beispiel #2
0
class RFBSettings(object):
    depth = WriteOnceAttribute()
    quality = WriteOnceAttribute()
    compression = WriteOnceAttribute()
    encodings = WriteOnceAttribute()

    def __init__(self, depth, quality, compression, encodings):
        if depth not in (8, 16, 24, 32, None):
            raise ValueError("invalid depth value: %r (should be one of 8, 16, 24, 32 or None)" % depth)
        allowed_levels = range(10)
        if quality not in allowed_levels:
            raise ValueError("invalid quality value: %r (should be between 0..9)" % quality)
        if compression not in allowed_levels:
            raise ValueError("invalid compression value: %r (should be between 0..9)" % compression)
        if not isinstance(encodings, str) or not encodings:
            raise ValueError("invalid encodings value: %r (should be a non-empty string)" % encodings)
        self.depth = depth
        self.quality = quality
        self.compression = compression
        self.encodings = encodings

    def __eq__(self, other):
        if isinstance(other, RFBSettings):
            return (self.depth, self.quality, self.compression, self.encodings) == (other.depth, other.quality, other.compression, other.encodings)
        return NotImplemented

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return '{0.__class__.__name__}(depth={0.depth!r}, quality={0.quality!r}, compression={0.compression!r}, encodings={0.encodings!r})'.format(self)
Beispiel #3
0
class S2XPresenceHandler(object):
    implements(IObserver)

    sip_identity = WriteOnceAttribute()
    xmpp_identity = WriteOnceAttribute()

    def __init__(self, sip_identity, xmpp_identity):
        self.ended = False
        self._sip_subscriptions = []
        self._stanza_cache = {}
        self._pidf = None
        self._xmpp_subscription = None
        self.sip_identity = sip_identity
        self.xmpp_identity = xmpp_identity

    def start(self):
        notification_center = NotificationCenter()
        self._xmpp_subscription = XMPPSubscription(local_identity=self.sip_identity, remote_identity=self.xmpp_identity)
        notification_center.add_observer(self, sender=self._xmpp_subscription)
        self._xmpp_subscription.start()
        notification_center.post_notification('S2XPresenceHandlerDidStart', sender=self)

    def end(self):
        if self.ended:
            return
        notification_center = NotificationCenter()
        if self._xmpp_subscription is not None:
            notification_center.remove_observer(self, sender=self._xmpp_subscription)
            self._xmpp_subscription.end()
            self._xmpp_subscription = None
        while self._sip_subscriptions:
            subscription = self._sip_subscriptions.pop()
            notification_center.remove_observer(self, sender=subscription)
            try:
                subscription.end()
            except SIPCoreError:
                pass
        self.ended = True
        notification_center.post_notification('S2XPresenceHandlerDidEnd', sender=self)

    def add_sip_subscription(self, subscription):
        # If s subscription is received after the handle has ended but before
        # S2XPresenceHandlerDidEnd has been processed we need to ignore it and wait for a retransmission
        # which we will handle by creating a new S2XPresenceHandler
        if self.ended:
            return
        self._sip_subscriptions.append(subscription)
        NotificationCenter().add_observer(self, sender=subscription)
        if self._xmpp_subscription.state == 'active':
            pidf_doc = self._pidf
            content_type = pidf.PIDFDocument.content_type if pidf_doc is not None else None
            try:
                subscription.accept(content_type, pidf_doc)
            except SIPCoreError, e:
                log.warning('Error accepting SIP subscription: %s' % e)
                subscription.end()
        else:
Beispiel #4
0
class OTRTrustedPeer(object):
    fingerprint = WriteOnceAttribute(
    )  # in order to be hashable this needs to be immutable

    def __init__(self, fingerprint, description='', **kw):
        if not isinstance(fingerprint, str):
            raise TypeError("fingerprint must be a string")
        self.fingerprint = fingerprint
        self.description = description
        self.__dict__.update(kw)

    def __hash__(self):
        return hash(self.fingerprint)

    def __eq__(self, other):
        if isinstance(other, OTRTrustedPeer):
            return self.fingerprint == other.fingerprint
        elif isinstance(other, str):
            return self.fingerprint == other
        else:
            return NotImplemented

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return "{0.__class__.__name__}({0.fingerprint!r}, description={0.description!r})".format(
            self)

    def __reduce__(self):
        return self.__class__, (self.fingerprint, ), self.__dict__
Beispiel #5
0
class XMPPIncomingMucSession(object):
    local_identity = WriteOnceAttribute()
    remote_identity = WriteOnceAttribute()

    def __init__(self, local_identity, remote_identity):
        self.local_identity = local_identity
        self.remote_identity = remote_identity
        self.state = None
        self.channel = coros.queue()
        self._proc = None
        from sylk.applications.xmppgateway.xmpp import XMPPManager
        self.xmpp_manager = XMPPManager()

    def start(self):
        NotificationCenter().post_notification('XMPPIncomingMucSessionDidStart', sender=self)
        self._proc = proc.spawn(self._run)
        self.state = 'started'

    def end(self):
        self._proc.kill()
        self._proc = None
        NotificationCenter().post_notification('XMPPIncomingMucSessionDidEnd', sender=self, data=NotificationData(originator='local'))
        self.state = 'terminated'

    def send_message(self, sender, body, html_body, message_id=None):
        # TODO: timestamp?
        message = GroupChatMessage(sender, self.remote_identity, body, html_body, id=message_id)
        self.xmpp_manager.send_muc_stanza(message)

    def _run(self):
        notification_center = NotificationCenter()
        while True:
            item = self.channel.wait()
            if isinstance(item, GroupChatMessage):
                notification_center.post_notification('XMPPIncomingMucSessionGotMessage', sender=self, data=NotificationData(message=item))
            elif isinstance(item, GroupChatSubject):
                notification_center.post_notification('XMPPIncomingMucSessionSubject', sender=self, data=NotificationData(message=item))
            elif isinstance(item, MUCAvailabilityPresence):
                if item.available:
                    nickname = item.recipient.uri.resource
                    notification_center.post_notification('XMPPIncomingMucSessionChangedNickname', sender=self, data=NotificationData(stanza=item, nickname=nickname))
                else:
                    notification_center.post_notification('XMPPIncomingMucSessionDidEnd', sender=self, data=NotificationData(originator='local'))
                    self.state = 'terminated'
                    break
        self._proc = None
Beispiel #6
0
class HTTPURL(str):
    url = WriteOnceAttribute()

    def __init__(self, value):
        url = urllib.parse.urlparse(value)
        if url.scheme not in ('http', 'https'):
            raise ValueError(
                NSLocalizedString(
                    "Illegal HTTP URL scheme (http and https only): %s",
                    "Preference option error") % url.scheme)
        # check port and hostname
        Hostname(url.hostname)
        if url.port is not None:
            if not (0 < url.port < 65536):
                raise ValueError(
                    NSLocalizedString("Illegal port value: %d",
                                      "Preference option error") % url.port)
        self.url = url

    def __getstate__(self):
        return str(self.url.geturl())

    def __setstate__(self, state):
        self.__init__(state)

    def __getitem__(self, index):
        return self.url.__getitem__(index)

    def __getattr__(self, attr):
        if attr in ('scheme', 'netloc', 'path', 'params', 'query', 'fragment',
                    'username', 'password', 'hostname', 'port'):
            return getattr(self.url, attr)
        else:
            raise AttributeError("'%s' object has no attribute '%s'" %
                                 (self.__class__.__name__, attr))

    def __unicode__(self):
        return str(self.url.geturl())
Beispiel #7
0
class ChatSessionHandler(object):
    implements(IObserver)

    sip_identity = WriteOnceAttribute()
    xmpp_identity = WriteOnceAttribute()

    def __init__(self):
        self.started = False
        self.ended = False
        self.sip_session = None
        self.msrp_stream = None
        self._sip_session_timer = None

        self.use_receipts = False
        self.xmpp_session = None
        self._xmpp_message_queue = deque()

        self._pending_msrp_chunks = {}
        self._pending_xmpp_stanzas = {}

    def _get_started(self):
        return self.__dict__['started']

    def _set_started(self, value):
        old_value = self.__dict__.get('started', False)
        self.__dict__['started'] = value
        if not old_value and value:
            NotificationCenter().post_notification('ChatSessionDidStart',
                                                   sender=self)
            self._send_queued_messages()

    started = property(_get_started, _set_started)
    del _get_started, _set_started

    def _get_xmpp_session(self):
        return self.__dict__['xmpp_session']

    def _set_xmpp_session(self, session):
        self.__dict__['xmpp_session'] = session
        if session is not None:
            # Reet SIP session timer in case it's active
            if self._sip_session_timer is not None and self._sip_session_timer.active(
            ):
                self._sip_session_timer.reset(SESSION_TIMEOUT)
            NotificationCenter().add_observer(self, sender=session)
            session.start()
            # Reet SIP session timer in case it's active
            if self._sip_session_timer is not None and self._sip_session_timer.active(
            ):
                self._sip_session_timer.reset(SESSION_TIMEOUT)

    xmpp_session = property(_get_xmpp_session, _set_xmpp_session)
    del _get_xmpp_session, _set_xmpp_session

    @classmethod
    def new_from_sip_session(cls, sip_identity, session):
        instance = cls()
        instance.sip_identity = sip_identity
        instance._start_incoming_sip_session(session)
        return instance

    @classmethod
    def new_from_xmpp_stanza(cls, xmpp_identity, recipient):
        instance = cls()
        instance.xmpp_identity = xmpp_identity
        instance._start_outgoing_sip_session(recipient)
        return instance

    @run_in_green_thread
    def _start_incoming_sip_session(self, session):
        self.sip_session = session
        self.msrp_stream = next(stream for stream in session.proposed_streams
                                if stream.type == 'chat')
        notification_center = NotificationCenter()
        notification_center.add_observer(self, sender=self.sip_session)
        notification_center.add_observer(self, sender=self.msrp_stream)
        self.sip_session.accept([self.msrp_stream])

    @run_in_green_thread
    def _start_outgoing_sip_session(self, target_uri):
        notification_center = NotificationCenter()
        # self.xmpp_identity is our local identity
        from_uri = self.xmpp_identity.uri.as_sip_uri()
        del from_uri.parameters['gr']  # no GRUU in From header
        contact_uri = self.xmpp_identity.uri.as_sip_uri()
        contact_uri.parameters['gr'] = encode_resource(
            contact_uri.parameters['gr'].decode('utf-8'))
        to_uri = target_uri.as_sip_uri()
        lookup = DNSLookup()
        settings = SIPSimpleSettings()
        account = DefaultAccount()
        if account.sip.outbound_proxy is not None:
            uri = SIPURI(
                host=account.sip.outbound_proxy.host,
                port=account.sip.outbound_proxy.port,
                parameters={'transport': account.sip.outbound_proxy.transport})
        else:
            uri = to_uri
        try:
            routes = lookup.lookup_sip_proxy(
                uri, settings.sip.transport_list).wait()
        except DNSLookupError:
            log.warning('DNS lookup error while looking for %s proxy' % uri)
            notification_center.post_notification(
                'ChatSessionDidFail',
                sender=self,
                data=NotificationData(reason='DNS lookup error'))
            return
        self.msrp_stream = MediaStreamRegistry.get('chat')()
        route = routes.pop(0)
        from_header = FromHeader(from_uri)
        to_header = ToHeader(to_uri)
        contact_header = ContactHeader(contact_uri)
        self.sip_session = Session(account)
        notification_center.add_observer(self, sender=self.sip_session)
        notification_center.add_observer(self, sender=self.msrp_stream)
        self.sip_session.connect(from_header,
                                 to_header,
                                 contact_header=contact_header,
                                 route=route,
                                 streams=[self.msrp_stream])

    def end(self):
        if self.ended:
            return
        if self._sip_session_timer is not None and self._sip_session_timer.active(
        ):
            self._sip_session_timer.cancel()
        self._sip_session_timer = None
        notification_center = NotificationCenter()
        if self.sip_session is not None:
            notification_center.remove_observer(self, sender=self.sip_session)
            notification_center.remove_observer(self, sender=self.msrp_stream)
            self.sip_session.end()
            self.sip_session = None
            self.msrp_stream = None
        if self.xmpp_session is not None:
            notification_center.remove_observer(self, sender=self.xmpp_session)
            self.xmpp_session.end()
            self.xmpp_session = None
        self.ended = True
        if self.started:
            notification_center.post_notification('ChatSessionDidEnd',
                                                  sender=self)
        else:
            notification_center.post_notification(
                'ChatSessionDidFail',
                sender=self,
                data=NotificationData(reason='Ended before actually started'))

    def enqueue_xmpp_message(self, message):
        self._xmpp_message_queue.append(message)
        if self.started:
            self._send_queued_messages()

    def _send_queued_messages(self):
        sender = None
        while self._xmpp_message_queue:
            message = self._xmpp_message_queue.popleft()
            if message.body is None:
                continue
            sender_uri = message.sender.uri.as_sip_uri()
            sender_uri.parameters['gr'] = encode_resource(
                sender_uri.parameters['gr'].decode('utf-8'))
            sender = ChatIdentity(sender_uri)
            self.msrp_stream.send_message(message.body,
                                          'text/plain',
                                          sender=sender,
                                          message_id=str(message.id),
                                          notify_progress=message.use_receipt)
        if sender:
            self.msrp_stream.send_composing_indication('idle',
                                                       30,
                                                       sender=sender)

    def _inactivity_timeout(self):
        log.info("Ending SIP session %s due to inactivity" %
                 self.sip_session.call_id)
        self.sip_session.end()

    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_SIPSessionDidStart(self, notification):
        log.info("SIP session %s started" % self.sip_session.call_id)
        self._sip_session_timer = reactor.callLater(SESSION_TIMEOUT,
                                                    self._inactivity_timeout)

        if self.sip_session.direction == 'outgoing':
            # Time to set sip_identity and create the XMPPChatSession
            contact_uri = self.sip_session._invitation.remote_contact_header.uri
            if contact_uri.parameters.get('gr') is not None:
                sip_leg_uri = FrozenURI(contact_uri.user, contact_uri.host,
                                        contact_uri.parameters.get('gr'))
            else:
                tmp = self.sip_session.remote_identity.uri
                sip_leg_uri = FrozenURI(tmp.user, tmp.host,
                                        generate_sylk_resource())
            self.sip_identity = Identity(
                sip_leg_uri, self.sip_session.remote_identity.display_name)
            session = XMPPChatSession(local_identity=self.sip_identity,
                                      remote_identity=self.xmpp_identity)
            self.xmpp_session = session
            # Session is now established on both ends
            self.started = True
            # Try to wakeup XMPP clients
            self.xmpp_session.send_composing_indication('active')
            self.xmpp_session.send_message(' ', 'text/plain')
        else:
            if self.xmpp_session is not None:
                # Session is now established on both ends
                self.started = True
                # Try to wakeup XMPP clients
                self.xmpp_session.send_composing_indication('active')
                self.xmpp_session.send_message(' ', 'text/plain')
            else:
                # Try to wakeup XMPP clients
                sender = self.sip_identity
                tmp = self.sip_session.local_identity.uri
                recipient_uri = FrozenURI(tmp.user, tmp.host)
                recipient = Identity(recipient_uri)
                xmpp_manager = XMPPManager()
                xmpp_manager.send_stanza(
                    ChatMessage(sender, recipient, ' ', 'text/plain'))
                # Send queued messages
                self._send_queued_messages()

    def _NH_SIPSessionDidEnd(self, notification):
        log.info("SIP session %s ended" % self.sip_session.call_id)
        notification.center.remove_observer(self, sender=self.sip_session)
        notification.center.remove_observer(self, sender=self.msrp_stream)
        self.sip_session = None
        self.msrp_stream = None
        self.end()

    def _NH_SIPSessionDidFail(self, notification):
        log.info("SIP session %s failed" % self.sip_session.call_id)
        notification.center.remove_observer(self, sender=self.sip_session)
        notification.center.remove_observer(self, sender=self.msrp_stream)
        self.sip_session = None
        self.msrp_stream = None
        self.end()

    def _NH_SIPSessionNewProposal(self, notification):
        if notification.data.originator == 'remote':
            self.sip_session.reject_proposal()

    def _NH_SIPSessionTransferNewIncoming(self, notification):
        self.sip_session.reject_transfer(403)

    def _NH_ChatStreamGotMessage(self, notification):
        # Notification is sent by the MSRP stream
        message = notification.data.message
        content_type = message.content_type.lower()
        if content_type not in ('text/plain', 'text/html'):
            return
        if content_type == 'text/plain':
            html_body = None
            body = message.content
        else:
            html_body = message.content
            body = None
        if self._sip_session_timer is not None and self._sip_session_timer.active(
        ):
            self._sip_session_timer.reset(SESSION_TIMEOUT)
        chunk = notification.data.chunk
        if self.started:
            self.xmpp_session.send_message(body,
                                           html_body,
                                           message_id=chunk.message_id)
            if self.use_receipts:
                self._pending_msrp_chunks[chunk.message_id] = chunk
            else:
                self.msrp_stream.msrp_session.send_report(chunk, 200, 'OK')
        else:
            sender = self.sip_identity
            recipient_uri = FrozenURI.parse(message.recipients[0].uri)
            recipient = Identity(recipient_uri,
                                 message.recipients[0].display_name)
            xmpp_manager = XMPPManager()
            xmpp_manager.send_stanza(
                ChatMessage(sender, recipient, body, html_body))
            self.msrp_stream.msrp_session.send_report(chunk, 200, 'OK')

    def _NH_ChatStreamGotComposingIndication(self, notification):
        # Notification is sent by the MSRP stream
        if self._sip_session_timer is not None and self._sip_session_timer.active(
        ):
            self._sip_session_timer.reset(SESSION_TIMEOUT)
        if not self.started:
            return
        state = None
        if notification.data.state == 'active':
            state = 'composing'
        elif notification.data.state == 'idle':
            state = 'paused'
        if state is not None:
            self.xmpp_session.send_composing_indication(state)

    def _NH_ChatStreamDidDeliverMessage(self, notification):
        if self.started:
            message = self._pending_xmpp_stanzas.pop(
                notification.data.message_id, None)
            if message is not None:
                self.xmpp_session.send_receipt_acknowledgement(message.id)

    def _NH_ChatStreamDidNotDeliverMessage(self, notification):
        if self.started:
            message = self._pending_xmpp_stanzas.pop(
                notification.data.message_id, None)
            if message is not None:
                self.xmpp_session.send_error(message, 'TODO', [])  # TODO

    def _NH_XMPPChatSessionDidStart(self, notification):
        if self.sip_session is not None:
            # Session is now established on both ends
            self.started = True

    def _NH_XMPPChatSessionDidEnd(self, notification):
        notification.center.remove_observer(self, sender=self.xmpp_session)
        self.xmpp_session = None
        self.end()

    def _NH_XMPPChatSessionGotMessage(self, notification):
        if self.sip_session is None or self.sip_session.state != 'connected':
            self._xmpp_message_queue.append(notification.data.message)
            return
        if self._sip_session_timer is not None and self._sip_session_timer.active(
        ):
            self._sip_session_timer.reset(SESSION_TIMEOUT)
        message = notification.data.message
        sender_uri = message.sender.uri.as_sip_uri()
        del sender_uri.parameters['gr']  # no GRUU in CPIM From header
        sender = ChatIdentity(sender_uri)
        self.use_receipts = message.use_receipt
        if not message.use_receipt:
            notify_progress = False
        else:
            notify_progress = True
            self._pending_xmpp_stanzas[message.id] = message
        # Prefer plaintext
        self.msrp_stream.send_message(message.body,
                                      'text/plain',
                                      sender=sender,
                                      message_id=str(message.id),
                                      notify_progress=notify_progress)
        self.msrp_stream.send_composing_indication('idle', 30, sender=sender)

    def _NH_XMPPChatSessionGotComposingIndication(self, notification):
        if self.sip_session is None or self.sip_session.state != 'connected':
            return
        if self._sip_session_timer is not None and self._sip_session_timer.active(
        ):
            self._sip_session_timer.reset(SESSION_TIMEOUT)
        message = notification.data.message
        state = None
        if message.state == 'composing':
            state = 'active'
        elif message.state == 'paused':
            state = 'idle'
        if state is not None:
            sender_uri = message.sender.uri.as_sip_uri()
            del sender_uri.parameters['gr']  # no GRUU in CPIM From header
            sender = ChatIdentity(sender_uri)
            self.msrp_stream.send_composing_indication(state,
                                                       30,
                                                       sender=sender)
            if message.use_receipt:
                self.xmpp_session.send_receipt_acknowledgement(message.id)

    def _NH_XMPPChatSessionDidDeliverMessage(self, notification):
        chunk = self._pending_msrp_chunks.pop(notification.data.message_id,
                                              None)
        if chunk is not None:
            self.msrp_stream.msrp_session.send_report(chunk, 200, 'OK')

    def _NH_XMPPChatSessionDidNotDeliverMessage(self, notification):
        chunk = self._pending_msrp_chunks.pop(notification.data.message_id,
                                              None)
        if chunk is not None:
            self.msrp_stream.msrp_session.send_report(chunk,
                                                      notification.data.code,
                                                      notification.data.reason)
Beispiel #8
0
class ScreenSharingStream(MSRPStreamBase):
    type = 'screen-sharing'
    priority = 1

    media_type = 'application'
    accept_types = ['application/x-rfb']
    accept_wrapped_types = None

    ServerHandler = InternalVNCServerHandler
    ViewerHandler = InternalVNCViewerHandler

    handler = WriteOnceAttribute()

    def __init__(self, mode):
        if mode not in ('viewer', 'server'):
            raise ValueError("mode should be 'viewer' or 'server' not '%s'" %
                             mode)
        super(ScreenSharingStream, self).__init__(direction='sendrecv')
        self.handler = self.ViewerHandler(
        ) if mode == 'viewer' else self.ServerHandler()
        self.incoming_queue = queue()
        self.outgoing_queue = queue()
        self.msrp_reader_thread = None
        self.msrp_writer_thread = None

    @classmethod
    def new_from_sdp(cls, session, remote_sdp, stream_index):
        remote_stream = remote_sdp.media[stream_index]
        if remote_stream.media != b'application':
            raise UnknownStreamError
        accept_types = remote_stream.attributes.getfirst(b'accept-types', None)
        accept_types = accept_types.decode() if accept_types else None
        if accept_types is None or 'application/x-rfb' not in accept_types.split(
        ):
            raise UnknownStreamError
        expected_transport = 'TCP/TLS/MSRP' if session.account.msrp.transport == 'tls' else 'TCP/MSRP'
        if remote_stream.transport != expected_transport.encode():
            raise InvalidStreamError(
                "expected %s transport in chat stream, got %s" %
                (expected_transport, remote_stream.transport))
        if remote_stream.formats != [b'*']:
            raise InvalidStreamError("wrong format list specified")
        remote_rfbsetup = remote_stream.attributes.getfirst(
            'rfbsetup', 'active')
        remote_rfbsetup = remote_rfbsetup.decode() if remote_rfbsetup else None
        if remote_rfbsetup == 'active':
            stream = cls(mode='server')
        elif remote_rfbsetup == 'passive':
            stream = cls(mode='viewer')
        else:
            raise InvalidStreamError(
                "unknown rfbsetup attribute in the remote screen sharing stream"
            )
        stream.remote_role = remote_stream.attributes.getfirst(
            b'setup', b'active')
        return stream

    def _create_local_media(self, uri_path):
        local_media = super(ScreenSharingStream,
                            self)._create_local_media(uri_path)
        local_media.attributes.append(
            SDPAttribute(b'rfbsetup', self.handler.type.encode()))
        return local_media

    def _msrp_reader(self):
        while True:
            try:
                chunk = self.msrp.read_chunk()
                if chunk.method in (None, 'REPORT'):
                    continue
                elif chunk.method == 'SEND':
                    if chunk.content_type in self.accept_types:
                        self.incoming_queue.send(chunk.data)
                        response = make_response(chunk, 200, 'OK')
                        report = make_report(chunk, 200, 'OK')
                    else:
                        response = make_response(chunk, 415,
                                                 'Invalid Content-Type')
                        report = None
                else:
                    response = make_response(chunk, 501, 'Unknown method')
                    report = None
                if response is not None:
                    self.msrp.write_chunk(response)
                if report is not None:
                    self.msrp.write_chunk(report)
            except Exception as e:
                self.msrp_reader_thread = None  # avoid issues caused by the notification handler killing this greenlet during post_notification
                if self.shutting_down and isinstance(e, ConnectionDone):
                    break
                self._failure_reason = str(e)
                NotificationCenter().post_notification(
                    'MediaStreamDidFail',
                    sender=self,
                    data=NotificationData(context='reading',
                                          reason=self._failure_reason))
                break

    def _msrp_writer(self):
        while True:
            try:
                data = self.outgoing_queue.wait()
                chunk = self.msrp.make_send_request(data=data)
                chunk.add_header(SuccessReportHeader('no'))
                chunk.add_header(FailureReportHeader('partial'))
                chunk.add_header(ContentTypeHeader('application/x-rfb'))
                self.msrp.write_chunk(chunk)
            except Exception as e:
                self.msrp_writer_thread = None  # avoid issues caused by the notification handler killing this greenlet during post_notification
                if self.shutting_down and isinstance(e, ConnectionDone):
                    break
                self._failure_reason = str(e)
                NotificationCenter().post_notification(
                    'MediaStreamDidFail',
                    sender=self,
                    data=NotificationData(context='sending',
                                          reason=self._failure_reason))
                break

    def _NH_MediaStreamDidInitialize(self, notification):
        notification.center.add_observer(self, sender=self.handler)
        self.handler.initialize(self)

    def _NH_MediaStreamDidStart(self, notification):
        self.msrp_reader_thread = spawn(self._msrp_reader)
        self.msrp_writer_thread = spawn(self._msrp_writer)

    def _NH_MediaStreamWillEnd(self, notification):
        notification.center.remove_observer(self, sender=self.handler)
        if self.msrp_reader_thread is not None:
            self.msrp_reader_thread.kill()
            self.msrp_reader_thread = None
        if self.msrp_writer_thread is not None:
            self.msrp_writer_thread.kill()
            self.msrp_writer_thread = None

    def _NH_ScreenSharingHandlerDidFail(self, notification):
        self._failure_reason = notification.data.reason
        notification.center.post_notification('MediaStreamDidFail',
                                              sender=self,
                                              data=notification.data)
Beispiel #9
0
class XMPPSubscription(object):
    local_identity = WriteOnceAttribute()
    remote_identity = WriteOnceAttribute()

    def __init__(self, local_identity, remote_identity):
        self.local_identity = local_identity
        self.remote_identity = remote_identity
        self.state = None
        self.channel = coros.queue()
        self._proc = None
        from sylk.applications.xmppgateway.xmpp import XMPPManager
        self.xmpp_manager = XMPPManager()

    @property
    def state(self):
        return self.__dict__['state']

    @state.setter
    def state(self, new_state):
        prev_state = self.__dict__.get('state', None)
        self.__dict__['state'] = new_state
        if prev_state != new_state:
            NotificationCenter().post_notification(
                'XMPPSubscriptionChangedState',
                sender=self,
                data=NotificationData(prev_state=prev_state, state=new_state))

    def start(self):
        NotificationCenter().post_notification('XMPPSubscriptionDidStart',
                                               sender=self)
        self._proc = proc.spawn(self._run)
        self.subscribe()

    def end(self):
        if self.state == 'terminated':
            return
        self._proc.kill()
        self._proc = None
        NotificationCenter().post_notification(
            'XMPPSubscriptionDidEnd',
            sender=self,
            data=NotificationData(originator='local'))
        self.state = 'terminated'

    def subscribe(self):
        self.state = 'subscribe_sent'
        stanza = SubscriptionPresence(self.local_identity,
                                      self.remote_identity, 'subscribe')
        self.xmpp_manager.send_stanza(stanza)
        # If we are already subscribed we may not receive an answer, send a probe just in case
        self._send_probe()

    def unsubscribe(self):
        self.state = 'unsubscribe_sent'
        stanza = SubscriptionPresence(self.local_identity,
                                      self.remote_identity, 'unsubscribe')
        self.xmpp_manager.send_stanza(stanza)

    def _send_probe(self):
        self.state = 'subscribe_sent'
        stanza = ProbePresence(self.local_identity, self.remote_identity)
        self.xmpp_manager.send_stanza(stanza)

    def _run(self):
        notification_center = NotificationCenter()
        while True:
            item = self.channel.wait()
            if isinstance(item, AvailabilityPresence):
                if self.state == 'subscribe_sent':
                    self.state = 'active'
                notification_center.post_notification(
                    'XMPPSubscriptionGotNotify',
                    sender=self,
                    data=NotificationData(presence=item))
            elif isinstance(item, SubscriptionPresence):
                if self.state == 'subscribe_sent' and item.type == 'subscribed':
                    self.state = 'active'
                elif item.type == 'unsubscribed':
                    prev_state = self.state
                    self.state = 'terminated'
                    if prev_state in ('active', 'unsubscribe_sent'):
                        notification_center.post_notification(
                            'XMPPSubscriptionDidEnd', sender=self)
                    else:
                        notification_center.post_notification(
                            'XMPPSubscriptionDidFail', sender=self)
                    break
        self._proc = None
Beispiel #10
0
class XMPPIncomingSubscription(object):
    local_identity = WriteOnceAttribute()
    remote_identity = WriteOnceAttribute()

    def __init__(self, local_identity, remote_identity):
        self.local_identity = local_identity
        self.remote_identity = remote_identity
        self.state = None
        self.channel = coros.queue()
        self._proc = None
        from sylk.applications.xmppgateway.xmpp import XMPPManager
        self.xmpp_manager = XMPPManager()

    @property
    def state(self):
        return self.__dict__['state']

    @state.setter
    def state(self, new_state):
        prev_state = self.__dict__.get('state', None)
        self.__dict__['state'] = new_state
        if prev_state != new_state:
            NotificationCenter().post_notification(
                'XMPPIncomingSubscriptionChangedState',
                sender=self,
                data=NotificationData(prev_state=prev_state, state=new_state))

    def start(self):
        NotificationCenter().post_notification(
            'XMPPIncomingSubscriptionDidStart', sender=self)
        self._proc = proc.spawn(self._run)

    def end(self):
        if self.state == 'terminated':
            return
        self.state = 'terminated'
        self._proc.kill()
        self._proc = None
        NotificationCenter().post_notification(
            'XMPPIncomingSubscriptionDidEnd',
            sender=self,
            data=NotificationData(originator='local'))

    def accept(self):
        self.state = 'active'
        stanza = SubscriptionPresence(self.local_identity,
                                      self.remote_identity, 'subscribed')
        self.xmpp_manager.send_stanza(stanza)

    def reject(self):
        self.state = 'terminating'
        stanza = SubscriptionPresence(self.local_identity,
                                      self.remote_identity, 'unsubscribed')
        self.xmpp_manager.send_stanza(stanza)
        self.end()

    def send_presence(self, stanza):
        self.xmpp_manager.send_stanza(stanza)

    def _run(self):
        notification_center = NotificationCenter()
        while True:
            item = self.channel.wait()
            if isinstance(item, SubscriptionPresence):
                if item.type == 'subscribe':
                    notification_center.post_notification(
                        'XMPPIncomingSubscriptionGotSubscribe', sender=self)
                elif item.type == 'unsubscribe':
                    self.state = 'terminated'
                    notification_center = NotificationCenter()
                    notification_center.post_notification(
                        'XMPPIncomingSubscriptionGotUnsubscribe', sender=self)
                    notification_center.post_notification(
                        'XMPPIncomingSubscriptionDidEnd',
                        sender=self,
                        data=NotificationData(originator='local'))
                    break
            elif isinstance(item, ProbePresence):
                notification_center = NotificationCenter()
                notification_center.post_notification(
                    'XMPPIncomingSubscriptionGotProbe', sender=self)
        self._proc = None
Beispiel #11
0
class XMPPChatSession(object):
    local_identity = WriteOnceAttribute()
    remote_identity = WriteOnceAttribute()

    def __init__(self, local_identity, remote_identity):
        self.local_identity = local_identity
        self.remote_identity = remote_identity
        self.state = None
        self.pending_receipts = {}
        self.channel = coros.queue()
        self._proc = None
        from sylk.applications.xmppgateway.xmpp import XMPPManager
        self.xmpp_manager = XMPPManager()

    def start(self):
        NotificationCenter().post_notification('XMPPChatSessionDidStart',
                                               sender=self)
        self._proc = proc.spawn(self._run)
        self.state = 'started'

    def end(self):
        self.send_composing_indication('gone')
        self._clear_pending_receipts()
        self._proc.kill()
        self._proc = None
        NotificationCenter().post_notification(
            'XMPPChatSessionDidEnd',
            sender=self,
            data=NotificationData(originator='local'))
        self.state = 'terminated'

    def send_message(self, body, html_body, message_id=None, use_receipt=True):
        message = ChatMessage(self.local_identity,
                              self.remote_identity,
                              body,
                              html_body,
                              id=message_id,
                              use_receipt=use_receipt)
        self.xmpp_manager.send_stanza(message)
        if message_id is not None:
            timer = reactor.callLater(30, self._receipt_timer_expired,
                                      message_id)
            self.pending_receipts[message_id] = timer
            NotificationCenter().post_notification(
                'XMPPChatSessionDidSendMessage',
                sender=self,
                data=NotificationData(message=message))

    def send_composing_indication(self,
                                  state,
                                  message_id=None,
                                  use_receipt=False):
        message = ChatComposingIndication(self.local_identity,
                                          self.remote_identity,
                                          state,
                                          id=message_id,
                                          use_receipt=use_receipt)
        self.xmpp_manager.send_stanza(message)
        if message_id is not None:
            timer = reactor.callLater(30, self._receipt_timer_expired,
                                      message_id)
            self.pending_receipts[message_id] = timer
            NotificationCenter().post_notification(
                'XMPPChatSessionDidSendMessage',
                sender=self,
                data=NotificationData(message=message))

    def send_receipt_acknowledgement(self, receipt_id):
        message = MessageReceipt(self.local_identity, self.remote_identity,
                                 receipt_id)
        self.xmpp_manager.send_stanza(message)

    def send_error(self, stanza, error_type, conditions):
        message = ErrorStanza.from_stanza(stanza, error_type, conditions)
        self.xmpp_manager.send_stanza(message)

    def _run(self):
        notification_center = NotificationCenter()
        while True:
            item = self.channel.wait()
            if isinstance(item, ChatMessage):
                notification_center.post_notification(
                    'XMPPChatSessionGotMessage',
                    sender=self,
                    data=NotificationData(message=item))
            elif isinstance(item, ChatComposingIndication):
                if item.state == 'gone':
                    self._clear_pending_receipts()
                    notification_center.post_notification(
                        'XMPPChatSessionDidEnd',
                        sender=self,
                        data=NotificationData(originator='remote'))
                    self.state = 'terminated'
                    break
                else:
                    notification_center.post_notification(
                        'XMPPChatSessionGotComposingIndication',
                        sender=self,
                        data=NotificationData(message=item))
            elif isinstance(item, MessageReceipt):
                if item.receipt_id in self.pending_receipts:
                    timer = self.pending_receipts.pop(item.receipt_id)
                    timer.cancel()
                    notification_center.post_notification(
                        'XMPPChatSessionDidDeliverMessage',
                        sender=self,
                        data=NotificationData(message_id=item.receipt_id))
            elif isinstance(item, ErrorStanza):
                if item.id in self.pending_receipts:
                    timer = self.pending_receipts.pop(item.id)
                    timer.cancel()
                    # TODO: translate cause
                    notification_center.post_notification(
                        'XMPPChatSessionDidNotDeliverMessage',
                        sender=self,
                        data=NotificationData(message_id=item.id,
                                              code=503,
                                              reason='Service Unavailable'))
        self._proc = None

    def _receipt_timer_expired(self, message_id):
        self.pending_receipts.pop(message_id)
        NotificationCenter().post_notification(
            'XMPPChatSessionDidNotDeliverMessage',
            sender=self,
            data=NotificationData(message_id=message_id,
                                  code=408,
                                  reason='Timeout'))

    def _clear_pending_receipts(self):
        notification_center = NotificationCenter()
        while self.pending_receipts:
            message_id, timer = self.pending_receipts.popitem()
            timer.cancel()
            notification_center.post_notification(
                'XMPPChatSessionDidNotDeliverMessage',
                sender=self,
                data=NotificationData(message_id=message_id,
                                      code=408,
                                      reason='Timeout'))
Beispiel #12
0
class X2SMucHandler(object):
    implements(IObserver)

    sip_identity = WriteOnceAttribute()
    xmpp_identity = WriteOnceAttribute()

    def __init__(self, sip_identity, xmpp_identity, nickname):
        self.sip_identity = sip_identity
        self.xmpp_identity = xmpp_identity
        self.nickname = nickname
        self._xmpp_muc_session = None
        self._sip_session = None
        self._msrp_stream = None
        self._first_stanza = None
        self._pending_nicknames_map = {
        }  # map message ID of MSRP NICKNAME chunk to corresponding stanza
        self._pending_messages_map = {
        }  # map message ID of MSRP SEND chunk to corresponding stanza
        self._participants = set()  # set of (URI, nickname) tuples
        self.ended = False

    def start(self):
        notification_center = NotificationCenter()
        self._xmpp_muc_session = XMPPIncomingMucSession(
            local_identity=self.sip_identity,
            remote_identity=self.xmpp_identity)
        notification_center.add_observer(self, sender=self._xmpp_muc_session)
        self._xmpp_muc_session.start()
        notification_center.post_notification('X2SMucHandlerDidStart',
                                              sender=self)
        self._start_sip_session()

    def end(self):
        if self.ended:
            return
        notification_center = NotificationCenter()
        if self._xmpp_muc_session is not None:
            notification_center.remove_observer(self,
                                                sender=self._xmpp_muc_session)
            # Send indication that the user has been kicked from the room
            sender = Identity(
                FrozenURI(self.sip_identity.uri.user,
                          self.sip_identity.uri.host, self.nickname))
            stanza = MUCAvailabilityPresence(sender,
                                             self.xmpp_identity,
                                             available=False)
            stanza.jid = self.xmpp_identity
            stanza.muc_statuses.append('307')
            xmpp_manager = XMPPManager()
            xmpp_manager.send_muc_stanza(stanza)
            self._xmpp_muc_session.end()
            self._xmpp_muc_session = None
        if self._sip_session is not None:
            notification_center.remove_observer(self, sender=self._sip_session)
            self._sip_session.end()
            self._sip_session = None
        self.ended = True
        notification_center.post_notification('X2SMucHandlerDidEnd',
                                              sender=self)

    @run_in_green_thread
    def _start_sip_session(self):
        # self.xmpp_identity is our local identity
        from_uri = self.xmpp_identity.uri.as_sip_uri()
        del from_uri.parameters['gr']  # no GRUU in From header
        contact_uri = self.xmpp_identity.uri.as_sip_uri()
        contact_uri.parameters['gr'] = encode_resource(
            contact_uri.parameters['gr'].decode('utf-8'))
        to_uri = self.sip_identity.uri.as_sip_uri()
        lookup = DNSLookup()
        settings = SIPSimpleSettings()
        account = DefaultAccount()
        if account.sip.outbound_proxy is not None:
            uri = SIPURI(
                host=account.sip.outbound_proxy.host,
                port=account.sip.outbound_proxy.port,
                parameters={'transport': account.sip.outbound_proxy.transport})
        else:
            uri = to_uri
        try:
            routes = lookup.lookup_sip_proxy(
                uri, settings.sip.transport_list).wait()
        except DNSLookupError:
            log.warning('DNS lookup error while looking for %s proxy' % uri)
            self.end()
            return
        self._msrp_stream = MediaStreamRegistry.get('chat')()
        route = routes.pop(0)
        from_header = FromHeader(from_uri)
        to_header = ToHeader(to_uri)
        contact_header = ContactHeader(contact_uri)
        self._sip_session = Session(account)
        notification_center = NotificationCenter()
        notification_center.add_observer(self, sender=self._sip_session)
        notification_center.add_observer(self, sender=self._msrp_stream)
        self._sip_session.connect(from_header,
                                  to_header,
                                  contact_header=contact_header,
                                  route=route,
                                  streams=[self._msrp_stream])

    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_SIPSessionDidStart(self, notification):
        log.info("SIP multiparty session %s started" %
                 self._sip_session.call_id)
        if not self._sip_session.remote_focus or not self._msrp_stream.nickname_allowed:
            self.end()
            return
        message_id = self._msrp_stream.set_local_nickname(self.nickname)
        self._pending_nicknames_map[message_id] = (self.nickname,
                                                   self._first_stanza)
        self._first_stanza = None

    def _NH_SIPSessionDidEnd(self, notification):
        log.info("SIP multiparty session %s ended" % self._sip_session.call_id)
        notification.center.remove_observer(self, sender=self._sip_session)
        notification.center.remove_observer(self, sender=self._msrp_stream)
        self._sip_session = None
        self._msrp_stream = None
        self.end()

    def _NH_SIPSessionDidFail(self, notification):
        log.info("SIP multiparty session %s failed" %
                 self._sip_session.call_id)
        notification.center.remove_observer(self, sender=self._sip_session)
        notification.center.remove_observer(self, sender=self._msrp_stream)
        self._sip_session = None
        self._msrp_stream = None
        self.end()

    def _NH_SIPSessionNewProposal(self, notification):
        if notification.data.originator == 'remote':
            self._sip_session.reject_proposal()

    def _NH_SIPSessionTransferNewIncoming(self, notification):
        self._sip_session.reject_transfer(403)

    def _NH_SIPSessionGotConferenceInfo(self, notification):
        # Translate to XMPP payload
        xmpp_manager = XMPPManager()
        own_uri = FrozenURI(self.xmpp_identity.uri.user,
                            self.xmpp_identity.uri.host)
        conference_info = notification.data.conference_info
        new_participants = set()
        for user in conference_info.users:
            user_uri = FrozenURI.parse(user.entity if user.entity.startswith((
                'sip:', 'sips:')) else 'sip:' + user.entity)
            nickname = user.display_text.value if user.display_text else user.entity
            new_participants.add((user_uri, nickname))
        # Remove participants that are no longer in the room
        for uri, nickname in self._participants - new_participants:
            sender = Identity(
                FrozenURI(self.sip_identity.uri.user,
                          self.sip_identity.uri.host, nickname))
            stanza = MUCAvailabilityPresence(sender,
                                             self.xmpp_identity,
                                             available=False)
            xmpp_manager.send_muc_stanza(stanza)
        # Send presence for current participants
        for uri, nickname in new_participants:
            if uri == own_uri:
                continue
            sender = Identity(
                FrozenURI(self.sip_identity.uri.user,
                          self.sip_identity.uri.host, nickname))
            stanza = MUCAvailabilityPresence(sender,
                                             self.xmpp_identity,
                                             available=True)
            stanza.jid = Identity(uri)
            xmpp_manager.send_muc_stanza(stanza)
        self._participants = new_participants
        # Send own status last
        sender = Identity(
            FrozenURI(self.sip_identity.uri.user, self.sip_identity.uri.host,
                      self.nickname))
        stanza = MUCAvailabilityPresence(sender,
                                         self.xmpp_identity,
                                         available=True)
        stanza.jid = self.xmpp_identity
        stanza.muc_statuses.append('110')
        xmpp_manager.send_muc_stanza(stanza)

    def _NH_ChatStreamGotMessage(self, notification):
        # Notification is sent by the MSRP stream
        if not self._xmpp_muc_session:
            return
        message = notification.data.message
        content_type = message.content_type.lower()
        if content_type not in ('text/plain', 'text/html'):
            return
        if content_type == 'text/plain':
            html_body = None
            body = message.content
        else:
            html_body = message.content
            body = None
        resource = message.sender.display_name or str(message.sender.uri)
        sender = Identity(
            FrozenURI(self.sip_identity.uri.user, self.sip_identity.uri.host,
                      resource))
        self._xmpp_muc_session.send_message(sender,
                                            body,
                                            html_body,
                                            message_id='MUC.' +
                                            uuid.uuid4().hex)
        self._msrp_stream.msrp_session.send_report(notification.data.chunk,
                                                   200, 'OK')

    def _NH_ChatStreamDidSetNickname(self, notification):
        # Notification is sent by the MSRP stream
        nickname, stanza = self._pending_nicknames_map.pop(
            notification.data.message_id)
        self.nickname = nickname

    def _NH_ChatStreamDidNotSetNickname(self, notification):
        # Notification is sent by the MSRP stream
        nickname, stanza = self._pending_nicknames_map.pop(
            notification.data.message_id)
        error_stanza = MUCErrorPresence.from_stanza(stanza, 'cancel',
                                                    [('conflict', STANZAS_NS)])
        xmpp_manager = XMPPManager()
        xmpp_manager.send_muc_stanza(error_stanza)

    def _NH_ChatStreamDidDeliverMessage(self, notification):
        # Echo back the message to the sender
        stanza = self._pending_messages_map.pop(notification.data.message_id)
        stanza.sender, stanza.recipient = stanza.recipient, stanza.sender
        stanza.sender.uri = FrozenURI(stanza.sender.uri.user,
                                      stanza.sender.uri.host, self.nickname)
        xmpp_manager = XMPPManager()
        xmpp_manager.send_muc_stanza(stanza)

    def _NH_ChatStreamDidNotDeliverMessage(self, notification):
        self._pending_messages_map.pop(notification.data.message_id)

    def _NH_XMPPIncomingMucSessionDidEnd(self, notification):
        notification.center.remove_observer(self,
                                            sender=self._xmpp_muc_session)
        self._xmpp_muc_session = None
        self.end()

    def _NH_XMPPIncomingMucSessionGotMessage(self, notification):
        if not self._sip_session:
            return
        message = notification.data.message
        sender_uri = message.sender.uri.as_sip_uri()
        del sender_uri.parameters['gr']  # no GRUU in CPIM From header
        sender = ChatIdentity(sender_uri, display_name=self.nickname)
        message_id = self._msrp_stream.send_message(message.body,
                                                    'text/plain',
                                                    sender=sender)
        self._pending_messages_map[message_id] = message
        # Message will be echoed back to the sender on ChatStreamDidDeliverMessage

    def _NH_XMPPIncomingMucSessionChangedNickname(self, notification):
        if not self._sip_session:
            return
        nickname = notification.data.nickname
        try:
            message_id = self._msrp_stream.set_local_nickname(nickname)
        except ChatStreamError:
            return
        self._pending_nicknames_map[message_id] = (nickname,
                                                   notification.data.stanza)

    def _NH_XMPPIncomingMucSessionSubject(self, notification):
        if not self._sip_session:
            return
        message = notification.data.message
        sender_uri = message.sender.uri.as_sip_uri()
        del sender_uri.parameters['gr']  # no GRUU in CPIM From header
        sender = ChatIdentity(sender_uri, display_name=self.nickname)
        message_id = self._msrp_stream.send_message(
            'Conference title set to: %s' % message.body,
            'text/plain',
            sender=sender)
        self._pending_messages_map[message_id] = message
Beispiel #13
0
class X2SPresenceHandler(object):
    implements(IObserver)

    sip_identity = WriteOnceAttribute()
    xmpp_identity = WriteOnceAttribute()

    def __init__(self, sip_identity, xmpp_identity):
        self.ended = False
        self.sip_identity = sip_identity
        self.xmpp_identity = xmpp_identity
        self.subscribed = False
        self._command_proc = None
        self._command_channel = coros.queue()
        self._data_channel = coros.queue()
        self._sip_subscription = None
        self._sip_subscription_proc = None
        self._sip_subscription_timer = None
        self._xmpp_subscription = None

    def start(self):
        notification_center = NotificationCenter()
        self._xmpp_subscription = XMPPIncomingSubscription(local_identity=self.sip_identity, remote_identity=self.xmpp_identity)
        notification_center.add_observer(self, sender=self._xmpp_subscription)
        self._xmpp_subscription.start()
        self._command_proc = proc.spawn(self._run)
        self._subscribe_sip()
        notification_center.post_notification('X2SPresenceHandlerDidStart', sender=self)

    def end(self):
        if self.ended:
            return
        notification_center = NotificationCenter()
        if self._xmpp_subscription is not None:
            notification_center.remove_observer(self, sender=self._xmpp_subscription)
            self._xmpp_subscription.end()
            self._xmpp_subscription = None
        if self._sip_subscription:
            self._unsubscribe_sip()
        self.ended = True
        notification_center.post_notification('X2SPresenceHandlerDidEnd', sender=self)

    @run_in_green_thread
    def _subscribe_sip(self):
        command = Command('subscribe')
        self._command_channel.send(command)

    @run_in_green_thread
    def _unsubscribe_sip(self):
        command = Command('unsubscribe')
        self._command_channel.send(command)
        command.wait()
        self._command_proc.kill()
        self._command_proc = None

    def _run(self):
        while True:
            command = self._command_channel.wait()
            handler = getattr(self, '_CH_%s' % command.name)
            handler(command)

    def _CH_subscribe(self, command):
        if self._sip_subscription_timer is not None and self._sip_subscription_timer.active():
            self._sip_subscription_timer.cancel()
        self._sip_subscription_timer = None
        if self._sip_subscription_proc is not None:
            subscription_proc = self._sip_subscription_proc
            subscription_proc.kill(InterruptSubscription)
            subscription_proc.wait()
        self._sip_subscription_proc = proc.spawn(self._sip_subscription_handler, command)

    def _CH_unsubscribe(self, command):
        # Cancel any timer which would restart the subscription process
        if self._sip_subscription_timer is not None and self._sip_subscription_timer.active():
            self._sip_subscription_timer.cancel()
        self._sip_subscription_timer = None
        if self._sip_subscription_proc is not None:
            subscription_proc = self._sip_subscription_proc
            subscription_proc.kill(TerminateSubscription)
            subscription_proc.wait()
            self._sip_subscription_proc = None
        command.signal()

    def _process_pidf(self, body):
        try:
            pidf_doc = pidf.PIDF.parse(body)
        except ParserError, e:
            log.warn('Error parsing PIDF document: %s' % e)
            return
        # Build XML stanzas out of PIDF documents
        try:
            person = next(p for p in pidf_doc.persons)
        except StopIteration:
            person = None
        for service in pidf_doc.services:
            sip_contact = self.sip_identity.uri.as_sip_uri()
            if service.device_info is not None:
                sip_contact.parameters['gr'] = 'urn:uuid:%s' % service.device_info.id
            else:
                sip_contact.parameters['gr'] = service.id
            sender = Identity(FrozenURI.parse(sip_contact))
            if service.status.extended is not None:
                available = service.status.extended != 'offline'
            else:
                available = service.status.basic == 'open'
            stanza = AvailabilityPresence(sender, self.xmpp_identity, available)
            for note in service.notes:
                stanza.statuses[note.lang] = note
            if service.status.extended is not None:
                if service.status.extended == 'away':
                    stanza.show = 'away'
                elif service.status.extended == 'busy':
                    stanza.show = 'dnd'
            elif person is not None and person.activities is not None:
                activities = set(list(person.activities))
                if 'away' in activities:
                    stanza.show = 'away'
                elif {'holiday', 'vacation'}.intersection(activities):
                    stanza.show = 'xa'
                elif 'busy' in activities:
                    stanza.show = 'dnd'
            self._xmpp_subscription.send_presence(stanza)
Beispiel #14
0
class S2XPresenceHandler(object):
    implements(IObserver)

    sip_identity = WriteOnceAttribute()
    xmpp_identity = WriteOnceAttribute()

    def __init__(self, sip_identity, xmpp_identity):
        self.ended = False
        self._sip_subscriptions = []
        self._stanza_cache = {}
        self._pidf = None
        self._xmpp_subscription = None
        self.sip_identity = sip_identity
        self.xmpp_identity = xmpp_identity

    def start(self):
        notification_center = NotificationCenter()
        self._xmpp_subscription = XMPPSubscription(
            local_identity=self.sip_identity,
            remote_identity=self.xmpp_identity)
        notification_center.add_observer(self, sender=self._xmpp_subscription)
        self._xmpp_subscription.start()
        notification_center.post_notification('S2XPresenceHandlerDidStart',
                                              sender=self)

    def end(self):
        if self.ended:
            return
        notification_center = NotificationCenter()
        if self._xmpp_subscription is not None:
            notification_center.remove_observer(self,
                                                sender=self._xmpp_subscription)
            self._xmpp_subscription.end()
            self._xmpp_subscription = None
        while self._sip_subscriptions:
            subscription = self._sip_subscriptions.pop()
            notification_center.remove_observer(self, sender=subscription)
            try:
                subscription.end()
            except SIPCoreError:
                pass
        self.ended = True
        notification_center.post_notification('S2XPresenceHandlerDidEnd',
                                              sender=self)

    def add_sip_subscription(self, subscription):
        # If s subscription is received after the handle has ended but before
        # S2XPresenceHandlerDidEnd has been processed we need to ignore it and wait for a retransmission
        # which we will handle by creating a new S2XPresenceHandler
        if self.ended:
            return
        self._sip_subscriptions.append(subscription)
        NotificationCenter().add_observer(self, sender=subscription)
        if self._xmpp_subscription.state == 'active':
            pidf_doc = self._pidf
            content_type = pidf.PIDFDocument.content_type if pidf_doc is not None else None
            try:
                subscription.accept(content_type, pidf_doc)
            except SIPCoreError as e:
                log.warning('Error accepting SIP subscription: %s' % e)
                subscription.end()
        else:
            try:
                subscription.accept_pending()
            except SIPCoreError as e:
                log.warning('Error accepting SIP subscription: %s' % e)
                subscription.end()
        if XMPPGatewayConfig.log_presence:
            log.info(
                'SIP subscription from %s to %s added to presence flow 0x%x (%d subs)'
                % (format_uri(self.sip_identity.uri, 'sip'),
                   format_uri(self.xmpp_identity.uri,
                              'xmpp'), id(self), len(self._sip_subscriptions)))

    def _build_pidf(self):
        if not self._stanza_cache:
            self._pidf = None
            return None
        pidf_doc = pidf.PIDF(str(self.xmpp_identity))
        uri = next(self._stanza_cache.iterkeys())
        person = pidf.Person(
            "PID-%s" % hashlib.md5("%s@%s" % (uri.user, uri.host)).hexdigest())
        person.activities = rpid.Activities()
        pidf_doc.add(person)
        for stanza in self._stanza_cache.itervalues():
            if not stanza.available:
                status = pidf.Status('closed')
                status.extended = 'offline'
            else:
                status = pidf.Status('open')
                if stanza.show == 'away':
                    status.extended = 'away'
                    if 'away' not in person.activities:
                        person.activities.add('away')
                elif stanza.show == 'xa':
                    status.extended = 'away'
                    if 'away' not in person.activities:
                        person.activities.add('away')
                elif stanza.show == 'dnd':
                    status.extended = 'busy'
                    if 'busy' not in person.activities:
                        person.activities.add('busy')
                else:
                    status.extended = 'available'
            if stanza.sender.uri.resource:
                resource = encode_resource(stanza.sender.uri.resource)
            else:
                # Workaround for clients not sending the resource under certain (unknown) circumstances
                resource = hashlib.md5("%s@%s" %
                                       (uri.user, uri.host)).hexdigest()
            service_id = "SID-%s" % resource
            sip_uri = stanza.sender.uri.as_sip_uri()
            sip_uri.parameters['gr'] = resource
            sip_uri.parameters['xmpp'] = None
            contact = pidf.Contact(str(sip_uri))
            service = pidf.Service(service_id, status=status, contact=contact)
            service.add(pidf.DeviceID(resource))
            service.device_info = pidf.DeviceInfo(
                resource, description=stanza.sender.uri.resource)
            service.timestamp = pidf.ServiceTimestamp(stanza.timestamp)
            service.capabilities = caps.ServiceCapabilities(text=True,
                                                            message=True)
            for lang, note in stanza.statuses.iteritems():
                service.notes.add(pidf.PIDFNote(note, lang=lang))
            pidf_doc.add(service)
        if not person.activities:
            person.activities = None
        self._pidf = pidf_doc.toxml()
        return self._pidf

    @run_in_twisted_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_SIPIncomingSubscriptionDidEnd(self, notification):
        subscription = notification.sender
        notification.center.remove_observer(self, sender=subscription)
        self._sip_subscriptions.remove(subscription)
        if XMPPGatewayConfig.log_presence:
            log.info(
                'SIP subscription from %s to %s removed from presence flow 0x%x (%d subs)'
                % (format_uri(self.sip_identity.uri, 'sip'),
                   format_uri(self.xmpp_identity.uri,
                              'xmpp'), id(self), len(self._sip_subscriptions)))
        if not self._sip_subscriptions:
            self.end()

    def _NH_SIPIncomingSubscriptionNotifyDidFail(self, notification):
        if XMPPGatewayConfig.log_presence:
            log.info(
                'Sending SIP NOTIFY failed from %s to %s for presence flow 0x%x: %s (%s)'
                % (format_uri(self.xmpp_identity.uri, 'xmpp'),
                   format_uri(self.sip_identity.uri, 'sip'), id(self),
                   notification.data.code, notification.data.reason))

    def _NH_SIPIncomingSubscriptionGotUnsubscribe(self, notification):
        if XMPPGatewayConfig.log_presence:
            log.info(
                'SIP subscription from %s to %s was terminated by user for presence flow 1x%x (%d subs)'
                % (format_uri(self.sip_identity.uri, 'sip'),
                   format_uri(self.xmpp_identity.uri,
                              'xmpp'), id(self), len(self._sip_subscriptions)))

    def _NH_SIPIncomingSubscriptionGotRefreshingSubscribe(self, notification):
        if XMPPGatewayConfig.log_presence:
            log.info(
                'SIP subscription from %s to %s was refreshed for presence flow 0x%x (%d subs)'
                % (format_uri(self.sip_identity.uri, 'sip'),
                   format_uri(self.xmpp_identity.uri,
                              'xmpp'), id(self), len(self._sip_subscriptions)))

    def _NH_SIPIncomingSubscriptionDidTimeout(self, notification):
        if XMPPGatewayConfig.log_presence:
            log.info(
                'SIP subscription from %s to %s timed out for presence flow 0x%x (%d subs)'
                % (format_uri(self.sip_identity.uri, 'sip'),
                   format_uri(self.xmpp_identity.uri,
                              'xmpp'), id(self), len(self._sip_subscriptions)))

    def _NH_XMPPSubscriptionChangedState(self, notification):
        if notification.data.prev_state == 'subscribe_sent' and notification.data.state == 'active':
            pidf_doc = self._pidf
            content_type = pidf.PIDFDocument.content_type if pidf_doc is not None else None
            for subscription in (subscription
                                 for subscription in self._sip_subscriptions
                                 if subscription.state == 'pending'):
                subscription.accept(content_type, pidf_doc)

    def _NH_XMPPSubscriptionGotNotify(self, notification):
        stanza = notification.data.presence
        self._stanza_cache[stanza.sender.uri] = stanza
        stanza.timestamp = ISOTimestamp.now(
        )  # TODO: mirror the one in the stanza, if present
        pidf_doc = self._build_pidf()
        if XMPPGatewayConfig.log_presence:
            log.info('XMPP notification from %s to %s for presence flow 0x%x' %
                     (format_uri(self.xmpp_identity.uri, 'xmpp'),
                      format_uri(self.sip_identity.uri, 'sip'), id(self)))
        for subscription in self._sip_subscriptions:
            try:
                subscription.push_content(pidf.PIDFDocument.content_type,
                                          pidf_doc)
            except SIPCoreError as e:
                if XMPPGatewayConfig.log_presence:
                    log.info(
                        'Failed to send SIP NOTIFY from %s to %s for presence flow 0x%x: %s'
                        % (format_uri(self.xmpp_identity.uri, 'xmpp'),
                           format_uri(self.sip_identity.uri,
                                      'sip'), id(self), e))
        if not stanza.available:
            # Only inform once about this device being unavailable
            del self._stanza_cache[stanza.sender.uri]

    def _NH_XMPPSubscriptionDidFail(self, notification):
        notification.center.remove_observer(self,
                                            sender=self._xmpp_subscription)
        self._xmpp_subscription = None
        self.end()

    _NH_XMPPSubscriptionDidEnd = _NH_XMPPSubscriptionDidFail
Beispiel #15
0
class X2SPresenceHandler(object):
    implements(IObserver)

    sip_identity = WriteOnceAttribute()
    xmpp_identity = WriteOnceAttribute()

    def __init__(self, sip_identity, xmpp_identity):
        self.ended = False
        self.sip_identity = sip_identity
        self.xmpp_identity = xmpp_identity
        self.subscribed = False
        self._command_proc = None
        self._command_channel = coros.queue()
        self._data_channel = coros.queue()
        self._sip_subscription = None
        self._sip_subscription_proc = None
        self._sip_subscription_timer = None
        self._xmpp_subscription = None

    def start(self):
        notification_center = NotificationCenter()
        self._xmpp_subscription = XMPPIncomingSubscription(
            local_identity=self.sip_identity,
            remote_identity=self.xmpp_identity)
        notification_center.add_observer(self, sender=self._xmpp_subscription)
        self._xmpp_subscription.start()
        self._command_proc = proc.spawn(self._run)
        self._subscribe_sip()
        notification_center.post_notification('X2SPresenceHandlerDidStart',
                                              sender=self)

    def end(self):
        if self.ended:
            return
        notification_center = NotificationCenter()
        if self._xmpp_subscription is not None:
            notification_center.remove_observer(self,
                                                sender=self._xmpp_subscription)
            self._xmpp_subscription.end()
            self._xmpp_subscription = None
        if self._sip_subscription:
            self._unsubscribe_sip()
        self.ended = True
        notification_center.post_notification('X2SPresenceHandlerDidEnd',
                                              sender=self)

    @run_in_green_thread
    def _subscribe_sip(self):
        command = Command('subscribe')
        self._command_channel.send(command)

    @run_in_green_thread
    def _unsubscribe_sip(self):
        command = Command('unsubscribe')
        self._command_channel.send(command)
        command.wait()
        self._command_proc.kill()
        self._command_proc = None

    def _run(self):
        while True:
            command = self._command_channel.wait()
            handler = getattr(self, '_CH_%s' % command.name)
            handler(command)

    def _CH_subscribe(self, command):
        if self._sip_subscription_timer is not None and self._sip_subscription_timer.active(
        ):
            self._sip_subscription_timer.cancel()
        self._sip_subscription_timer = None
        if self._sip_subscription_proc is not None:
            subscription_proc = self._sip_subscription_proc
            subscription_proc.kill(InterruptSubscription)
            subscription_proc.wait()
        self._sip_subscription_proc = proc.spawn(
            self._sip_subscription_handler, command)

    def _CH_unsubscribe(self, command):
        # Cancel any timer which would restart the subscription process
        if self._sip_subscription_timer is not None and self._sip_subscription_timer.active(
        ):
            self._sip_subscription_timer.cancel()
        self._sip_subscription_timer = None
        if self._sip_subscription_proc is not None:
            subscription_proc = self._sip_subscription_proc
            subscription_proc.kill(TerminateSubscription)
            subscription_proc.wait()
            self._sip_subscription_proc = None
        command.signal()

    def _process_pidf(self, body):
        try:
            pidf_doc = pidf.PIDF.parse(body)
        except ParserError as e:
            log.warn('Error parsing PIDF document: %s' % e)
            return
        # Build XML stanzas out of PIDF documents
        try:
            person = next(p for p in pidf_doc.persons)
        except StopIteration:
            person = None
        for service in pidf_doc.services:
            sip_contact = self.sip_identity.uri.as_sip_uri()
            if service.device_info is not None:
                sip_contact.parameters[
                    'gr'] = 'urn:uuid:%s' % service.device_info.id
            else:
                sip_contact.parameters['gr'] = service.id
            sender = Identity(FrozenURI.parse(sip_contact))
            if service.status.extended is not None:
                available = service.status.extended != 'offline'
            else:
                available = service.status.basic == 'open'
            stanza = AvailabilityPresence(sender, self.xmpp_identity,
                                          available)
            for note in service.notes:
                stanza.statuses[note.lang] = note
            if service.status.extended is not None:
                if service.status.extended == 'away':
                    stanza.show = 'away'
                elif service.status.extended == 'busy':
                    stanza.show = 'dnd'
            elif person is not None and person.activities is not None:
                activities = set(list(person.activities))
                if 'away' in activities:
                    stanza.show = 'away'
                elif {'holiday', 'vacation'}.intersection(activities):
                    stanza.show = 'xa'
                elif 'busy' in activities:
                    stanza.show = 'dnd'
            self._xmpp_subscription.send_presence(stanza)

    def _sip_subscription_handler(self, command):
        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        account = DefaultAccount()
        refresh_interval = getattr(command, 'refresh_interval',
                                   None) or account.sip.subscribe_interval

        try:
            # Lookup routes
            if account.sip.outbound_proxy is not None:
                uri = SIPURI(host=account.sip.outbound_proxy.host,
                             port=account.sip.outbound_proxy.port,
                             parameters={
                                 'transport':
                                 account.sip.outbound_proxy.transport
                             })
            else:
                uri = SIPURI(host=self.sip_identity.uri.as_sip_uri().host)
            lookup = DNSLookup()
            try:
                routes = lookup.lookup_sip_proxy(
                    uri, settings.sip.transport_list).wait()
            except DNSLookupError as e:
                timeout = random.uniform(15, 30)
                raise SubscriptionError(error='DNS lookup failed: %s' % e,
                                        timeout=timeout)

            timeout = time() + 30
            for route in routes:
                remaining_time = timeout - time()
                if remaining_time > 0:
                    transport = route.transport
                    parameters = {} if transport == 'udp' else {
                        'transport': transport
                    }
                    contact_uri = SIPURI(user=account.contact.username,
                                         host=SIPConfig.local_ip.normalized,
                                         port=getattr(Engine(),
                                                      '%s_port' % transport),
                                         parameters=parameters)
                    subscription_uri = self.sip_identity.uri.as_sip_uri()
                    subscription = Subscription(
                        subscription_uri,
                        FromHeader(self.xmpp_identity.uri.as_sip_uri()),
                        ToHeader(subscription_uri),
                        ContactHeader(contact_uri),
                        'presence',
                        RouteHeader(route.uri),
                        refresh=refresh_interval)
                    notification_center.add_observer(self, sender=subscription)
                    try:
                        subscription.subscribe(
                            timeout=limit(remaining_time, min=1, max=5))
                    except SIPCoreError:
                        notification_center.remove_observer(
                            self, sender=subscription)
                        raise SubscriptionError(error='Internal error',
                                                timeout=5)
                    self._sip_subscription = subscription
                    try:
                        while True:
                            notification = self._data_channel.wait()
                            if notification.sender is subscription and notification.name == 'SIPSubscriptionDidStart':
                                break
                    except SIPSubscriptionDidFail as e:
                        notification_center.remove_observer(
                            self, sender=subscription)
                        self._sip_subscription = None
                        if e.data.code == 407:
                            # Authentication failed, so retry the subscription in some time
                            raise SubscriptionError(
                                error='Authentication failed',
                                timeout=random.uniform(60, 120))
                        elif e.data.code == 403:
                            # Forbidden
                            raise SubscriptionError(error='Forbidden',
                                                    timeout=None,
                                                    fatal=True)
                        elif e.data.code == 423:
                            # Get the value of the Min-Expires header
                            if e.data.min_expires is not None and e.data.min_expires > refresh_interval:
                                interval = e.data.min_expires
                            else:
                                interval = None
                            raise SubscriptionError(error='Interval too short',
                                                    timeout=random.uniform(
                                                        60, 120),
                                                    refresh_interval=interval)
                        elif e.data.code in (405, 406, 489):
                            raise SubscriptionError(
                                error='Method or event not supported',
                                timeout=None,
                                fatal=True)
                        elif e.data.code == 1400:
                            raise SubscriptionError(error=e.data.reason,
                                                    timeout=None,
                                                    fatal=True)
                        else:
                            # Otherwise just try the next route
                            continue
                    else:
                        self.subscribed = True
                        command.signal()
                        break
            else:
                # There are no more routes to try, give up
                raise SubscriptionError(error='No more routes to try',
                                        timeout=None,
                                        fatal=True)
            # At this point it is subscribed. Handle notifications and ending/failures.
            try:
                while True:
                    notification = self._data_channel.wait()
                    if notification.sender is not self._sip_subscription:
                        continue
                    if self._xmpp_subscription is None:
                        continue
                    if notification.name == 'SIPSubscriptionGotNotify':
                        if notification.data.event == 'presence':
                            subscription_state = notification.data.headers.get(
                                'Subscription-State').state
                            if subscription_state == 'active' and self._xmpp_subscription.state != 'active':
                                self._xmpp_subscription.accept()
                            elif subscription_state == 'pending' and self._xmpp_subscription.state == 'active':
                                # The state went from active to pending, hide the presence state?
                                pass
                            if notification.data.body:
                                if XMPPGatewayConfig.log_presence:
                                    log.info(
                                        'SIP NOTIFY from %s to %s' %
                                        (format_uri(self.sip_identity.uri,
                                                    'sip'),
                                         format_uri(self.xmpp_identity.uri,
                                                    'xmpp')))
                                self._process_pidf(notification.data.body)
                    elif notification.name == 'SIPSubscriptionDidEnd':
                        break
            except SIPSubscriptionDidFail as e:
                if e.data.code == 0 and e.data.reason == 'rejected':
                    self._xmpp_subscription.reject()
                else:
                    self._command_channel.send(Command('subscribe'))
            notification_center.remove_observer(self,
                                                sender=self._sip_subscription)
        except InterruptSubscription as e:
            if not self.subscribed:
                command.signal(e)
            if self._sip_subscription is not None:
                notification_center.remove_observer(
                    self, sender=self._sip_subscription)
                try:
                    self._sip_subscription.end(timeout=2)
                except SIPCoreError:
                    pass
        except TerminateSubscription as e:
            if not self.subscribed:
                command.signal(e)
            if self._sip_subscription is not None:
                try:
                    self._sip_subscription.end(timeout=2)
                except SIPCoreError:
                    pass
                else:
                    try:
                        while True:
                            notification = self._data_channel.wait()
                            if notification.sender is self._sip_subscription and notification.name == 'SIPSubscriptionDidEnd':
                                break
                    except SIPSubscriptionDidFail:
                        pass
                finally:
                    notification_center.remove_observer(
                        self, sender=self._sip_subscription)
        except SubscriptionError as e:
            if not e.fatal:
                self._sip_subscription_timer = reactor.callLater(
                    e.timeout, self._command_channel.send,
                    Command('subscribe',
                            command.event,
                            refresh_interval=e.refresh_interval))
        finally:
            self.subscribed = False
            self._sip_subscription = None
            self._sip_subscription_proc = None
            reactor.callLater(0, self.end)

    @run_in_twisted_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_SIPSubscriptionDidStart(self, notification):
        self._data_channel.send(notification)

    def _NH_SIPSubscriptionDidEnd(self, notification):
        self._data_channel.send(notification)

    def _NH_SIPSubscriptionDidFail(self, notification):
        self._data_channel.send_exception(
            SIPSubscriptionDidFail(notification.data))

    def _NH_SIPSubscriptionGotNotify(self, notification):
        self._data_channel.send(notification)

    def _NH_XMPPIncomingSubscriptionGotUnsubscribe(self, notification):
        self.end()

    def _NH_XMPPIncomingSubscriptionGotSubscribe(self, notification):
        if self._sip_subscription is not None and self._sip_subscription.state.lower(
        ) == 'active':
            self._xmpp_subscription.accept()

    _NH_XMPPIncomingSubscriptionGotProbe = _NH_XMPPIncomingSubscriptionGotSubscribe