diff --git a/sylk/configuration/__init__.py b/sylk/configuration/__init__.py index 107cd1c..92830eb 100644 --- a/sylk/configuration/__init__.py +++ b/sylk/configuration/__init__.py @@ -1,55 +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, SIPProxyAddress, SRTPEncryption +from sipsimple.configuration.datatypes import NonNegativeInteger, SRTPEncryption from sylk import configuration_filename -from sylk.configuration.datatypes import AudioCodecs, IPAddress, Port, PortRange +from sylk.configuration.datatypes import AudioCodecs, IPAddress, Port, PortRange, SIPProxyAddress 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/datatypes.py b/sylk/configuration/datatypes.py index a236317..0ad3edf 100644 --- a/sylk/configuration/datatypes.py +++ b/sylk/configuration/datatypes.py @@ -1,99 +1,144 @@ # Copyright (C) 2010-2011 AG Projects. See LICENSE for details. # import os import re import socket import sys -from sipsimple.configuration.datatypes import AudioCodecList +from sipsimple.configuration.datatypes import AudioCodecList, Hostname, SIPTransport from sipsimple.util import classproperty class AudioCodecs(list): def __new__(cls, value): if isinstance(value, (tuple, list)): return [str(x) for x in value if x in AudioCodecList.available_values] or None elif isinstance(value, basestring): if value.lower() in ('none', ''): return None return [x for x in re.split(r'\s*,\s*', value) if x in AudioCodecList.available_values] or None else: raise TypeError("value must be a string, list or tuple") class IPAddress(str): """An IP address in quad dotted number notation""" def __new__(cls, value): if value == '0.0.0.0': raise ValueError("%s is not allowed, please specify a specific IP address" % value) else: try: socket.inet_aton(value) except socket.error: raise ValueError("invalid IP address: %r" % value) except TypeError: raise TypeError("value must be a string") return str(value) class ResourcePath(object): def __init__(self, path): self.path = os.path.normpath(str(path)) def __getstate__(self): return unicode(self.path) def __setstate__(self, state): self.__init__(state) @property def normalized(self): path = os.path.expanduser(self.path) if os.path.isabs(path): return os.path.realpath(path) return os.path.realpath(os.path.join(self.resources_directory, path)) @classproperty def resources_directory(cls): binary_directory = os.path.dirname(os.path.realpath(sys.argv[0])) if os.path.basename(binary_directory) == 'bin': application_directory = os.path.dirname(binary_directory) resources_component = 'share/sylkserver' else: application_directory = binary_directory resources_component = 'resources' return os.path.realpath(os.path.join(application_directory, resources_component)) def __eq__(self, other): try: return self.path == other.path except AttributeError: return False def __hash__(self): return hash(self.path) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.path) def __unicode__(self): return unicode(self.path) class Port(int): def __new__(cls, value): try: value = int(value) except ValueError: return None if not (0 <= value <= 65535): raise ValueError("illegal port value: %s" % value) return value class PortRange(object): """A port range in the form start:end with start and end being even numbers in the [1024, 65536] range""" def __init__(self, value): self.start, self.end = [int(p) for p in value.split(':', 1)] allowed = xrange(1024, 65537, 2) if not (self.start in allowed and self.end in allowed and self.start < self.end): raise ValueError("bad range: %r: ports must be even numbers in the range [1024, 65536] with start < end" % value) +class SIPProxyAddress(object): + _description_re = re.compile(r"^(?P(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)*))(:(?P\d+))?(;transport=(?P.+))?$") + + def __new__(cls, description): + if not description: + return None + if not cls._description_re.match(description): + raise ValueError("illegal SIP proxy address: %s" % description) + return super(SIPProxyAddress, cls).__new__(cls) + + def __init__(self, description): + match = self.__class__._description_re.match(description) + data = match.groupdict() + host = data.get('host') + port = data.get('port', None) or 5060 + transport = data.get('transport', None) or 'udp' + self.host = Hostname(host) + self.port = Port(port) + if self.port == 0: + raise ValueError("illegal port value: 0") + self.transport = SIPTransport(transport) + + def __getstate__(self): + return unicode(self) + + def __setstate__(self, state): + if not self.__class__._description_re.match(state): + raise ValueError("illegal SIP proxy address: %s" % state) + self.__init__(state) + + def __eq__(self, other): + try: + return (self.host, self.port, self.transport) == (other.host, other.port, other.transport) + except AttributeError: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.host, self.port, self.transport)) + + def __unicode__(self): + return u'%s:%d;transport=%s' % (self.host, self.port, self.transport) + diff --git a/sylk/configuration/settings.py b/sylk/configuration/settings.py index 9fd5bf7..7e4396c 100644 --- a/sylk/configuration/settings.py +++ b/sylk/configuration/settings.py @@ -1,123 +1,121 @@ # 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, SIPProxyAddress, SIPTransportList, SRTPEncryption +from sipsimple.configuration.datatypes import AudioCodecList, MSRPTransport, NonNegativeInteger, Path, Port, PortRange, SampleRate, 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 +from sylk.configuration.datatypes import SIPProxyAddress # 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