Ejemplo n.º 1
0
 def __init__(self, id):
     self.contact = ContactURIFactory()
     self.xcap_manager = XCAPManager(self)
     self._started = False
     self._deleted = False
     self._active = False
     self._activation_lock = coros.Semaphore(1)
     self._registrar = Registrar(self)
     self._mwi_subscriber = MWISubscriber(self)
     self._pwi_subscriber = PresenceWinfoSubscriber(self)
     self._dwi_subscriber = DialogWinfoSubscriber(self)
     self._presence_subscriber = PresenceSubscriber(self)
     self._self_presence_subscriber = SelfPresenceSubscriber(self)
     self._dialog_subscriber = DialogSubscriber(self)
     self._presence_publisher = PresencePublisher(self)
     self._dialog_publisher = DialogPublisher(self)
     self._mwi_voicemail_uri = None
     self._pwi_version = None
     self._dwi_version = None
     self._presence_version = None
     self._dialog_version = None
Ejemplo n.º 2
0
 def __init__(self, id):
     self.contact = ContactURIFactory()
     self.xcap_manager = XCAPManager(self)
     self._started = False
     self._deleted = False
     self._active = False
     self._activation_lock = coros.Semaphore(1)
     self._registrar = Registrar(self)
     self._mwi_subscriber = MWISubscriber(self)
     self._pwi_subscriber = PresenceWinfoSubscriber(self)
     self._dwi_subscriber = DialogWinfoSubscriber(self)
     self._presence_subscriber = PresenceSubscriber(self)
     self._self_presence_subscriber = SelfPresenceSubscriber(self)
     self._dialog_subscriber = DialogSubscriber(self)
     self._presence_publisher = PresencePublisher(self)
     self._dialog_publisher = DialogPublisher(self)
     self._mwi_voicemail_uri = None
     self._pwi_version = None
     self._dwi_version = None
     self._presence_version = None
     self._dialog_version = None
