diff --git a/config.ini.sample b/config.ini.sample index 237fcc1..6c08419 100644 --- a/config.ini.sample +++ b/config.ini.sample @@ -1,67 +1,70 @@ ; SylkServer configuration file [Server] ; The following settings are the default used by the software, uncomment ; them only if you want to make changes ; default_application = conference ; Map user part of the Request URI to a specific application ; application_map = 123:conference,test:irc-conference ; trace_dir = /var/log/sylkserver ; trace_sip = False ; trace_msrp = False ; trace_notifications = False ; TLS can be used for encryption of SIP signaling and MSRP media. TLS is ; disabled by default. To enabled TLS, you must have a valid X.509 ; certificate and configure it below, then set the local_tls_port in the SIP ; section and use_tls in MSRP section ; The X.509 Certificate Authorities file ; ca_file = /etc/sylkserver/tls/ca.crt ; The file containing X.509 certificate and private key in unencrypted format ; certificate = /etc/sylkserver/tls/sylkserver.crt ; verify_server = False [SIP] ; SIP transport settings ; IP address used for SIP signaling; empty string or any means listen on interface used ; by the default route ; local_ip = ; Ports used for SIP transports, if not set to any value the transport will be disabled ; local_udp_port = 5060 ; local_tcp_port = 5060 ; local_tls_port = +; If set all outbound SIP requests will be sent through this SIP proxy +; outbound_proxy = + [MSRP] ; MSRP transport settings ; use_tls = False [RTP] ; RTP transport settings ; Allowed codec list, valid values: G722, speex, PCMU, PCMA, iLBC, GSM ; allowed_codecs = G722,speex,PCMU,PCMA ; Port range used for RTP ; port_range = 50000:50500 ; SRTP valid values: disabled, mandatory, optional ; srtp_encryption = optional ; RTP stream timeout, session will be disconnected after this value ; timeout = 30 diff --git a/sylk/applications/conference/__init__.py b/sylk/applications/conference/__init__.py index ccd1c92..cd41781 100644 --- a/sylk/applications/conference/__init__.py +++ b/sylk/applications/conference/__init__.py @@ -1,243 +1,250 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details # import re from application import log from application.notification import IObserver, NotificationCenter from application.python.util import Null, Singleton from sipsimple.account import AccountManager from sipsimple.configuration.settings import SIPSimpleSettings from sipsimple.core import SIPURI, SIPCoreError, Header, ContactHeader, FromHeader, ToHeader from sipsimple.lookup import DNSLookup from sipsimple.streams import AudioStream from twisted.internet import reactor from zope.interface import implements from sylk.applications import ISylkApplication, sylk_application from sylk.applications.conference.configuration import ConferenceConfig from sylk.applications.conference.room import Room from sylk.configuration import SIPConfig from sylk.extensions import ChatStream from sylk.session import ServerSession # Initialize database from sylk.applications.conference import database @sylk_application class ConferenceApplication(object): __metaclass__ = Singleton implements(ISylkApplication, IObserver) __appname__ = 'conference' def __init__(self): self.rooms = set() self.pending_sessions = [] def incoming_session(self, session): log.msg('New incoming session from %s' % session.remote_identity.uri) audio_streams = [stream for stream in session.proposed_streams if stream.type=='audio'] chat_streams = [stream for stream in session.proposed_streams if stream.type=='chat'] if not audio_streams and not chat_streams: session.reject(488) return self.pending_sessions.append(session) notification_center = NotificationCenter() notification_center.add_observer(self, sender=session) if audio_streams: session.send_ring_indication() streams = [streams[0] for streams in (audio_streams, chat_streams) if streams] reactor.callLater(4 if audio_streams else 0, self.accept_session, session, streams) def incoming_subscription(self, subscribe_request, data): to_header = data.headers.get('To', Null) if to_header is Null: subscribe_request.reject(400) return room = Room.get_room(data.request_uri) if not room.started: room = Room.get_room(to_header.uri) if not room.started: subscribe_request.reject(480) return room.handle_incoming_subscription(subscribe_request, data) def incoming_referral(self, refer_request, data): from_header = data.headers.get('From', Null) to_header = data.headers.get('To', Null) refer_to_header = data.headers.get('Refer-To', Null) if Null in (from_header, to_header, refer_to_header): refer_request.reject(400) return referral_handler = IncomingReferralHandler(refer_request, data) referral_handler.start() def incoming_sip_message(self, message_request, data): if not ConferenceConfig.enable_sip_message: return room = Room.get_room(data.request_uri) if not room.started: message_request.answer(480) return room.handle_incoming_sip_message(message_request, data) def accept_session(self, session, streams): if session in self.pending_sessions: session.accept(streams, is_focus=True) def add_participant(self, session, room_uri): log.msg('New outgoing session to %s' % session.remote_identity.uri) notification_center = NotificationCenter() notification_center.add_observer(self, sender=session) room = Room.get_room(room_uri) room.start() room.add_session(session) self.rooms.add(room) def remove_participant(self, participant_uri, room_uri): room = Room.get_room(room_uri) room.terminate_sessions(participant_uri) def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPSessionDidStart(self, notification): session = notification.sender self.pending_sessions.remove(session) room = Room.get_room(session._invitation.request_uri) # FIXME room.start() room.add_session(session) self.rooms.add(room) def _NH_SIPSessionDidEnd(self, notification): session = notification.sender log.msg('Session from %s ended' % session.remote_identity.uri) notification_center = NotificationCenter() notification_center.remove_observer(self, sender=session) if session.direction == 'incoming': room = Room.get_room(session._invitation.request_uri) # FIXME else: room = Room.get_room(session.local_identity.uri) if session in room.sessions: # We could get this notifiction even if we didn't get SIPSessionDidStart room.remove_session(session) if room.empty: room.stop() try: self.rooms.remove(room) except KeyError: pass def _NH_SIPSessionDidFail(self, notification): session = notification.sender self.pending_sessions.remove(session) log.msg('Session from %s failed' % session.remote_identity.uri) class IncomingReferralHandler(object): implements(IObserver) def __init__(self, refer_request, data): self._refer_request = refer_request self._refer_headers = data.headers self.room_uri = data.headers.get('To').uri self.refer_to_uri = data.headers.get('Refer-To').uri self.method = data.headers.get('Refer-To').parameters.get('method', 'invite').lower() self.session = None self.streams = [] def start(self): if not re.match('^(sip:|sips:).*', self.refer_to_uri): self.refer_to_uri = 'sip:%s' % self.refer_to_uri try: self.refer_to_uri = SIPURI.parse(self.refer_to_uri) except SIPCoreError: self._refer_request.reject(488) return notification_center = NotificationCenter() notification_center.add_observer(self, sender=self._refer_request) if self.method == 'invite': self._refer_request.accept() settings = SIPSimpleSettings() + account = AccountManager().default_account + if account.sip.outbound_proxy is not None: + uri = SIPURI(host=account.sip.outbound_proxy.host, + port=account.sip.outbound_proxy.port, + parameters={'transport': account.sip.outbound_proxy.transport}) + else: + uri = self.refer_to_uri lookup = DNSLookup() notification_center.add_observer(self, sender=lookup) - lookup.lookup_sip_proxy(self.refer_to_uri, settings.sip.transport_list) + lookup.lookup_sip_proxy(uri, settings.sip.transport_list) elif self.method == 'bye': self._refer_request.accept() conference_application = ConferenceApplication() conference_application.remove_participant(self.refer_to_uri, self.room_uri) self._refer_request.end(200) else: self._refer_request.reject(488) def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_DNSLookupDidSucceed(self, notification): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=notification.sender) account = AccountManager().default_account self.streams.append(AudioStream(account)) self.streams.append(ChatStream(account)) self.session = ServerSession(account) notification_center.add_observer(self, sender=self.session) original_from_header = self._refer_headers.get('From') if original_from_header.display_name: original_identity = "%s <%s@%s>" % (original_from_header.display_name, original_from_header.uri.user, original_from_header.uri.host) else: original_identity = "%s@%s" % (original_from_header.uri.user, original_from_header.uri.host) from_header = FromHeader(SIPURI.new(self.room_uri)) to_header = ToHeader(self.refer_to_uri) transport = notification.data.result[0].transport parameters = {} if transport=='udp' else {'transport': transport} contact_header = ContactHeader(SIPURI(user=self.room_uri.user, host=SIPConfig.local_ip, port=getattr(SIPConfig, 'local_%s_port' % transport), parameters=parameters)) extra_headers = [] if self._refer_headers.get('Referred-By', None) is not None: extra_headers.append(Header.new(self._refer_headers.get('Referred-By'))) subject = u'Join conference request from: %s' % original_identity self.session.connect(from_header, to_header, contact_header, routes=notification.data.result, streams=self.streams, is_focus=True, subject=subject, extra_headers=extra_headers) def _NH_DNSLookupDidFail(self, notification): NotificationCenter().remove_observer(self, sender=notification.sender) def _NH_SIPSessionGotRingIndication(self, notification): if self._refer_request is not None: self._refer_request.send_notify(180) def _NH_SIPSessionGotProvisionalResponse(self, notification): if self._refer_request is not None: self._refer_request.send_notify(notification.data.code) def _NH_SIPSessionDidStart(self, notification): NotificationCenter().remove_observer(self, sender=notification.sender) if self._refer_request is not None: self._refer_request.end(200) conference_application = ConferenceApplication() conference_application.add_participant(self.session, self.room_uri) self.session = None self.streams = [] def _NH_SIPSessionDidFail(self, notification): NotificationCenter().remove_observer(self, sender=notification.sender) if self._refer_request is not None: self._refer_request.end(notification.data.code or 500) self.session = None self.streams = [] def _NH_SIPSessionDidEnd(self, notification): # If any stream fails to start we won't get SIPSessionDidFail, we'll get here instead NotificationCenter().remove_observer(self, sender=notification.sender) if self._refer_request is not None: self._refer_request.end(200) self.session = None self.streams = [] def _NH_SIPIncomingReferralDidEnd(self, notification): NotificationCenter().remove_observer(self, sender=notification.sender) self._refer_request = None self._refer_headers = None diff --git a/sylk/configuration/__init__.py b/sylk/configuration/__init__.py index b27e4f7..107cd1c 100644 --- a/sylk/configuration/__init__.py +++ b/sylk/configuration/__init__.py @@ -1,54 +1,55 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # from application.configuration import ConfigSection, ConfigSetting from application.configuration.datatypes import StringList from application.system import host -from sipsimple.configuration.datatypes import NonNegativeInteger, SRTPEncryption +from sipsimple.configuration.datatypes import NonNegativeInteger, SIPProxyAddress, SRTPEncryption from sylk import configuration_filename from sylk.configuration.datatypes import AudioCodecs, IPAddress, Port, PortRange class ServerConfig(ConfigSection): __cfgfile__ = configuration_filename __section__ = 'Server' ca_file = ConfigSetting(type=str, value='/etc/sylkserver/tls/ca.crt') certificate = ConfigSetting(type=str, value='/etc/sylkserver/tls/sylkserver.crt') verify_server = False default_application = 'conference' application_map = ConfigSetting(type=StringList, value='') trace_dir = ConfigSetting(type=str, value='/var/log/sylkserver') trace_sip = False trace_msrp = False trace_notifications = False class SIPConfig(ConfigSection): __cfgfile__ = configuration_filename __section__ = 'SIP' local_ip = ConfigSetting(type=IPAddress, value=host.default_ip) local_udp_port = ConfigSetting(type=Port, value=5060) local_tcp_port = ConfigSetting(type=Port, value=5060) local_tls_port = ConfigSetting(type=Port, value=None) + outbound_proxy = ConfigSetting(type=SIPProxyAddress, value=None) class MSRPConfig(ConfigSection): __cfgfile__ = configuration_filename __section__ = 'MSRP' use_tls = False class RTPConfig(ConfigSection): __cfgfile__ = configuration_filename __section__ = 'RTP' audio_codecs = ConfigSetting(type=AudioCodecs, value=None) port_range = ConfigSetting(type=PortRange, value=PortRange('50000:50500')) srtp_encryption = ConfigSetting(type=SRTPEncryption, value='optional') timeout = ConfigSetting(type=NonNegativeInteger, value=30) diff --git a/sylk/configuration/settings.py b/sylk/configuration/settings.py index 02939c6..9fd5bf7 100644 --- a/sylk/configuration/settings.py +++ b/sylk/configuration/settings.py @@ -1,122 +1,123 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # """ SIP SIMPLE SDK settings extensions. """ __all__ = ['AccountExtension', 'BonjourAccountExtension', 'SylkServerSettingsExtension'] import os from sipsimple.account import MSRPSettings as AccountMSRPSettings, NATTraversalSettings as AccountNATTraversalSettings from sipsimple.account import RTPSettings as AccountRTPSettings, SIPSettings as AccountSIPSettings, TLSSettings as AccountTLSSettings from sipsimple.configuration import CorrelatedSetting, Setting, SettingsObjectExtension -from sipsimple.configuration.datatypes import AudioCodecList, MSRPTransport, NonNegativeInteger, Path, Port, PortRange, SampleRate, SIPTransportList, SRTPEncryption +from sipsimple.configuration.datatypes import AudioCodecList, MSRPTransport, NonNegativeInteger, Path, Port, PortRange, SampleRate, SIPProxyAddress, SIPTransportList, SRTPEncryption from sipsimple.configuration.settings import AudioSettings, LogsSettings, RTPSettings, SIPSettings, TLSSettings from sylk import __version__ as server_version from sylk.configuration import ServerConfig, SIPConfig, MSRPConfig, RTPConfig # Account settings extensions msrp_transport = 'tls' if MSRPConfig.use_tls else 'tcp' class AccountMSRPSettingsExtension(AccountMSRPSettings): transport = Setting(type=MSRPTransport, default=msrp_transport) class AccountNATTraversalSettingsExtension(AccountNATTraversalSettings): use_msrp_relay_for_inbound = Setting(type=bool, default=False) use_msrp_relay_for_outbound = Setting(type=bool, default=False) class AccountRTPSettingsExtension(AccountRTPSettings): audio_codec_list = Setting(type=AudioCodecList, default=RTPConfig.audio_codecs, nillable=True) srtp_encryption = Setting(type=SRTPEncryption, default=RTPConfig.srtp_encryption) use_srtp_without_tls = Setting(type=bool, default=True) class AccountSIPSettingsExtension(AccountSIPSettings): register = Setting(type=bool, default=False) + outbound_proxy = Setting(type=SIPProxyAddress, default=SIPConfig.outbound_proxy, nillable=True) tls_certificate = Path(ServerConfig.certificate) if ServerConfig.certificate and os.path.isfile(ServerConfig.certificate) else None class AccountTLSSettingsExtension(AccountTLSSettings): certificate = Setting(type=Path, default=tls_certificate, nillable=True) verify_server = Setting(type=bool, default=ServerConfig.verify_server) class AccountExtension(SettingsObjectExtension): enabled = Setting(type=bool, default=True) msrp = AccountMSRPSettingsExtension nat_traversal = AccountNATTraversalSettingsExtension rtp = AccountRTPSettingsExtension sip = AccountSIPSettingsExtension tls = AccountTLSSettingsExtension class BonjourAccountExtension(SettingsObjectExtension): enabled = Setting(type=bool, default=False) # General settings extensions class AudioSettingsExtension(AudioSettings): input_device = Setting(type=str, default=None, nillable=True) output_device = Setting(type=str, default=None, nillable=True) sample_rate = Setting(type=SampleRate, default=16000) log_directory = Path(ServerConfig.trace_dir) if ServerConfig.trace_dir else None class LogsSettingsExtension(LogsSettings): directory = Setting(type=Path, default=log_directory) trace_sip = Setting(type=bool, default=ServerConfig.trace_sip) trace_msrp = Setting(type=bool, default=ServerConfig.trace_msrp) trace_pjsip = Setting(type=bool, default=False) trace_notifications = Setting(type=bool, default=ServerConfig.trace_notifications) class RTPSettingsExtension(RTPSettings): port_range = Setting(type=PortRange, default=PortRange(RTPConfig.port_range.start, RTPConfig.port_range.end)) timeout = Setting(type=NonNegativeInteger, default=RTPConfig.timeout) def sip_port_validator(port, sibling_port): if port == sibling_port != 0: raise ValueError("the TCP and TLS ports must be different") transport_list = [] if SIPConfig.local_udp_port: transport_list.append('udp') if SIPConfig.local_tcp_port: transport_list.append('tcp') if SIPConfig.local_tls_port: transport_list.append('tls') udp_port = SIPConfig.local_udp_port or 0 tcp_port = SIPConfig.local_tcp_port or 0 tls_port = SIPConfig.local_tls_port or 0 class SIPSettingsExtension(SIPSettings): udp_port = Setting(type=Port, default=udp_port) tcp_port = CorrelatedSetting(type=Port, sibling='tls_port', validator=sip_port_validator, default=tcp_port) tls_port = CorrelatedSetting(type=Port, sibling='tcp_port', validator=sip_port_validator, default=tls_port) transport_list = Setting(type=SIPTransportList, default=transport_list) ca_list = Path(ServerConfig.ca_file) if ServerConfig.ca_file and os.path.isfile(ServerConfig.ca_file) else None class TLSSettingsExtension(TLSSettings): ca_list = Setting(type=Path, default=ca_list, nillable=True) class SylkServerSettingsExtension(SettingsObjectExtension): user_agent = Setting(type=str, default='SylkServer-%s' % server_version) audio = AudioSettingsExtension logs = LogsSettingsExtension rtp = RTPSettingsExtension sip = SIPSettingsExtension tls = TLSSettingsExtension