class BonjourMSRPSettings(SettingsGroup): transport = Setting(type=MSRPTransport, default='tcp')
class BonjourAccount(SettingsObject): """ Object representing a bonjour account. Contains configuration settings and attributes for accessing bonjour related options. When the account is active, it will send broadcast its contact address on the LAN. 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 """ implements(IObserver) __group__ = 'Accounts' __id__ = SIPAddress('bonjour@local') id = property(lambda self: self.__id__) enabled = BonjourAccountEnabledSetting(type=bool, default=True) display_name = Setting(type=unicode, default=user_info.fullname, nillable=False) msrp = BonjourMSRPSettings presence = PresenceSettings rtp = RTPSettings tls = TLSSettings def __new__(cls): with AccountManager.load.lock: if not AccountManager.load.called: raise RuntimeError( "cannot instantiate %s before calling AccountManager.load" % cls.__name__) return SettingsObject.__new__(cls) def __init__(self): self.contact = ContactURIFactory() self.credentials = None self._started = False self._active = False self._activation_lock = coros.Semaphore(1) self._bonjour_services = BonjourServices(self) # initialize fake settings (these are here to make the bonjour account quack like a duck) self.nat_traversal = NATTraversalSettings() self.nat_traversal.use_ice = False self.nat_traversal.msrp_relay = None self.nat_traversal.use_msrp_relay_for_outbound = False self.xcap = XCAPSettings() self.xcap.enabled = False self.xcap.discovered = False self.xcap.xcap_root = None def __repr__(self): return '%s()' % self.__class__.__name__ def start(self): if self._started: 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()) self._bonjour_services.start() if self.enabled: self._activate() def stop(self): if not self._started: return self._started = False self._deactivate() self._bonjour_services.stop() notification_center = NotificationCenter() notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=self) notification_center.remove_observer(self, name='CFGSettingsObjectDidChange', sender=SIPSimpleSettings()) @classproperty def mdns_available(cls): return _bonjour.available @property def registered(self): 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.contact.username, host=Host.default_ip or '127.0.0.1') def _get_presence_state(self): return self._bonjour_services.presence_state def _set_presence_state(self, state): self._bonjour_services.presence_state = state presence_state = property(_get_presence_state, _set_presence_state) del _get_presence_state, _set_presence_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: if 'enabled' in notification.data.modified: if self.enabled: self._activate() else: self._deactivate() elif self.enabled: if 'display_name' in notification.data.modified: self._bonjour_services.update_registrations() if set(['sip.transport_list', 'tls.certificate' ]).intersection(notification.data.modified): self._bonjour_services.update_registrations() self._bonjour_services.restart_discovery() 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._bonjour_services.activate() 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 self._bonjour_services.deactivate() notification_center.post_notification('SIPAccountDidDeactivate', sender=self)
class TLSSettings(SettingsGroup): ca_list = Setting(type=Path, default=None, nillable=True) certificate = Setting(type=Path, default=None, nillable=True) verify_server = Setting(type=bool, default=False)
class AuthSettings(SettingsGroup): username = Setting(type=str, default=None, nillable=True) password = Setting(type=str, default='')
class EchoCancellerSettings(SettingsGroup): enabled = Setting(type=bool, default=True) tail_length = Setting(type=NonNegativeInteger, default=2)
class FileTransferSettings(SettingsGroup): directory = Setting(type=Path, default=Path('~/Downloads'))
class LogsSettingsExtension(LogsSettings): directory = Setting(type=UserDataPath, default=UserDataPath('logs')) trace_notifications = Setting(type=bool, default=False)
class TLSSettingsExtension(TLSSettings): certificate = Setting(type=ApplicationDataPath, default=ApplicationDataPath( Resources.get('tls/default.crt')), nillable=True)
class BlinkContactGroupExtension(ContactGroupExtension): position = Setting(type=int, nillable=True) expanded = Setting(type=bool, default=True) type = Setting(type=str, nillable=True)
class AudioSettingsExtension(AudioSettings): directory = Setting(type=UserDataPath, default=UserDataPath('history'))
class GroupExtension(GroupExtension): position = Setting(type=int, nillable=True) collapsed = Setting(type=bool, default=False)
class ContactExtension(ContactExtension): presence = PresenceSettingsExtension icon = Setting(type=IconDescriptor, nillable=True, default=None) alternate_icon = Setting(type=IconDescriptor, nillable=True, default=None) preferred_media = SharedSetting(type=str, default='audio')
class MSRPSettings(SettingsGroup): transport = Setting(type=MSRPTransport, default='tls') connection_model = Setting(type=MSRPConnectionModel, default='relay')
class PresenceSettings(SettingsGroup): enabled = Setting(type=bool, default=False)
class BonjourMSRPSettingsExtension(BonjourMSRPSettings): transport = Setting(type=MSRPTransport, default='tls')
class SoundSettings(SettingsGroup): inbound_ringtone = Setting(type=SoundFile, default=None, nillable=True)
class BonjourSIPSettings(SettingsGroup): transport_order = Setting(type=SIPTransportList, default=SIPTransportList(['tcp', 'udp', 'tls']))
class XCAPSettingsExtension(XCAPSettings): enabled = Setting(type=bool, default=True) icon = RuntimeSetting(type=IconDescriptor, nillable=True, default=None)
class MessageSummarySettingsExtension(MessageSummarySettings): enabled = Setting(type=bool, default=True)
class H264Settings(SettingsGroup): profile = Setting(type=H264Profile, default='baseline') level = Setting(type=str, default='3.1')
class MSRPSettingsExtension(MSRPSettings): connection_model = Setting(type=MSRPConnectionModel, default='relay') transport = Setting(type=MSRPTransport, default='tls')
class LogsSettings(SettingsGroup): trace_msrp = Setting(type=bool, default=False) trace_sip = Setting(type=bool, default=False) trace_pjsip = Setting(type=bool, default=False) pjsip_level = Setting(type=PJSIPLogLevel, default=5)
class PresenceSettingsExtension(PresenceSettings): enabled = Setting(type=bool, default=True)
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 """ 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()
class PSTNSettings(SettingsGroup): idd_prefix = Setting(type=unicode, default=None, nillable=True) prefix = Setting(type=unicode, default=None, nillable=True)
class SRTPEncryptionSettings(SettingsGroup): enabled = Setting(type=bool, default=True) key_negotiation = Setting(type=SRTPKeyNegotiation, default='opportunistic')
class SIPSettingsExtension(SIPSettings): always_use_my_proxy = Setting(type=bool, default=True) register = Setting(type=bool, default=True) register_interval = Setting(type=NonNegativeInteger, default=600) subscribe_interval = Setting(type=NonNegativeInteger, default=600) publish_interval = Setting(type=NonNegativeInteger, default=600)
def __set__(self, obj, value): if not _bonjour.available: raise RuntimeError('mdns support is not available') Setting.__set__(self, obj, value)
class ServerSettings(SettingsGroup): conference_server = Setting(type=Hostname, default=None, nillable=True) settings_url = Setting(type=HTTPURL, default=None, nillable=True)
class XCAPSettings(SettingsGroup): enabled = Setting(type=bool, default=False) discovered = Setting(type=bool, default=False) xcap_root = Setting(type=XCAPRoot, default=None, nillable=True)