Ejemplo n.º 3
0
class Account(SettingsObject):
    """
    Object represeting a SIP account. Contains configuration settings and
    attributes for accessing SIP related objects.

    When the account is active, it will register, publish its presence and
    subscribe to watcher-info events depending on its settings.

    If the object is unpickled and its enabled flag was set, it will
    automatically activate.

    When the save method is called, depending on the value of the enabled flag,
    the account will activate/deactivate.

    Notifications sent by instances of Account:
     * CFGSettingsObjectWasCreated
     * CFGSettingsObjectWasActivated
     * CFGSettingsObjectWasDeleted
     * CFGSettingsObjectDidChange
     * SIPAccountWillActivate
     * SIPAccountDidActivate
     * SIPAccountWillDeactivate
     * SIPAccountDidDeactivate
    """

    implements(IObserver)

    __group__ = 'Accounts'
    __id__ = SettingsObjectID(type=SIPAddress)

    id = __id__
    enabled = Setting(type=bool, default=False)
    display_name = Setting(type=unicode, default=None, nillable=True)

    auth = AuthSettings
    sip = SIPSettings
    rtp = RTPSettings
    nat_traversal = NATTraversalSettings
    message_summary = MessageSummarySettings
    msrp = MSRPSettings
    presence = PresenceSettings
    xcap = XCAPSettings
    tls = TLSSettings

    def __new__(cls, id):
        with AccountManager.load.lock:
            if not AccountManager.load.called:
                raise RuntimeError("cannot instantiate %s before calling AccountManager.load" % cls.__name__)
        return SettingsObject.__new__(cls, id)

    def __init__(self, id):
        self.contact = ContactURIFactory()
        self.xcap_manager = XCAPManager(self)
        self._started = False
        self._deleted = False
        self._active = False
        self._activation_lock = coros.Semaphore(1)
        self._registrar = Registrar(self)
        self._mwi_subscriber = MWISubscriber(self)
        self._pwi_subscriber = PresenceWinfoSubscriber(self)
        self._dwi_subscriber = DialogWinfoSubscriber(self)
        self._presence_subscriber = PresenceSubscriber(self)
        self._self_presence_subscriber = SelfPresenceSubscriber(self)
        self._dialog_subscriber = DialogSubscriber(self)
        self._presence_publisher = PresencePublisher(self)
        self._dialog_publisher = DialogPublisher(self)
        self._mwi_voicemail_uri = None
        self._pwi_version = None
        self._dwi_version = None
        self._presence_version = None
        self._dialog_version = None

    def start(self):
        if self._started or self._deleted:
            return
        self._started = True

        notification_center = NotificationCenter()
        notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=self)
        notification_center.add_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
        notification_center.add_observer(self, name='XCAPManagerDidDiscoverServerCapabilities', sender=self.xcap_manager)
        notification_center.add_observer(self, sender=self._mwi_subscriber)
        notification_center.add_observer(self, sender=self._pwi_subscriber)
        notification_center.add_observer(self, sender=self._dwi_subscriber)
        notification_center.add_observer(self, sender=self._presence_subscriber)
        notification_center.add_observer(self, sender=self._self_presence_subscriber)
        notification_center.add_observer(self, sender=self._dialog_subscriber)

        self.xcap_manager.init()
        if self.enabled:
            self._activate()

    def stop(self):
        if not self._started:
            return
        self._started = False

        self._deactivate()

        notification_center = NotificationCenter()
        notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=self)
        notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings())
        notification_center.remove_observer(self, name='XCAPManagerDidDiscoverServerCapabilities', sender=self.xcap_manager)
        notification_center.remove_observer(self, sender=self._mwi_subscriber)
        notification_center.remove_observer(self, sender=self._pwi_subscriber)
        notification_center.remove_observer(self, sender=self._dwi_subscriber)
        notification_center.remove_observer(self, sender=self._presence_subscriber)
        notification_center.remove_observer(self, sender=self._self_presence_subscriber)
        notification_center.remove_observer(self, sender=self._dialog_subscriber)

    @run_in_green_thread
    def delete(self):
        if self._deleted:
            return
        self._deleted = True
        self.stop()
        self._registrar = None
        self._mwi_subscriber = None
        self._pwi_subscriber = None
        self._dwi_subscriber = None
        self._presence_subscriber = None
        self._self_presence_subscriber = None
        self._dialog_subscriber = None
        self._presence_publisher = None
        self._dialog_publisher = None
        self.xcap_manager = None
        SettingsObject.delete(self)

    @run_in_green_thread
    def reregister(self):
        if self._started:
            self._registrar.reregister()

    @run_in_green_thread
    def resubscribe(self):
        if self._started:
            self._mwi_subscriber.resubscribe()
            self._pwi_subscriber.resubscribe()
            self._dwi_subscriber.resubscribe()
            self._presence_subscriber.resubscribe()
            self._self_presence_subscriber.resubscribe()
            self._dialog_subscriber.resubscribe()

    @property
    def credentials(self):
        return Credentials(self.auth.username or self.id.username, self.auth.password)

    @property
    def registered(self):
        try:
            return self._registrar.registered
        except AttributeError:
            return False

    @property
    def mwi_active(self):
        try:
            return self._mwi_subscriber.subscribed
        except AttributeError:
            return False

    @property
    def tls_credentials(self):
        # This property can be optimized to cache the credentials it loads from disk,
        # however this is not a time consuming operation (~ 3000 req/sec). -Luci
        settings = SIPSimpleSettings()
        if self.tls.certificate is not None:
            certificate_data = open(self.tls.certificate.normalized).read()
            certificate = X509Certificate(certificate_data)
            private_key = X509PrivateKey(certificate_data)
        else:
            certificate = None
            private_key = None
        if settings.tls.ca_list is not None:
            # we should read all certificates in the file, rather than just the first -Luci
            trusted = [X509Certificate(open(settings.tls.ca_list.normalized).read())]
        else:
            trusted = []
        credentials = X509Credentials(certificate, private_key, trusted)
        credentials.verify_peer = self.tls.verify_server
        return credentials

    @property
    def uri(self):
        return SIPURI(user=self.id.username, host=self.id.domain)

    @property
    def voicemail_uri(self):
        return self._mwi_voicemail_uri or self.message_summary.voicemail_uri

    def _get_presence_state(self):
        try:
            return self._presence_publisher.state
        except AttributeError:
            return None

    def _set_presence_state(self, state):
        try:
            self._presence_publisher.state = state
        except AttributeError:
            pass

    presence_state = property(_get_presence_state, _set_presence_state)
    del _get_presence_state, _set_presence_state

    def _get_dialog_state(self):
        try:
            return self._dialog_publisher.state
        except AttributeError:
            return None

    def _set_dialog_state(self, state):
        try:
            self._dialog_publisher.state = state
        except AttributeError:
            pass

    dialog_state = property(_get_dialog_state, _set_dialog_state)
    del _get_dialog_state, _set_dialog_state

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

    @run_in_green_thread
    def _NH_CFGSettingsObjectDidChange(self, notification):
        if self._started and 'enabled' in notification.data.modified:
            if self.enabled:
                self._activate()
            else:
                self._deactivate()

    def _NH_XCAPManagerDidDiscoverServerCapabilities(self, notification):
        if self._started and self.xcap.discovered is False:
            self.xcap.discovered = True
            self.save()
            notification.center.post_notification('SIPAccountDidDiscoverXCAPSupport', sender=self)

    def _NH_MWISubscriberDidDeactivate(self, notification):
        self._mwi_voicemail_uri = None

    def _NH_MWISubscriptionGotNotify(self, notification):
        if notification.data.body and notification.data.content_type == MessageSummary.content_type:
            try:
                message_summary = MessageSummary.parse(notification.data.body)
            except ParserError:
                pass
            else:
                self._mwi_voicemail_uri = message_summary.message_account and SIPAddress(message_summary.message_account.replace('sip:', '', 1)) or None
                notification.center.post_notification('SIPAccountGotMessageSummary', sender=self, data=NotificationData(message_summary=message_summary))

    def _NH_PresenceWinfoSubscriptionGotNotify(self, notification):
        if notification.data.body and notification.data.content_type == WatcherInfoDocument.content_type:
            try:
                watcher_info = WatcherInfoDocument.parse(notification.data.body)
                watcher_list = watcher_info['sip:' + self.id]
            except (ParserError, KeyError):
                pass
            else:
                if watcher_list.package != 'presence':
                    return
                if self._pwi_version is None:
                    if watcher_info.state == 'partial':
                        self._pwi_subscriber.resubscribe()
                elif watcher_info.version <= self._pwi_version:
                    return
                elif watcher_info.state == 'partial' and watcher_info.version > self._pwi_version + 1:
                    self._pwi_subscriber.resubscribe()
                self._pwi_version = watcher_info.version
                data = NotificationData(version=watcher_info.version, state=watcher_info.state, watcher_list=watcher_list)
                notification.center.post_notification('SIPAccountGotPresenceWinfo', sender=self, data=data)

    def _NH_PresenceWinfoSubscriptionDidEnd(self, notification):
        self._pwi_version = None

    def _NH_PresenceWinfoSubscriptionDidFail(self, notification):
        self._pwi_version = None

    def _NH_DialogWinfoSubscriptionGotNotify(self, notification):
        if notification.data.body and notification.data.content_type == WatcherInfoDocument.content_type:
            try:
                watcher_info = WatcherInfoDocument.parse(notification.data.body)
                watcher_list = watcher_info['sip:' + self.id]
            except (ParserError, KeyError):
                pass
            else:
                if watcher_list.package != 'dialog':
                    return
                if self._dwi_version is None:
                    if watcher_info.state == 'partial':
                        self._dwi_subscriber.resubscribe()
                elif watcher_info.version <= self._dwi_version:
                    return
                elif watcher_info.state == 'partial' and watcher_info.version > self._dwi_version + 1:
                    self._dwi_subscriber.resubscribe()
                self._dwi_version = watcher_info.version
                data = NotificationData(version=watcher_info.version, state=watcher_info.state, watcher_list=watcher_list)
                notification.center.post_notification('SIPAccountGotDialogWinfo', sender=self, data=data)

    def _NH_DialogWinfoSubscriptionDidEnd(self, notification):
        self._dwi_version = None

    def _NH_DialogWinfoSubscriptionDidFail(self, notification):
        self._dwi_version = None

    def _NH_PresenceSubscriptionGotNotify(self, notification):
        if notification.data.body and notification.data.content_type == RLSNotify.content_type:
            try:
                rls_notify = RLSNotify.parse('{content_type}\r\n\r\n{body}'.format(content_type=notification.data.headers['Content-Type'], body=notification.data.body))
            except ParserError:
                pass
            else:
                if rls_notify.uri != self.xcap_manager.rls_presence_uri:
                    return
                if self._presence_version is None:
                    if not rls_notify.full_state:
                        self._presence_subscriber.resubscribe()
                elif rls_notify.version <= self._presence_version:
                    return
                elif not rls_notify.full_state and rls_notify.version > self._presence_version + 1:
                    self._presence_subscriber.resubscribe()
                self._presence_version = rls_notify.version
                data = NotificationData(version=rls_notify.version, full_state=rls_notify.full_state, resource_map=dict((resource.uri, resource) for resource in rls_notify))
                notification.center.post_notification('SIPAccountGotPresenceState', sender=self, data=data)

    def _NH_PresenceSubscriptionDidEnd(self, notification):
        self._presence_version = None

    def _NH_PresenceSubscriptionDidFail(self, notification):
        self._presence_version = None

    def _NH_SelfPresenceSubscriptionGotNotify(self, notification):
        if notification.data.body and notification.data.content_type == PIDFDocument.content_type:
            try:
                pidf_doc = PIDFDocument.parse(notification.data.body)
            except ParserError:
                pass
            else:
                if pidf_doc.entity.partition('sip:')[2] != self.id:
                    return
                notification.center.post_notification('SIPAccountGotSelfPresenceState', sender=self, data=NotificationData(pidf=pidf_doc))

    def _NH_DialogSubscriptionGotNotify(self, notification):
        if notification.data.body and notification.data.content_type == RLSNotify.content_type:
            try:
                rls_notify = RLSNotify.parse('{content_type}\r\n\r\n{body}'.format(content_type=notification.data.headers['Content-Type'], body=notification.data.body))
            except ParserError:
                pass
            else:
                if rls_notify.uri != self.xcap_manager.rls_dialog_uri:
                    return
                if self._dialog_version is None:
                    if not rls_notify.full_state:
                        self._dialog_subscriber.resubscribe()
                elif rls_notify.version <= self._dialog_version:
                    return
                elif not rls_notify.full_state and rls_notify.version > self._dialog_version + 1:
                    self._dialog_subscriber.resubscribe()
                self._dialog_version = rls_notify.version
                data = NotificationData(version=rls_notify.version, full_state=rls_notify.full_state, resource_map=dict((resource.uri, resource) for resource in rls_notify))
                notification.center.post_notification('SIPAccountGotDialogState', sender=self, data=data)

    def _NH_DialogSubscriptionDidEnd(self, notification):
        self._dialog_version = None

    def _NH_DialogSubscriptionDidFail(self, notification):
        self._dialog_version = None

    def _activate(self):
        with self._activation_lock:
            if self._active:
                return
            notification_center = NotificationCenter()
            notification_center.post_notification('SIPAccountWillActivate', sender=self)
            self._active = True
            self._registrar.start()
            self._mwi_subscriber.start()
            self._pwi_subscriber.start()
            self._dwi_subscriber.start()
            self._presence_subscriber.start()
            self._self_presence_subscriber.start()
            self._dialog_subscriber.start()
            self._presence_publisher.start()
            self._dialog_publisher.start()
            if self.xcap.enabled:
                self.xcap_manager.start()
            notification_center.post_notification('SIPAccountDidActivate', sender=self)

    def _deactivate(self):
        with self._activation_lock:
            if not self._active:
                return
            notification_center = NotificationCenter()
            notification_center.post_notification('SIPAccountWillDeactivate', sender=self)
            self._active = False
            handlers = [self._registrar, self._mwi_subscriber, self._pwi_subscriber, self._dwi_subscriber,
                        self._presence_subscriber, self._self_presence_subscriber, self._dialog_subscriber,
                        self._presence_publisher, self._dialog_publisher, self.xcap_manager]
            proc.waitall([proc.spawn(handler.stop) for handler in handlers])
            notification_center.post_notification('SIPAccountDidDeactivate', sender=self)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.id)

    def __setstate__(self, data):
        # This restores the password from its previous location as a top level setting
        # after it was moved under the auth group.
        SettingsObject.__setstate__(self, data)
        if not data.get('auth', {}).get('password') and data.get('password'):
            self.auth.password = data.pop('password')
            self.save()
