diff --git a/config.ini.sample b/config.ini.sample index b282fcd..c622548 100644 --- a/config.ini.sample +++ b/config.ini.sample @@ -1,78 +1,82 @@ ; 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 = ; A comma-separated list of hosts or networks to trust. ; The elements can be an IP address in CIDR format, a ; hostname or an IP address (in the latter 2 a mask of 32 ; is assumed), or the special keywords 'any' and 'none' ; (being equivalent to 0.0.0.0/0 and 0.0.0.0/32 ; respectively). It defaults to 'any'. ; trusted_peers = [MSRP] ; MSRP transport settings +; Indicate if ACM should be offered. If set to true (default) the server will offer 'actpass' +; for outbound sessions +; use_acm = True + ; use_tls = False [RTP] ; RTP transport settings ; Allowed codec list, valid values: G722, speex, PCMU, PCMA, iLBC, GSM ; audio_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/configuration/__init__.py b/sylk/configuration/__init__.py index d175e93..1e478b7 100644 --- a/sylk/configuration/__init__.py +++ b/sylk/configuration/__init__.py @@ -1,69 +1,70 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # from application.configuration import ConfigSection, ConfigSetting from application.configuration.datatypes import NetworkRangeList, StringList from application.system import host from sipsimple.configuration.datatypes import NonNegativeInteger, SRTPEncryption from sylk import configuration_filename from sylk.configuration.datatypes import AudioCodecs, IPAddress, Port, PortRange, SIPProxyAddress from sylk.tls import Certificate, PrivateKey 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) trusted_peers = ConfigSetting(type=NetworkRangeList, value=NetworkRangeList('any')) class MSRPConfig(ConfigSection): __cfgfile__ = configuration_filename __section__ = 'MSRP' + use_acm = True 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) class ThorNodeConfig(ConfigSection): __cfgfile__ = configuration_filename __section__ = 'ThorNetwork' enabled = False domain = "sipthor.net" multiply = 1000 certificate = ConfigSetting(type=Certificate, value=None) private_key = ConfigSetting(type=PrivateKey, value=None) ca = ConfigSetting(type=Certificate, value=None) diff --git a/sylk/extensions.py b/sylk/extensions.py index a578a80..6991ba7 100644 --- a/sylk/extensions.py +++ b/sylk/extensions.py @@ -1,140 +1,134 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # import random from datetime import datetime from application.notification import NotificationCenter from eventlet import api from msrplib.connect import get_acceptor, get_connector from msrplib.protocol import URI from msrplib.session import contains_mime_type from sipsimple.account import AccountManager from sipsimple.core import SDPAttribute from sipsimple.payloads.iscomposing import IsComposingMessage, State, LastActive, Refresh, ContentType from sipsimple.streams import MediaStreamRegistry from sipsimple.streams.applications.chat import CPIMMessage from sipsimple.streams.msrp import ChatStream as _ChatStream, ChatStreamError, MSRPStreamBase, MSRPStreamError, NotificationProxyLogger from sipsimple.threading.green import run_in_green_thread from sipsimple.util import TimestampedNotificationData from twisted.python.failure import Failure -from sylk.configuration import SIPConfig +from sylk.configuration import SIPConfig, MSRPConfig # We need to match on the only account that will be available def _always_find_default_account(self, contact_uri): return self.default_account AccountManager.find_account = _always_find_default_account # We need to be able to set the local identity in the message CPIM envelope # so that messages appear to be coming from the users themselves, instead of # just seeying the server identity registry = MediaStreamRegistry() for stream_type in registry.stream_types[:]: if stream_type is _ChatStream: registry.stream_types.remove(stream_type) break del registry class ChatStream(_ChatStream): accept_types = ['message/cpim'] accept_wrapped_types = ['*'] chatroom_capabilities = ['private-messages'] def _create_local_media(self, uri_path): local_media = MSRPStreamBase._create_local_media(self, uri_path) if self.session.local_focus and self.chatroom_capabilities: local_media.attributes.append(SDPAttribute('chatroom', ' '.join(self.chatroom_capabilities))) return local_media def send_message(self, content, content_type='text/plain', local_identity=None, recipients=None, courtesy_recipients=None, subject=None, timestamp=None, required=None, additional_headers=None): if self.direction=='recvonly': raise ChatStreamError('Cannot send message on recvonly stream') message_id = '%x' % random.getrandbits(64) if not contains_mime_type(self.accept_wrapped_types, content_type): raise ChatStreamError('Invalid content_type for outgoing message: %r' % content_type) if not recipients: recipients = [self.remote_identity] if timestamp is None: timestamp = datetime.now() # Only use CPIM, it's the only type we accept msg = CPIMMessage(content, content_type, sender=local_identity or self.local_identity, recipients=recipients, courtesy_recipients=courtesy_recipients, subject=subject, timestamp=timestamp, required=required, additional_headers=additional_headers) self._enqueue_message(message_id, str(msg), 'message/cpim', failure_report='yes', success_report='yes', notify_progress=True) return message_id def send_composing_indication(self, state, refresh, last_active=None, recipients=None, local_identity=None): if self.direction == 'recvonly': raise ChatStreamError('Cannot send message on recvonly stream') if state not in ('active', 'idle'): raise ValueError('Invalid value for composing indication state') message_id = '%x' % random.getrandbits(64) content = IsComposingMessage(state=State(state), refresh=Refresh(refresh), last_active=LastActive(last_active or datetime.now()), content_type=ContentType('text')).toxml() if recipients is None: recipients = [self.remote_identity] # Only use CPIM, it's the only type we accept msg = CPIMMessage(content, IsComposingMessage.content_type, sender=local_identity or self.local_identity, recipients=recipients, timestamp=datetime.now()) self._enqueue_message(message_id, str(msg), 'message/cpim', failure_report='partial', success_report='no') return message_id # Patch MediaStreamBase initialize method # - There is no need for relay support # - We need to choose the outbound IP address @run_in_green_thread def _initialize(self, session, direction): self.greenlet = api.getcurrent() notification_center = NotificationCenter() notification_center.add_observer(self, sender=self) try: self.session = session outgoing = direction=='outgoing' self.transport = self.account.msrp.transport if not outgoing and self.transport == 'tls' and None in (self.account.tls_credentials.cert, self.account.tls_credentials.key): raise MSRPStreamError("Cannot accept MSRP connection without a TLS certificate") logger = NotificationProxyLogger() local_uri = URI(host=SIPConfig.local_ip, port=0, use_tls=self.transport=='tls', credentials=self.account.tls_credentials) - from sipsimple.application import SIPApplication if outgoing: - # TODO: Fix ACM support - if SIPApplication.local_nat_type == 'open': + if MSRPConfig.use_acm: # We start the transport as passive, because we expect the other end to become active. -Saul self.msrp_connector = get_acceptor(relay=None, use_acm=True, logger=logger) self.local_role = 'actpass' else: self.msrp_connector = get_connector(relay=None, use_acm=True, logger=logger) self.local_role = 'active' else: - if self.remote_role == 'actpass': - behind_nat = SIPApplication.local_nat_type != 'open' - self.msrp_connector = get_connector(relay=None, use_acm=True, logger=logger) if behind_nat else get_acceptor(relay=None, use_acm=True, logger=logger) - self.local_role = 'active' if behind_nat else 'passive' - elif self.remote_role == 'passive': - # Not allowed by the draft but play nice for interoperability. -Saul - self.msrp_connector = get_connector(relay=None, use_acm=True, logger=logger) - self.local_role = 'active' - else: + if self.remote_role in ('actpass', 'active'): self.msrp_connector = get_acceptor(relay=None, use_acm=True, logger=logger) self.local_role = 'passive' + else: + # Remote role is passive. Not allowed by the draft but play nice for interoperability. -Saul + self.msrp_connector = get_connector(relay=None, use_acm=True, logger=logger) + self.local_role = 'active' full_local_path = self.msrp_connector.prepare(local_uri) self.local_media = self._create_local_media(full_local_path) except api.GreenletExit: raise except Exception, ex: ndata = TimestampedNotificationData(context='initialize', failure=Failure(), reason=str(ex)) notification_center.post_notification('MediaStreamDidFail', self, ndata) else: notification_center.post_notification('MediaStreamDidInitialize', self, data=TimestampedNotificationData()) finally: if self.msrp_session is None and self.msrp is None and self.msrp_connector is None: notification_center.remove_observer(self, sender=self) self.greenlet = None MSRPStreamBase.initialize = _initialize