Esempio n. 1
0
 def send_publish(self, uri, body):
     if self.outbound_proxy is None:
         return
     uri = self.sip_prefix_re.sub('', uri)
     publication = Publication(FromHeader(SIPURI(uri)),
                               "xcap-diff",
                               "application/xcap-diff+xml",
                               duration=0,
                               extra_headers=[Header('Thor-Scope', 'publish-xcap')])
     NotificationCenter().add_observer(self, sender=publication)
     route_header = RouteHeader(SIPURI(host=self.outbound_proxy.host, port=self.outbound_proxy.port, parameters=dict(transport=self.outbound_proxy.transport)))
     publication.publish(body, route_header, timeout=5)
Esempio n. 2
0
 def send_publish(self, uri, body):
     uri = re.sub("^(sip:|sips:)", "", uri)
     destination_node = self.provisioning.lookup(uri)
     if destination_node is not None:
         # TODO: add configuration settings for SIP transport. -Saul
         publication = Publication(FromHeader(SIPURI(uri)),
                                   "xcap-diff",
                                   "application/xcap-diff+xml",
                                   duration=0,
                                   extra_headers=[Header('Thor-Scope', 'publish-xcap')])
         NotificationCenter().add_observer(self, sender=publication)
         route_header = RouteHeader(SIPURI(host=str(destination_node), port='5060', parameters=dict(transport='udp')))
         publication.publish(body, route_header, timeout=5)
Esempio n. 3
0
 def send_publish(self, uri, body):
     if self.outbound_proxy is None:
         return
     uri = self.sip_prefix_re.sub('', uri)
     publication = Publication(
         FromHeader(SIPURI(uri)),
         "xcap-diff",
         "application/xcap-diff+xml",
         duration=0,
         extra_headers=[Header('Thor-Scope', 'publish-xcap')])
     NotificationCenter().add_observer(self, sender=publication)
     route_header = RouteHeader(
         SIPURI(host=self.outbound_proxy.host,
                port=self.outbound_proxy.port,
                parameters=dict(transport=self.outbound_proxy.transport)))
     publication.publish(body, route_header, timeout=5)
Esempio n. 4
0
 def send_publish(self, uri, body):
     uri = re.sub("^(sip:|sips:)", "", uri)
     destination_node = self.provisioning.lookup(uri)
     if destination_node is not None:
         # TODO: add configuration settings for SIP transport. -Saul
         publication = Publication(
             FromHeader(SIPURI(uri)),
             "xcap-diff",
             "application/xcap-diff+xml",
             duration=0,
             extra_headers=[Header('Thor-Scope', 'publish-xcap')])
         NotificationCenter().add_observer(self, sender=publication)
         route_header = RouteHeader(
             SIPURI(host=str(destination_node),
                    port='5060',
                    parameters=dict(transport='udp')))
         publication.publish(body, route_header, timeout=5)
Esempio n. 5
0
    def _CH_publish(self, command):
        if command.state is None or self._publication is None and command.state is SameState:
            command.signal()
            return

        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        if self._publication_timer is not None and self._publication_timer.active(
        ):
            self._publication_timer.cancel()
        self._publication_timer = None

        if self._publication is None:
            duration = command.refresh_interval or self.account.sip.publish_interval
            from_header = FromHeader(self.account.uri,
                                     self.account.display_name)
            self._publication = Publication(
                from_header,
                self.event,
                self.payload_type.content_type,
                credentials=self.account.credentials,
                duration=duration,
                extra_headers=self.extra_headers)
            notification_center.add_observer(self, sender=self._publication)
            notification_center.post_notification(
                self.__class__.__name__ + 'WillPublish',
                sender=self,
                data=NotificationData(state=command.state, duration=duration))
        else:
            notification_center.post_notification(
                self.__class__.__name__ + 'WillRefresh',
                sender=self,
                data=NotificationData(state=command.state))

        try:
            # Lookup routes
            valid_transports = self.__transports__.intersection(
                settings.sip.transport_list)
            if self.account.sip.outbound_proxy is not None and self.account.sip.outbound_proxy.transport in valid_transports:
                uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                             port=self.account.sip.outbound_proxy.port,
                             parameters={
                                 'transport':
                                 self.account.sip.outbound_proxy.transport
                             })
            else:
                uri = SIPURI(host=self.account.id.domain)
            lookup = DNSLookup()
            try:
                routes = lookup.lookup_sip_proxy(uri, valid_transports).wait()
            except DNSLookupError, e:
                retry_after = random.uniform(self._dns_wait,
                                             2 * self._dns_wait)
                self._dns_wait = limit(2 * self._dns_wait, max=30)
                raise PublicationError('DNS lookup failed: %s' % e,
                                       retry_after=retry_after)
            else:
