diff --git a/config.ini.sample b/config.ini.sample index 0e10650..b282fcd 100644 --- a/config.ini.sample +++ b/config.ini.sample @@ -1,70 +1,78 @@ ; 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 ; 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/applications/__init__.py b/sylk/applications/__init__.py index 8efa343..27a2a88 100644 --- a/sylk/applications/__init__.py +++ b/sylk/applications/__init__.py @@ -1,151 +1,224 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details # __all__ = ['ISylkApplication', 'ApplicationRegistry', 'sylk_application', 'IncomingRequestHandler'] import os +import socket +import struct from application import log +from application.configuration.datatypes import NetworkRange from application.notification import IObserver, NotificationCenter from application.python.util import Null, Singleton +from itertools import chain from sipsimple.threading import run_in_twisted_thread from zope.interface import Attribute, Interface, implements -from sylk.configuration import ServerConfig +from sylk.configuration import ServerConfig, SIPConfig, ThorNodeConfig class ISylkApplication(Interface): """ Interface defining attributes and methods any application must implement. Each application must be a Singleton and has to be decorated with the @sylk_application decorator. """ __appname__ = Attribute("Application name") def incoming_session(self, session): pass def incoming_subscription(self, subscribe_request, data): pass def incoming_referral(self, refer_request, data): pass def incoming_sip_message(self, message_request, data): pass class ApplicationRegistry(object): __metaclass__ = Singleton def __init__(self): self.applications = [] def __iter__(self): return iter(self.applications) def add(self, app): if app not in self.applications: self.applications.append(app) def sylk_application(cls): """Class decorator for adding applications to the ApplicationRegistry""" ApplicationRegistry().add(cls()) return cls def load_applications(): toplevel = os.path.dirname(__file__) app_list = ['sylk.applications.%s' % item for item in os.listdir(toplevel) if os.path.isdir(os.path.join(toplevel, item)) and '__init__.py' in os.listdir(os.path.join(toplevel, item))] map(__import__, app_list) class ApplicationNotLoadedError(Exception): pass class IncomingRequestHandler(object): """ Handle incoming requests and match them to applications. """ __metaclass__ = Singleton implements(IObserver) - # TODO: apply ACLs (before or after?) def __init__(self): load_applications() log.msg('Loaded applications: %s' % ', '.join([app.__appname__ for app in ApplicationRegistry()])) self.application_map = dict((item.split(':')) for item in ServerConfig.application_map) + self.authorization_handler = AuthorizationHandler() def start(self): + self.authorization_handler.start() notification_center = NotificationCenter() notification_center.add_observer(self, name='SIPSessionNewIncoming') notification_center.add_observer(self, name='SIPIncomingSubscriptionGotSubscribe') notification_center.add_observer(self, name='SIPIncomingReferralGotRefer') notification_center.add_observer(self, name='SIPIncomingRequestGotRequest') def stop(self): + self.authorization_handler.stop() notification_center = NotificationCenter() notification_center.remove_observer(self, name='SIPSessionNewIncoming') notification_center.remove_observer(self, name='SIPIncomingSubscriptionGotSubscribe') notification_center.remove_observer(self, name='SIPIncomingReferralGotRefer') notification_center.remove_observer(self, name='SIPIncomingRequestGotRequest') def get_application(self, uri): application = self.application_map.get(uri.user, ServerConfig.default_application) try: app = (app for app in ApplicationRegistry() if app.__appname__ == application).next() except StopIteration: log.error('Application %s is not loaded' % application) raise ApplicationNotLoadedError else: return app @run_in_twisted_thread def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_SIPSessionNewIncoming(self, notification): session = notification.sender + try: + self.authorization_handler.authorize_source(session.peer_address.ip) + except UnauthorizedRequest: + session.reject(403) + return try: app = self.get_application(session._invitation.request_uri) except ApplicationNotLoadedError: session.reject(404) else: app.incoming_session(session) def _NH_SIPIncomingSubscriptionGotSubscribe(self, notification): subscribe_request = notification.sender + try: + self.authorization_handler.authorize_source(subscribe_request.peer_address.ip) + except UnauthorizedRequest: + subscribe_request.reject(403) + return try: app = self.get_application(notification.data.request_uri) except ApplicationNotLoadedError: subscribe_request.reject(404) else: app.incoming_subscription(subscribe_request, notification.data) def _NH_SIPIncomingReferralGotRefer(self, notification): refer_request = notification.sender + try: + self.authorization_handler.authorize_source(refer_request.peer_address.ip) + except UnauthorizedRequest: + refer_request.reject(403) + return try: app = self.get_application(notification.data.request_uri) except ApplicationNotLoadedError: refer_request.reject(404) else: app.incoming_referral(refer_request, notification.data) def _NH_SIPIncomingRequestGotRequest(self, notification): request = notification.sender if notification.data.method != 'MESSAGE': request.answer(405) return + try: + self.authorization_handler.authorize_source(request.peer_address.ip) + except UnauthorizedRequest: + request.answer(403) + return try: app = self.get_application(notification.data.request_uri) except ApplicationNotLoadedError: request.answer(404) else: app.incoming_sip_message(request, notification.data) +class UnauthorizedRequest(Exception): + pass + +class AuthorizationHandler(object): + implements(IObserver) + + def __init__(self): + self.state = None + self.trusted_peers = SIPConfig.trusted_peers + self.thor_nodes = [] + + @property + def trusted_parties(self): + if ThorNodeConfig.enabled: + return self.thor_nodes + return self.trusted_peers + + def start(self): + notification_center = NotificationCenter() + notification_center.add_observer(self, name='ThorNetworkGotUpdate') + self.state = 'started' + + def stop(self): + self.state = 'stopped' + notification_center = NotificationCenter() + notification_center.remove_observer(self, name='ThorNetworkGotUpdate') + + def authorize_source(self, ip_address): + if self.state != 'started': + raise UnauthorizedRequest + for range in self.trusted_parties: + if struct.unpack('!L', socket.inet_aton(ip_address))[0] & range[1] == range[0]: + return True + raise UnauthorizedRequest + + @run_in_twisted_thread + def handle_notification(self, notification): + handler = getattr(self, '_NH_%s' % notification.name, Null) + handler(notification) + + def _NH_ThorNetworkGotUpdate(self, notification): + thor_nodes = [] + for node in chain(*(n.nodes for n in notification.data.networks.values())): + thor_nodes.append(NetworkRange(node)) + self.thor_nodes = thor_nodes + + diff --git a/sylk/configuration/__init__.py b/sylk/configuration/__init__.py index 8d3d80e..d175e93 100644 --- a/sylk/configuration/__init__.py +++ b/sylk/configuration/__init__.py @@ -1,68 +1,69 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # from application.configuration import ConfigSection, ConfigSetting -from application.configuration.datatypes import StringList +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_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)