Ejemplo n.º 4
0
class Account(SettingsObject):
    """
    Object representing a SIP account. Contains configuration settings and
    attributes for accessing SIP related objects.

    When the account is active, it will register, publish its presence and
    subscribe to watcher-info events depending on its settings.

    If the object is un-pickled and its enabled flag was set, it will
    automatically activate.

    When the save method is called, depending on the value of the enabled flag,
    the account will activate/deactivate.

    Notifications sent by instances of Account:
     * CFGSettingsObjectWasCreated
     * CFGSettingsObjectWasActivated
     * CFGSettingsObjectWasDeleted
     * CFGSettingsObjectDidChange
     * SIPAccountWillActivate
     * SIPAccountDidActivate
     * SIPAccountWillDeactivate
     * SIPAccountDidDeactivate
    """

    __group__ = 'Accounts'
    __id__ = SettingsObjectID(type=SIPAddress)

    id = __id__
    enabled = Setting(type=bool, default=False)
    display_name = Setting(type=str, default=None, nillable=True)

    auth = AuthSettings
    sip = SIPSettings
    rtp = RTPSettings
    nat_traversal = NATTraversalSettings
    message_summary = MessageSummarySettings
    msrp = MSRPSettings
    presence = PresenceSettings
    xcap = XCAPSettings
    tls = TLSSettings

    def __new__(cls, id):
        #with AccountManager.load.lock:
        #    if not AccountManager.load.called:
        #        raise RuntimeError("cannot instantiate %s before calling AccountManager.load" % cls.__name__)
        return SettingsObject.__new__(cls, id)

    def __init__(self, id):
        self.contact = ContactURIFactory()
        self.xcap_manager = XCAPManager(self)
        self._started = False
        self._deleted = False
        self._active = False
        self._activation_lock = coros.Semaphore(1)
        self._registrar = Registrar(self)
        self._mwi_subscriber = MWISubscriber(self)
        self._pwi_subscriber = PresenceWinfoSubscriber(self)
        self._dwi_subscriber = DialogWinfoSubscriber(self)
        self._presence_subscriber = PresenceSubscriber(self)
        self._self_presence_subscriber = SelfPresenceSubscriber(self)
        self._dialog_subscriber = DialogSubscriber(self)
        self._presence_publisher = PresencePublisher(self)
        self._dialog_publisher = DialogPublisher(self)
        self._mwi_voicemail_uri = None
        self._pwi_version = None
        self._dwi_version = None
        self._presence_version = None
        self._dialog_version = None
        self.trusted_cas = []
        self.ca_list = None

    def start(self):
        if self._started or self._deleted:
            return
        self._started = True

        notification_center = NotificationCenter()
        notification_center.add_observer(self,
                                         name='CFGSettingsObjectDidChange',
                                         sender=self)
        notification_center.add_observer(self,
                                         name='CFGSettingsObjectDidChange',
                                         sender=SIPSimpleSettings())
        notification_center.add_observer(
            self,
            name='XCAPManagerDidDiscoverServerCapabilities',
            sender=self.xcap_manager)
        notification_center.add_observer(self, sender=self._mwi_subscriber)
        notification_center.add_observer(self, sender=self._pwi_subscriber)
        notification_center.add_observer(self, sender=self._dwi_subscriber)
        notification_center.add_observer(self,
                                         sender=self._presence_subscriber)
        notification_center.add_observer(self,
                                         sender=self._self_presence_subscriber)
        notification_center.add_observer(self, sender=self._dialog_subscriber)

        self.xcap_manager.init()
        if self.enabled:
            self._activate()

    def stop(self):
        if not self._started:
            return
        self._started = False

        self._deactivate()

        notification_center = NotificationCenter()
        notification_center.remove_observer(self,
                                            name='CFGSettingsObjectDidChange',
                                            sender=self)
        notification_center.remove_observer(self,
                                            name='CFGSettingsObjectDidChange',
                                            sender=SIPSimpleSettings())
        notification_center.remove_observer(
            self,
            name='XCAPManagerDidDiscoverServerCapabilities',
            sender=self.xcap_manager)
        notification_center.remove_observer(self, sender=self._mwi_subscriber)
        notification_center.remove_observer(self, sender=self._pwi_subscriber)
        notification_center.remove_observer(self, sender=self._dwi_subscriber)
        notification_center.remove_observer(self,
                                            sender=self._presence_subscriber)
        notification_center.remove_observer(
            self, sender=self._self_presence_subscriber)
        notification_center.remove_observer(self,
                                            sender=self._dialog_subscriber)

    def delete(self):
        if self._deleted:
            return
        self._deleted = True
        self.stop()
        self._registrar = None
        self._mwi_subscriber = None
        self._pwi_subscriber = None
        self._dwi_subscriber = None
        self._presence_subscriber = None
        self._self_presence_subscriber = None
        self._dialog_subscriber = None
        self._presence_publisher = None
        self._dialog_publisher = None
        self.xcap_manager = None
        SettingsObject.delete(self)

    @run_in_green_thread
    def reregister(self):
        if self._started:
            self._registrar.reregister()

    @run_in_green_thread
    def resubscribe(self):
        if self._started:
            self._mwi_subscriber.resubscribe()
            self._pwi_subscriber.resubscribe()
            self._dwi_subscriber.resubscribe()
            self._presence_subscriber.resubscribe()
            self._self_presence_subscriber.resubscribe()
            self._dialog_subscriber.resubscribe()

    @property
    def credentials(self):
        username = self.auth.username or self.id.username
        username = username.encode() if username else None
        password = self.auth.password.encode() if self.auth.password else None
        return Credentials(username, password)

    @property
    def registered(self):
        try:
            return self._registrar.registered
        except AttributeError:
            return False

    @property
    def mwi_active(self):
        try:
            return self._mwi_subscriber.subscribed
        except AttributeError:
            return False

    @property
    def tls_credentials(self):
        # This property can be optimized to cache the credentials it loads from disk,
        # however this is not a time consuming operation (~ 3000 req/sec). -Luci

        settings = SIPSimpleSettings()
        tls_certificate = self.tls.certificate or settings.tls.certificate

        certificate = None
        private_key = None

        if tls_certificate is not None:
            try:
                certificate_data = open(tls_certificate.normalized).read()
                certificate = X509Certificate(certificate_data)
                private_key = X509PrivateKey(certificate_data)
            except (FileNotFoundError, GNUTLSError, UnicodeDecodeError):
                pass

        trusted_cas = []
        ca_list = self.tls.ca_list or settings.tls.ca_list

        if ca_list is not None:
            if len(self.trusted_cas) > 0:
                trusted_cas = self.trusted_cas
            else:
                crt = None
                start = False
                try:
                    ca_text = open(ca_list.normalized).read()
                except (FileNotFoundError, GNUTLSError, UnicodeDecodeError):
                    ca_text = ''

                for line in ca_text.split("\n"):
                    if "BEGIN CERT" in line:
                        start = True
                        crt = line + "\n"
                    elif "END CERT" in line:
                        crt = crt + line + "\n"
                        end = True
                        start = False
                        try:
                            trusted_cas.append(X509Certificate(crt))
                        except (GNUTLSError, ValueError) as e:
                            continue

                    elif start:
                        crt = crt + line + "\n"

                self.trusted_cas = trusted_cas
                self.ca_list = ca_list

        credentials = X509Credentials(certificate, private_key, trusted_cas)
        credentials.verify_peer = self.tls.verify_server or settings.tls.certificate
        return credentials

    @property
    def uri(self):
        return SIPURI(user=self.id.username, host=self.id.domain)

    @property
    def voicemail_uri(self):
        return self._mwi_voicemail_uri or self.message_summary.voicemail_uri

    @property
    def presence_state(self):
        try:
            return self._presence_publisher.state
        except AttributeError:
            return None

    @presence_state.setter
    def presence_state(self, state):
        try:
            self._presence_publisher.state = state
        except AttributeError:
            pass

    @property
    def dialog_state(self):
        try:
            return self._dialog_publisher.state
        except AttributeError:
            return None

    @dialog_state.setter
    def dialog_state(self, state):
        try:
            self._dialog_publisher.state = state
        except AttributeError:
            pass

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

    @run_in_green_thread
    def _NH_CFGSettingsObjectDidChange(self, notification):
        if self._started and 'enabled' in notification.data.modified:
            if self.enabled:
                self._activate()
            else:
                self._deactivate()

    def _NH_XCAPManagerDidDiscoverServerCapabilities(self, notification):
        if self._started and self.xcap.discovered is False:
            self.xcap.discovered = True
            self.save()
            notification.center.post_notification(
                'SIPAccountDidDiscoverXCAPSupport', sender=self)

    def _NH_MWISubscriberDidDeactivate(self, notification):
        self._mwi_voicemail_uri = None

    def _NH_MWISubscriptionGotNotify(self, notification):
        body = notification.data.body.decode(
        ) if notification.data.body else None
        if body and notification.data.content_type == MessageSummary.content_type:
            try:
                message_summary = MessageSummary.parse(body)
            except ParserError:
                pass
            else:
                self._mwi_voicemail_uri = message_summary.message_account and SIPAddress(
                    message_summary.message_account.replace('sip:', '',
                                                            1)) or None
                notification.center.post_notification(
                    'SIPAccountGotMessageSummary',
                    sender=self,
                    data=NotificationData(message_summary=message_summary))

    def _NH_PresenceWinfoSubscriptionGotNotify(self, notification):
        body = notification.data.body.decode(
        ) if notification.data.body else None
        if body and notification.data.content_type == WatcherInfoDocument.content_type:
            try:
                watcher_info = WatcherInfoDocument.parse(body)
                watcher_list = watcher_info['sip:' + self.id]
            except (ParserError, KeyError):
                pass
            else:
                if watcher_list.package != 'presence':
                    return
                if self._pwi_version is None:
                    if watcher_info.state == 'partial':
                        self._pwi_subscriber.resubscribe()
                elif watcher_info.version <= self._pwi_version:
                    return
                elif watcher_info.state == 'partial' and watcher_info.version > self._pwi_version + 1:
                    self._pwi_subscriber.resubscribe()
                self._pwi_version = watcher_info.version
                data = NotificationData(version=watcher_info.version,
                                        state=watcher_info.state,
                                        watcher_list=watcher_list)
                notification.center.post_notification(
                    'SIPAccountGotPresenceWinfo', sender=self, data=data)

    def _NH_PresenceWinfoSubscriptionDidEnd(self, notification):
        self._pwi_version = None

    def _NH_PresenceWinfoSubscriptionDidFail(self, notification):
        self._pwi_version = None

    def _NH_DialogWinfoSubscriptionGotNotify(self, notification):
        body = notification.data.body.decode(
        ) if notification.data.body else None
        if body and notification.data.content_type == WatcherInfoDocument.content_type:
            try:
                watcher_info = WatcherInfoDocument.parse(body)
                watcher_list = watcher_info['sip:' + self.id]
            except (ParserError, KeyError):
                pass
            else:
                if watcher_list.package != 'dialog':
                    return
                if self._dwi_version is None:
                    if watcher_info.state == 'partial':
                        self._dwi_subscriber.resubscribe()
                elif watcher_info.version <= self._dwi_version:
                    return
                elif watcher_info.state == 'partial' and watcher_info.version > self._dwi_version + 1:
                    self._dwi_subscriber.resubscribe()
                self._dwi_version = watcher_info.version
                data = NotificationData(version=watcher_info.version,
                                        state=watcher_info.state,
                                        watcher_list=watcher_list)
                notification.center.post_notification(
                    'SIPAccountGotDialogWinfo', sender=self, data=data)

    def _NH_DialogWinfoSubscriptionDidEnd(self, notification):
        self._dwi_version = None

    def _NH_DialogWinfoSubscriptionDidFail(self, notification):
        self._dwi_version = None

    def _NH_PresenceSubscriptionGotNotify(self, notification):
        body = notification.data.body.decode(
        ) if notification.data.body else None
        if body and notification.data.content_type == RLSNotify.content_type:
            try:
                rls_notify = RLSNotify.parse(
                    '{content_type}\r\n\r\n{body}'.format(
                        content_type=notification.data.headers['Content-Type'],
                        body=body))
            except ParserError:
                pass
            else:
                if rls_notify.uri != self.xcap_manager.rls_presence_uri:
                    return
                if self._presence_version is None:
                    if not rls_notify.full_state:
                        self._presence_subscriber.resubscribe()
                elif rls_notify.version <= self._presence_version:
                    return
                elif not rls_notify.full_state and rls_notify.version > self._presence_version + 1:
                    self._presence_subscriber.resubscribe()
                self._presence_version = rls_notify.version
                data = NotificationData(version=rls_notify.version,
                                        full_state=rls_notify.full_state,
                                        resource_map=dict(
                                            (str(resource.uri), resource)
                                            for resource in rls_notify))
                notification.center.post_notification(
                    'SIPAccountGotPresenceState', sender=self, data=data)

    def _NH_PresenceSubscriptionDidEnd(self, notification):
        self._presence_version = None

    def _NH_PresenceSubscriptionDidFail(self, notification):
        self._presence_version = None

    def _NH_SelfPresenceSubscriptionGotNotify(self, notification):
        body = notification.data.body.decode(
        ) if notification.data.body else None
        if body and notification.data.content_type == PIDFDocument.content_type:
            try:
                pidf_doc = PIDFDocument.parse(body)
            except ParserError:
                pass
            else:
                if pidf_doc.entity.partition('sip:')[2] != self.id:
                    return
                notification.center.post_notification(
                    'SIPAccountGotSelfPresenceState',
                    sender=self,
                    data=NotificationData(pidf=pidf_doc))

    def _NH_DialogSubscriptionGotNotify(self, notification):
        body = notification.data.body.decode(
        ) if notification.data.body else None
        if body and notification.data.content_type == RLSNotify.content_type:
            try:
                rls_notify = RLSNotify.parse(
                    '{content_type}\r\n\r\n{body}'.format(
                        content_type=notification.data.headers['Content-Type'],
                        body=body))
            except ParserError:
                pass
            else:
                if rls_notify.uri != self.xcap_manager.rls_dialog_uri:
                    return
                if self._dialog_version is None:
                    if not rls_notify.full_state:
                        self._dialog_subscriber.resubscribe()
                elif rls_notify.version <= self._dialog_version:
                    return
                elif not rls_notify.full_state and rls_notify.version > self._dialog_version + 1:
                    self._dialog_subscriber.resubscribe()
                self._dialog_version = rls_notify.version
                data = NotificationData(version=rls_notify.version,
                                        full_state=rls_notify.full_state,
                                        resource_map=dict(
                                            (resource.uri, resource)
                                            for resource in rls_notify))
                notification.center.post_notification(
                    'SIPAccountGotDialogState', sender=self, data=data)

    def _NH_DialogSubscriptionDidEnd(self, notification):
        self._dialog_version = None

    def _NH_DialogSubscriptionDidFail(self, notification):
        self._dialog_version = None

    def _activate(self):
        with self._activation_lock:
            if self._active:
                return
            notification_center = NotificationCenter()
            notification_center.post_notification('SIPAccountWillActivate',
                                                  sender=self)
            self._active = True
            self._registrar.start()
            self._mwi_subscriber.start()
            self._pwi_subscriber.start()
            self._dwi_subscriber.start()
            self._presence_subscriber.start()
            self._self_presence_subscriber.start()
            self._dialog_subscriber.start()
            self._presence_publisher.start()
            self._dialog_publisher.start()
            if self.xcap.enabled:
                self.xcap_manager.start()
            notification_center.post_notification('SIPAccountDidActivate',
                                                  sender=self)

    def _deactivate(self):
        with self._activation_lock:
            if not self._active:
                return
            notification_center = NotificationCenter()
            notification_center.post_notification('SIPAccountWillDeactivate',
                                                  sender=self)
            self._active = False
            handlers = [
                self._registrar, self._mwi_subscriber, self._pwi_subscriber,
                self._dwi_subscriber, self._presence_subscriber,
                self._self_presence_subscriber, self._dialog_subscriber,
                self._presence_publisher, self._dialog_publisher,
                self.xcap_manager
            ]
            proc.waitall([proc.spawn(handler.stop) for handler in handlers])
            notification_center.post_notification('SIPAccountDidDeactivate',
                                                  sender=self)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.id)

    def __setstate__(self, data):
        # This restores the password from its previous location as a top level setting
        # after it was moved under the auth group.
        SettingsObject.__setstate__(self, data)
        if not data.get('auth', {}).get('password') and data.get('password'):
            self.auth.password = data.pop('password')
            self.save()