Esempio n. 6
0
class Publisher(object, metaclass=ABCMeta):
    __nickname__  = PublisherNickname()
    __transports__ = frozenset(['tls', 'tcp', 'udp'])


    def __init__(self, account):
        self.account = account
        self.started = False
        self.active = False
        self.publishing = False
        self._lock = Lock()
        self._command_proc = None
        self._command_channel = coros.queue()
        self._data_channel = coros.queue()
        self._publication = None
        self._dns_wait = 1
        self._publish_wait = 1
        self._publication_timer = None
        self.__dict__['state'] = None

    @abstractproperty
    def event(self):
        return None

    @abstractproperty
    def payload_type(self):
        return None

    @property
    def extra_headers(self):
        return []

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

    @state.setter
    def state(self, state):
        if state is not None and not isinstance(state, self.payload_type.root_element):
            raise ValueError("state must be a %s document or None" % self.payload_type.root_element.__name__)
        with self._lock:
            old_state = self.__dict__['state']
            self.__dict__['state'] = state
            if state == old_state:
                return
            self._publish(state)

    def start(self):
        if self.started:
            return
        self.started = True
        notification_center = NotificationCenter()
        notification_center.add_observer(self, sender=self)
        notification_center.post_notification(self.__class__.__name__ + 'WillStart', sender=self)
        notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=self.account)
        notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
        notification_center.add_observer(self, name='NetworkConditionsDidChange')
        self._command_proc = proc.spawn(self._run)
        notification_center.post_notification(self.__class__.__name__ + 'DidStart', sender=self)
        notification_center.remove_observer(self, sender=self)

    def stop(self):
        if not self.started:
            return
        self.started = False
        self.active = False
        notification_center = NotificationCenter()
        notification_center.add_observer(self, sender=self)
        notification_center.post_notification(self.__class__.__name__ + 'WillEnd', sender=self)
        notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=self.account)
        notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
        notification_center.remove_observer(self, name='NetworkConditionsDidChange')
        command = Command('terminate')
        self._command_channel.send(command)
        command.wait()
        self._command_proc = None
        notification_center.post_notification(self.__class__.__name__ + 'DidDeactivate', sender=self)
        notification_center.post_notification(self.__class__.__name__ + 'DidEnd', sender=self)
        notification_center.remove_observer(self, sender=self)

    def activate(self):
        if not self.started:
            raise RuntimeError("not started")
        self.active = True
        self._command_channel.send(Command('publish', state=self.state))
        notification_center = NotificationCenter()
        notification_center.post_notification(self.__class__.__name__ + 'DidActivate', sender=self)

    def deactivate(self):
        if not self.started:
            raise RuntimeError("not started")
        self.active = False
        self._command_channel.send(Command('unpublish'))
        notification_center = NotificationCenter()
        notification_center.post_notification(self.__class__.__name__ + 'DidDeactivate', sender=self)

    @run_in_twisted_thread
    def _publish(self, state):
        if not self.active:
            return
        if state is None:
            self._command_channel.send(Command('unpublish'))
        else:
            self._command_channel.send(Command('publish', state=state))

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

    def _CH_publish(self, command):
        if command.state is None or self._publication is None and command.state is SameState:
            command.signal()
            return

        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        if self._publication_timer is not None and self._publication_timer.active():
            self._publication_timer.cancel()
        self._publication_timer = None

        if self._publication is None:
            duration = command.refresh_interval or self.account.sip.publish_interval
            from_header = FromHeader(self.account.uri, self.account.display_name)
            self._publication = Publication(from_header, self.event, self.payload_type.content_type, credentials=self.account.credentials, duration=duration, extra_headers=self.extra_headers)
            notification_center.add_observer(self, sender=self._publication)
            notification_center.post_notification(self.__class__.__name__ + 'WillPublish', sender=self, data=NotificationData(state=command.state, duration=duration))
        else:
            notification_center.post_notification(self.__class__.__name__ + 'WillRefresh', sender=self, data=NotificationData(state=command.state))

        try:
            if Host.default_ip is None:
                raise PublicationError('No IP address', retry_after=60)

            # Lookup routes
            valid_transports = self.__transports__.intersection(settings.sip.transport_list)
            if self.account.sip.outbound_proxy is not None and self.account.sip.outbound_proxy.transport in valid_transports:
                uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={'transport': self.account.sip.outbound_proxy.transport})
            else:
                uri = SIPURI(host=self.account.id.domain)

            lookup = DNSLookup()
            try:
                routes = lookup.lookup_sip_proxy(uri, valid_transports, tls_name=self.account.sip.tls_name).wait()
            except DNSLookupError as e:
                retry_after = random.uniform(self._dns_wait, 2*self._dns_wait)
                self._dns_wait = limit(2*self._dns_wait, max=30)
                raise PublicationError('DNS lookup failed: %s' % e, retry_after=retry_after)
            else:
                self._dns_wait = 1

            body = None if command.state is SameState else command.state.toxml()

            # Publish by trying each route in turn
            publish_timeout = time() + 30
            for route in routes:
                if Host.default_ip is None:
                    raise PublicationError('No IP address', retry_after=60)

                remaining_time = publish_timeout-time()
                if remaining_time > 0:
                    try:
                        try:
                            self._publication.publish(body, RouteHeader(route.uri), timeout=limit(remaining_time, min=1, max=10))
                        except ValueError as e:  # this happens for an initial PUBLISH with body=None
                            raise PublicationError(str(e), retry_after=0)
                        except PublicationETagError:
                            state = self.state # access self.state only once to avoid race conditions
                            if state is not None:
                                self._publication.publish(state.toxml(), RouteHeader(route.uri), timeout=limit(remaining_time, min=1, max=10))
                            else:
                                command.signal()
                                return
                    except SIPCoreError:
                        raise PublicationError('Internal error', retry_after=5)

                    try:
                        while True:
                            notification = self._data_channel.wait()
                            if notification.name == 'SIPPublicationDidSucceed':
                                break
                            if notification.name == 'SIPPublicationDidEnd':
                                raise PublicationError('Publication expired', retry_after=random.uniform(60, 120))  # publication expired while we were trying to re-publish
                    except SIPPublicationDidFail as e:
                        if e.data.code == 407:
                            # Authentication failed, so retry the publication in some time
                            raise PublicationError('Authentication failed', retry_after=random.uniform(60, 120))
                        elif e.data.code == 412:
                            raise PublicationError('Conditional request failed', retry_after=0)
                        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 > self.account.sip.publish_interval:
                                refresh_interval = e.data.min_expires
                            else:
                                refresh_interval = None
                            raise PublicationError('Interval too short', retry_after=random.uniform(60, 120), refresh_interval=refresh_interval)
                        elif e.data.code in (405, 406, 489):
                            raise PublicationError('Method or event not supported', retry_after=3600)
                        else:
                            # Otherwise just try the next route
                            continue
                    else:
                        self.publishing = True
                        self._publish_wait = 1
                        command.signal()
                        break
            else:
                # There are no more routes to try, reschedule the publication
                retry_after = random.uniform(self._publish_wait, 2*self._publish_wait)
                self._publish_wait = limit(self._publish_wait*2, max=30)
                raise PublicationError('No more routes to try', retry_after=retry_after)
        except PublicationError as e:
            self.publishing = False
            notification_center.remove_observer(self, sender=self._publication)
            def publish(e):
                if self.active:
                    self._command_channel.send(Command('publish', event=command.event, state=self.state, refresh_interval=e.refresh_interval))
                else:
                    command.signal()
                self._publication_timer = None
            self._publication_timer = reactor.callLater(e.retry_after, publish, e)
            self._publication = None
            notification_center.post_notification(self.__nickname__ + 'PublicationDidFail', sender=self, data=NotificationData(reason=e.error))
        else:
            notification_center.post_notification(self.__nickname__ + 'PublicationDidSucceed', sender=self)

    def _CH_unpublish(self, command):
        # Cancel any timer which would restart the publication process
        if self._publication_timer is not None and self._publication_timer.active():
            self._publication_timer.cancel()
        self._publication_timer = None
        publishing = self.publishing
        self.publishing = False
        if self._publication is not None:
            notification_center = NotificationCenter()
            if publishing:
                self._publication.end(timeout=2)
                try:
                    while True:
                        notification = self._data_channel.wait()
                        if notification.name == 'SIPPublicationDidEnd':
                            break
                except (SIPPublicationDidFail, SIPPublicationDidNotEnd):
                    notification_center.post_notification(self.__nickname__ + 'PublicationDidNotEnd', sender=self)
                else:
                    notification_center.post_notification(self.__nickname__ + 'PublicationDidEnd', sender=self)
            notification_center.remove_observer(self, sender=self._publication)
            self._publication = None
        command.signal()

    def _CH_terminate(self, command):
        self._CH_unpublish(command)
        raise proc.ProcExit

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

    def _NH_SIPPublicationDidSucceed(self, notification):
        if notification.sender is self._publication:
            self._data_channel.send(notification)

    def _NH_SIPPublicationDidFail(self, notification):
        if notification.sender is self._publication:
            self._data_channel.send_exception(SIPPublicationDidFail(notification.data))

    def _NH_SIPPublicationDidEnd(self, notification):
        if notification.sender is self._publication:
            self._data_channel.send(notification)

    def _NH_SIPPublicationDidNotEnd(self, notification):
        if notification.sender is self._publication:
            self._data_channel.send_exception(SIPPublicationDidNotEnd(notification.data))

    def _NH_SIPPublicationWillExpire(self, notification):
        if notification.sender is self._publication:
            self._publish(SameState)

    @run_in_green_thread
    def _NH_CFGSettingsObjectDidChange(self, notification):
        if not self.started:
            return
        if 'enabled' in notification.data.modified:
            return # global account activation is handled separately by the account itself
        elif 'presence.enabled' in notification.data.modified:
            if self.account.presence.enabled:
                self.activate()
            else:
                self.deactivate()
        elif self.active and {'__id__', 'auth.password', 'auth.username', 'sip.outbound_proxy', 'sip.transport_list', 'sip.publish_interval'}.intersection(notification.data.modified):
            self._command_channel.send(Command('unpublish'))
            self._command_channel.send(Command('publish', state=self.state))

    def _NH_NetworkConditionsDidChange(self, notification):
        if self.active:
            self._command_channel.send(Command('unpublish'))
            self._command_channel.send(Command('publish', state=self.state))
Esempio n. 7
0
    def _CH_publish(self, command):
        if command.state is None or self._publication is None and command.state is SameState:
            command.signal()
            return

        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        if self._publication_timer is not None and self._publication_timer.active():
            self._publication_timer.cancel()
        self._publication_timer = None

        if self._publication is None:
            duration = command.refresh_interval or self.account.sip.publish_interval
            from_header = FromHeader(self.account.uri, self.account.display_name)
            self._publication = Publication(from_header, self.event, self.payload_type.content_type, credentials=self.account.credentials, duration=duration, extra_headers=self.extra_headers)
            notification_center.add_observer(self, sender=self._publication)
            notification_center.post_notification(self.__class__.__name__ + 'WillPublish', sender=self, data=NotificationData(state=command.state, duration=duration))
        else:
            notification_center.post_notification(self.__class__.__name__ + 'WillRefresh', sender=self, data=NotificationData(state=command.state))

        try:
            if Host.default_ip is None:
                raise PublicationError('No IP address', retry_after=60)

            # Lookup routes
            valid_transports = self.__transports__.intersection(settings.sip.transport_list)
            if self.account.sip.outbound_proxy is not None and self.account.sip.outbound_proxy.transport in valid_transports:
                uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={'transport': self.account.sip.outbound_proxy.transport})
            else:
                uri = SIPURI(host=self.account.id.domain)

            lookup = DNSLookup()
            try:
                routes = lookup.lookup_sip_proxy(uri, valid_transports, tls_name=self.account.sip.tls_name).wait()
            except DNSLookupError as e:
                retry_after = random.uniform(self._dns_wait, 2*self._dns_wait)
                self._dns_wait = limit(2*self._dns_wait, max=30)
                raise PublicationError('DNS lookup failed: %s' % e, retry_after=retry_after)
            else:
                self._dns_wait = 1

            body = None if command.state is SameState else command.state.toxml()

            # Publish by trying each route in turn
            publish_timeout = time() + 30
            for route in routes:
                if Host.default_ip is None:
                    raise PublicationError('No IP address', retry_after=60)

                remaining_time = publish_timeout-time()
                if remaining_time > 0:
                    try:
                        try:
                            self._publication.publish(body, RouteHeader(route.uri), timeout=limit(remaining_time, min=1, max=10))
                        except ValueError as e:  # this happens for an initial PUBLISH with body=None
                            raise PublicationError(str(e), retry_after=0)
                        except PublicationETagError:
                            state = self.state # access self.state only once to avoid race conditions
                            if state is not None:
                                self._publication.publish(state.toxml(), RouteHeader(route.uri), timeout=limit(remaining_time, min=1, max=10))
                            else:
                                command.signal()
                                return
                    except SIPCoreError:
                        raise PublicationError('Internal error', retry_after=5)

                    try:
                        while True:
                            notification = self._data_channel.wait()
                            if notification.name == 'SIPPublicationDidSucceed':
                                break
                            if notification.name == 'SIPPublicationDidEnd':
                                raise PublicationError('Publication expired', retry_after=random.uniform(60, 120))  # publication expired while we were trying to re-publish
                    except SIPPublicationDidFail as e:
                        if e.data.code == 407:
                            # Authentication failed, so retry the publication in some time
                            raise PublicationError('Authentication failed', retry_after=random.uniform(60, 120))
                        elif e.data.code == 412:
                            raise PublicationError('Conditional request failed', retry_after=0)
                        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 > self.account.sip.publish_interval:
                                refresh_interval = e.data.min_expires
                            else:
                                refresh_interval = None
                            raise PublicationError('Interval too short', retry_after=random.uniform(60, 120), refresh_interval=refresh_interval)
                        elif e.data.code in (405, 406, 489):
                            raise PublicationError('Method or event not supported', retry_after=3600)
                        else:
                            # Otherwise just try the next route
                            continue
                    else:
                        self.publishing = True
                        self._publish_wait = 1
                        command.signal()
                        break
            else:
                # There are no more routes to try, reschedule the publication
                retry_after = random.uniform(self._publish_wait, 2*self._publish_wait)
                self._publish_wait = limit(self._publish_wait*2, max=30)
                raise PublicationError('No more routes to try', retry_after=retry_after)
        except PublicationError as e:
            self.publishing = False
            notification_center.remove_observer(self, sender=self._publication)
            def publish(e):
                if self.active:
                    self._command_channel.send(Command('publish', event=command.event, state=self.state, refresh_interval=e.refresh_interval))
                else:
                    command.signal()
                self._publication_timer = None
            self._publication_timer = reactor.callLater(e.retry_after, publish, e)
            self._publication = None
            notification_center.post_notification(self.__nickname__ + 'PublicationDidFail', sender=self, data=NotificationData(reason=e.error))
        else:
            notification_center.post_notification(self.__nickname__ + 'PublicationDidSucceed', sender=self)