mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +00:00 
			
		
		
		
	* Remove .gitreview and switch to pulling the main asterisk branch version from configure.ac instead. * Replace references to JIRA with GitHub. * Other minor cleanup found along the way. Resolves: #39
		
			
				
	
	
		
			1331 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1331 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| from __future__ import print_function
 | |
| 
 | |
| import sys
 | |
| import optparse
 | |
| import socket
 | |
| try:
 | |
|     from urllib.parse import urlparse
 | |
| except ImportError:
 | |
|     from urlparse import urlparse # Python 2.7 required for Literal IPv6 Addresses
 | |
| import astdicts
 | |
| import astconfigparser
 | |
| 
 | |
| PREFIX = 'pjsip_'
 | |
| QUIET = False
 | |
| 
 | |
| ###############################################################################
 | |
| ### some utility functions
 | |
| ###############################################################################
 | |
| 
 | |
| 
 | |
| def section_by_type(section, pjsip, type):
 | |
|     """Finds a section based upon the given type, adding it if not found."""
 | |
|     def __find_dict(mdicts, key, val):
 | |
|         """Given a list of multi-dicts, return the multi-dict that contains
 | |
|            the given key/value pair."""
 | |
| 
 | |
|         def found(d):
 | |
|             return key in d and val in d[key]
 | |
| 
 | |
|         try:
 | |
|             return [d for d in mdicts if found(d)][0]
 | |
|         except IndexError:
 | |
|             raise LookupError("Dictionary not located for key = %s, value = %s"
 | |
|                               % (key, val))
 | |
| 
 | |
|     try:
 | |
|         return __find_dict(pjsip.section(section), 'type', type)
 | |
|     except LookupError:
 | |
|         # section for type doesn't exist, so add
 | |
|         sect = pjsip.add_section(section)
 | |
|         sect['type'] = type
 | |
|         return sect
 | |
| 
 | |
| 
 | |
| def ignore(key=None, val=None, section=None, pjsip=None,
 | |
|            nmapped=None, type='endpoint'):
 | |
|     """Ignore a key and mark it as mapped"""
 | |
| 
 | |
| 
 | |
| def set_value(key=None, val=None, section=None, pjsip=None,
 | |
|               nmapped=None, type='endpoint'):
 | |
|     """Sets the key to the value within the section in pjsip.conf"""
 | |
|     def _set_value(k, v, s, r, n):
 | |
|         set_value(key if key else k, v, s, r, n, type)
 | |
| 
 | |
|     # if no value or section return the set_value
 | |
|     # function with the enclosed key and type
 | |
|     if not val and not section:
 | |
|         return _set_value
 | |
| 
 | |
|     # otherwise try to set the value
 | |
|     section_by_type(section, pjsip, type)[key] = \
 | |
|         val[0] if isinstance(val, list) else val
 | |
| 
 | |
| 
 | |
| def merge_value(key=None, val=None, section=None, pjsip=None,
 | |
|                 nmapped=None, type='endpoint', section_to=None,
 | |
|                 key_to=None):
 | |
|     """Merge values from the given section with those from the default."""
 | |
|     def _merge_value(k, v, s, r, n):
 | |
|         merge_value(key if key else k, v, s, r, n, type, section_to, key_to)
 | |
| 
 | |
|     # if no value or section return the merge_value
 | |
|     # function with the enclosed key and type
 | |
|     if not val and not section:
 | |
|         return _merge_value
 | |
| 
 | |
|     # should return a single value section list
 | |
|     try:
 | |
|         sect = sip.section(section)[0]
 | |
|     except LookupError:
 | |
|         sect = sip.default(section)[0]
 | |
|     # for each merged value add it to pjsip.conf
 | |
|     for i in sect.get_merged(key):
 | |
|         set_value(key_to if key_to else key, i,
 | |
|                   section_to if section_to else section,
 | |
|                   pjsip, nmapped, type)
 | |
| 
 | |
| def merge_codec_value(key=None, val=None, section=None, pjsip=None,
 | |
|                 nmapped=None, type='endpoint', section_to=None,
 | |
|                 key_to=None):
 | |
|     """Merge values from allow/deny with those from the default. Special treatment for all"""
 | |
|     def _merge_codec_value(k, v, s, r, n):
 | |
|         merge_codec_value(key if key else k, v, s, r, n, type, section_to, key_to)
 | |
| 
 | |
|     # if no value or section return the merge_codec_value
 | |
|     # function with the enclosed key and type
 | |
|     if not val and not section:
 | |
|         return _merge_codec_value
 | |
| 
 | |
|     if key == 'allow':
 | |
|         try:
 | |
|             disallow = sip.get(section, 'disallow')[0]
 | |
|             if disallow == 'all':
 | |
|                 #don't inherit
 | |
|                 for i in sip.get(section, 'allow'):
 | |
|                     set_value(key, i, section, pjsip, nmapped, type)
 | |
|             else:
 | |
|                 merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
 | |
|         except LookupError:
 | |
|             print("lookup error", file=sys.stderr)
 | |
|             merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
 | |
|             return
 | |
|     elif key == 'disallow':
 | |
|         try:
 | |
|             allow = sip.get(section, 'allow')[0]
 | |
|             if allow == 'all':
 | |
|                 #don't inherit
 | |
|                 for i in sip.get(section, 'disallow'):
 | |
|                     set_value(key, i, section, pjsip, nmapped, type)
 | |
|             else:
 | |
|                 merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
 | |
|         except LookupError:
 | |
|             merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
 | |
|             return
 | |
|     else:
 | |
|         merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
 | |
| 
 | |
| 
 | |
| def non_mapped(nmapped):
 | |
|     """Write non-mapped sip.conf values to the non-mapped object"""
 | |
|     def _non_mapped(section, key, val):
 | |
|         """Writes a non-mapped value from sip.conf to the non-mapped object."""
 | |
|         if section not in nmapped:
 | |
|             nmapped[section] = astconfigparser.Section()
 | |
|             if isinstance(val, list):
 | |
|                 for v in val:
 | |
|                     # since coming from sip.conf we can assume
 | |
|                     # single section lists
 | |
|                     nmapped[section][0][key] = v
 | |
|             else:
 | |
|                 nmapped[section][0][key] = val
 | |
|     return _non_mapped
 | |
| 
 | |
| ###############################################################################
 | |
| ### mapping functions -
 | |
| ###      define f(key, val, section) where key/val are the key/value pair to
 | |
| ###      write to given section in pjsip.conf
 | |
| ###############################################################################
 | |
| 
 | |
| 
 | |
| def set_dtmfmode(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     Sets the dtmfmode value.  If value matches allowable option in pjsip
 | |
|     then map it, otherwise set it to none.
 | |
|     """
 | |
|     key = 'dtmf_mode'
 | |
|     # available pjsip.conf values: rfc4733, inband, info, none
 | |
|     if val == 'inband' or val == 'info' or val == 'auto':
 | |
|         set_value(key, val, section, pjsip, nmapped)
 | |
|     elif val == 'rfc2833':
 | |
|         set_value(key, 'rfc4733', section, pjsip, nmapped)
 | |
|     else:
 | |
|         nmapped(section, key, val + " ; did not fully map - set to none")
 | |
|         set_value(key, 'none', section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def setup_udptl(section, pjsip, nmapped):
 | |
|     """Sets values from udptl into the appropriate pjsip.conf options."""
 | |
|     try:
 | |
|         val = sip.get(section, 't38pt_udptl')[0]
 | |
|     except LookupError:
 | |
|         try:
 | |
|             val = sip.get('general', 't38pt_udptl')[0]
 | |
|         except LookupError:
 | |
|             return
 | |
| 
 | |
|     ec = 'none'
 | |
|     if 'yes' in val:
 | |
|         set_value('t38_udptl', 'yes', section, pjsip, nmapped)
 | |
|     if 'no' in val:
 | |
|         set_value('t38_udptl', 'no', section, pjsip, nmapped)
 | |
|     if 'redundancy' in val:
 | |
|         ec = 'redundancy'
 | |
|     if 'fec' in val:
 | |
|         ec = 'fec'
 | |
|     set_value('t38_udptl_ec', ec, section, pjsip, nmapped)
 | |
| 
 | |
| def from_nat(key, val, section, pjsip, nmapped):
 | |
|     """Sets values from nat into the appropriate pjsip.conf options."""
 | |
|     # nat from sip.conf can be comma separated list of values:
 | |
|     # yes/no, [auto_]force_rport, [auto_]comedia
 | |
|     if 'yes' in val:
 | |
|         set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
 | |
|         set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
 | |
|     if 'comedia' in val:
 | |
|         set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
 | |
|     if 'force_rport' in val:
 | |
|         set_value('force_rport', 'yes', section, pjsip, nmapped)
 | |
|         set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def set_timers(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     Sets the timers in pjsip.conf from the session-timers option
 | |
|     found in sip.conf.
 | |
|     """
 | |
|     # pjsip.conf values can be yes/no, required, always
 | |
|     # 'required' is a new feature of chan_pjsip, which rejects
 | |
|     #            all SIP clients not supporting Session Timers
 | |
|     # 'Accept' is the default value of chan_sip and maps to 'yes'
 | |
|     # chan_sip ignores the case, for example 'session-timers=Refuse'
 | |
|     val = val.lower()
 | |
|     if val == 'originate':
 | |
|         set_value('timers', 'always', section, pjsip, nmapped)
 | |
|     elif val == 'refuse':
 | |
|         set_value('timers', 'no', section, pjsip, nmapped)
 | |
|     else:
 | |
|         set_value('timers', 'yes', section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def set_direct_media(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     Maps values from the sip.conf comma separated direct_media option
 | |
|     into pjsip.conf direct_media options.
 | |
|     """
 | |
|     if 'yes' in val:
 | |
|         set_value('direct_media', 'yes', section, pjsip, nmapped)
 | |
|     if 'update' in val:
 | |
|         set_value('direct_media_method', 'update', section, pjsip, nmapped)
 | |
|     if 'outgoing' in val:
 | |
|         set_value('directed_media_glare_mitigation', 'outgoing', section,
 | |
|                   pjsip, nmapped)
 | |
|     if 'nonat' in val:
 | |
|         set_value('disable_directed_media_on_nat', 'yes', section, pjsip,
 | |
|                   nmapped)
 | |
|     if 'no' in val:
 | |
|         set_value('direct_media', 'no', section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def from_sendrpid(key, val, section, pjsip, nmapped):
 | |
|     """Sets the send_rpid/pai values in pjsip.conf."""
 | |
|     if val == 'yes' or val == 'rpid':
 | |
|         set_value('send_rpid', 'yes', section, pjsip, nmapped)
 | |
|     elif val == 'pai':
 | |
|         set_value('send_pai', 'yes', section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def set_media_encryption(key, val, section, pjsip, nmapped):
 | |
|     """Sets the media_encryption value in pjsip.conf"""
 | |
|     try:
 | |
|         dtls = sip.get(section, 'dtlsenable')[0]
 | |
|         if dtls == 'yes':
 | |
|             # If DTLS is enabled, then that overrides SDES encryption.
 | |
|             return
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     if val == 'yes':
 | |
|         set_value('media_encryption', 'sdes', section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def from_recordfeature(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     If record on/off feature is set to automixmon then set
 | |
|     one_touch_recording, otherwise it can't be mapped.
 | |
|     """
 | |
|     set_value('one_touch_recording', 'yes', section, pjsip, nmapped)
 | |
|     set_value(key, val, section, pjsip, nmapped)
 | |
| 
 | |
| def set_record_on_feature(key, val, section, pjsip, nmapped):
 | |
|     """Sets the record_on_feature in pjsip.conf"""
 | |
|     from_recordfeature('record_on_feature', val, section, pjsip, nmapped)
 | |
| 
 | |
| def set_record_off_feature(key, val, section, pjsip, nmapped):
 | |
|     """Sets the record_off_feature in pjsip.conf"""
 | |
|     from_recordfeature('record_off_feature', val, section, pjsip, nmapped)
 | |
| 
 | |
| def from_progressinband(key, val, section, pjsip, nmapped):
 | |
|     """Sets the inband_progress value in pjsip.conf"""
 | |
|     # progressinband can = yes/no/never
 | |
|     if val == 'never':
 | |
|         val = 'no'
 | |
|     set_value('inband_progress', val, section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def build_host(config, host, section='general', port_key=None):
 | |
|     """
 | |
|     Returns a string composed of a host:port. This assumes that the host
 | |
|     may have a port as part of the initial value. The port_key overrides
 | |
|     a port in host, see parameter 'bindport' in chan_sip.
 | |
|     """
 | |
|     try:
 | |
|         socket.inet_pton(socket.AF_INET6, host)
 | |
|         if not host.startswith('['):
 | |
|             # SIP URI will need brackets.
 | |
|             host = '[' + host + ']'
 | |
|     except socket.error:
 | |
|         pass
 | |
| 
 | |
|     # Literal IPv6 (like [::]), IPv4, or hostname
 | |
|     # does not work for IPv6 without brackets; case catched above
 | |
|     url = urlparse('sip://' + host)
 | |
| 
 | |
|     if port_key:
 | |
|         try:
 | |
|             port = config.get(section, port_key)[0]
 | |
|             host = url.hostname # no port, but perhaps no brackets
 | |
|             try:
 | |
|                 socket.inet_pton(socket.AF_INET6, host)
 | |
|                 if not host.startswith('['):
 | |
|                     # SIP URI will need brackets.
 | |
|                     host = '[' + host + ']'
 | |
|             except socket.error:
 | |
|                 pass
 | |
|             return host + ':' + port
 | |
|         except LookupError:
 | |
|             pass
 | |
| 
 | |
|     # Returns host:port, in brackets if required
 | |
|     # TODO Does not compress IPv6, for example 0:0:0:0:0:0:0:0 should get [::]
 | |
|     return url.netloc
 | |
| 
 | |
| 
 | |
| def from_host(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     Sets contact info in an AOR section in pjsip.conf using 'host'
 | |
|     and 'port' data from sip.conf
 | |
|     """
 | |
|     # all aors have the same name as the endpoint so makes
 | |
|     # it easy to set endpoint's 'aors' value
 | |
|     set_value('aors', section, section, pjsip, nmapped)
 | |
|     if val == 'dynamic':
 | |
|         # Easy case. Just set the max_contacts on the aor and we're done
 | |
|         set_value('max_contacts', 1, section, pjsip, nmapped, 'aor')
 | |
|         return
 | |
| 
 | |
|     result = 'sip:'
 | |
| 
 | |
|     # More difficult case. The host will be either a hostname or
 | |
|     # IP address and may or may not have a port specified. pjsip.conf
 | |
|     # expects the contact to be a SIP URI.
 | |
| 
 | |
|     user = None
 | |
| 
 | |
|     try:
 | |
|         user = sip.multi_get(section, ['defaultuser', 'username'])[0]
 | |
|         result += user + '@'
 | |
|     except LookupError:
 | |
|         # It's fine if there's no user name
 | |
|         pass
 | |
| 
 | |
|     result += build_host(sip, val, section, 'port')
 | |
| 
 | |
|     set_value('contact', result, section, pjsip, nmapped, 'aor')
 | |
| 
 | |
| 
 | |
| def from_mailbox(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     Determines whether a mailbox configured in sip.conf should map to
 | |
|     an endpoint or aor in pjsip.conf. If subscribemwi is true, then the
 | |
|     mailboxes are set on an aor. Otherwise the mailboxes are set on the
 | |
|     endpoint.
 | |
|     """
 | |
| 
 | |
|     try:
 | |
|         subscribemwi = sip.get(section, 'subscribemwi')[0]
 | |
|     except LookupError:
 | |
|         # No subscribemwi option means default it to 'no'
 | |
|         subscribemwi = 'no'
 | |
| 
 | |
|     set_value('mailboxes', val, section, pjsip, nmapped, 'aor'
 | |
|               if subscribemwi == 'yes' else 'endpoint')
 | |
| 
 | |
| 
 | |
| def setup_auth(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     Sets up authentication information for a specific endpoint based on the
 | |
|     'secret' setting on a peer in sip.conf
 | |
|     """
 | |
|     set_value('username', section, section, pjsip, nmapped, 'auth')
 | |
|     # In chan_sip, if a secret and an md5secret are both specified on a peer,
 | |
|     # then in practice, only the md5secret is used. If both are encountered
 | |
|     # then we build an auth section that has both an md5_cred and password.
 | |
|     # However, the auth_type will indicate to authenticators to use the
 | |
|     # md5_cred, so like with sip.conf, the password will be there but have
 | |
|     # no purpose.
 | |
|     if key == 'secret':
 | |
|         set_value('password', val, section, pjsip, nmapped, 'auth')
 | |
|     else:
 | |
|         set_value('md5_cred', val, section, pjsip, nmapped, 'auth')
 | |
|         set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
 | |
| 
 | |
|     realms = [section]
 | |
|     try:
 | |
|         auths = sip.get('authentication', 'auth')
 | |
|         for i in auths:
 | |
|             user, at, realm = i.partition('@')
 | |
|             realms.append(realm)
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     realm_str = ','.join(realms)
 | |
| 
 | |
|     set_value('auth', section, section, pjsip, nmapped)
 | |
|     set_value('outbound_auth', realm_str, section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def setup_ident(key, val, section, pjsip, nmapped):
 | |
|     """
 | |
|     Examines the 'type' field for a sip.conf peer and creates an identify
 | |
|     section if the type is either 'peer' or 'friend'. The identify section uses
 | |
|     either the host or defaultip field of the sip.conf peer.
 | |
|     """
 | |
|     if val != 'peer' and val != 'friend':
 | |
|         return
 | |
| 
 | |
|     try:
 | |
|         ip = sip.get(section, 'host')[0]
 | |
|     except LookupError:
 | |
|         return
 | |
| 
 | |
|     if ip == 'dynamic':
 | |
|         try:
 | |
|             ip = sip.get(section, 'defaultip')[0]
 | |
|         except LookupError:
 | |
|             return
 | |
| 
 | |
|     set_value('endpoint', section, section, pjsip, nmapped, 'identify')
 | |
|     set_value('match', ip, section, pjsip, nmapped, 'identify')
 | |
| 
 | |
| 
 | |
| def from_encryption_taglen(key, val, section, pjsip, nmapped):
 | |
|     """Sets the srtp_tag32 option based on sip.conf encryption_taglen"""
 | |
|     if val == '32':
 | |
|         set_value('srtp_tag_32', 'yes', section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def from_dtlsenable(key, val, section, pjsip, nmapped):
 | |
|     """Optionally sets media_encryption=dtls based on sip.conf dtlsenable"""
 | |
|     if val == 'yes':
 | |
|         set_value('media_encryption', 'dtls', section, pjsip, nmapped)
 | |
| 
 | |
| ###############################################################################
 | |
| 
 | |
| # options in pjsip.conf on an endpoint that have no sip.conf equivalent:
 | |
| # type, 100rel, trust_id_outbound, aggregate_mwi, connected_line_method
 | |
| 
 | |
| # known sip.conf peer keys that can be mapped to a pjsip.conf section/key
 | |
| peer_map = [
 | |
|     # sip.conf option      mapping function     pjsip.conf option(s)
 | |
|     ###########################################################################
 | |
|     ['context',            set_value],
 | |
|     ['dtmfmode',           set_dtmfmode],
 | |
|     ['disallow',           merge_codec_value],
 | |
|     ['allow',              merge_codec_value],
 | |
|     ['nat',                from_nat],            # rtp_symmetric, force_rport,
 | |
|                                                  # rewrite_contact
 | |
|     ['rtptimeout',         set_value('rtp_timeout')],
 | |
|     ['icesupport',         set_value('ice_support')],
 | |
|     ['autoframing',        set_value('use_ptime')],
 | |
|     ['outboundproxy',      set_value('outbound_proxy')],
 | |
|     ['mohsuggest',         set_value('moh_suggest')],
 | |
|     ['session-timers',     set_timers],          # timers
 | |
|     ['session-minse',      set_value('timers_min_se')],
 | |
|     ['session-expires',    set_value('timers_sess_expires')],
 | |
|     # identify_by ?
 | |
|     ['canreinvite',        set_direct_media],    # direct_media alias
 | |
|     ['directmedia',        set_direct_media],    # direct_media
 | |
|                                                  # direct_media_method
 | |
|                                                  # directed_media_glare_mitigation
 | |
|                                                  # disable_directed_media_on_nat
 | |
|     ['callerid',           set_value],           # callerid
 | |
|     ['callingpres',        set_value('callerid_privacy')],
 | |
|     ['cid_tag',            set_value('callerid_tag')],
 | |
|     ['trustrpid',          set_value('trust_id_inbound')],
 | |
|     ['sendrpid',           from_sendrpid],       # send_pai, send_rpid
 | |
|     ['send_diversion',     set_value],
 | |
|     ['encryption',         set_media_encryption],
 | |
|     ['avpf',               set_value('use_avpf')],
 | |
|     ['recordonfeature',    set_record_on_feature],  # automixon
 | |
|     ['recordofffeature',   set_record_off_feature],  # automixon
 | |
|     ['progressinband',     from_progressinband], # in_band_progress
 | |
|     ['callgroup',          set_value('call_group')],
 | |
|     ['pickupgroup',        set_value('pickup_group')],
 | |
|     ['namedcallgroup',     set_value('named_call_group')],
 | |
|     ['namedpickupgroup',   set_value('named_pickup_group')],
 | |
|     ['allowtransfer',      set_value('allow_transfer')],
 | |
|     ['fromuser',           set_value('from_user')],
 | |
|     ['fromdomain',         set_value('from_domain')],
 | |
|     ['mwifrom',            set_value('mwi_from_user')],
 | |
|     ['tos_audio',          set_value],
 | |
|     ['tos_video',          set_value],
 | |
|     ['cos_audio',          set_value],
 | |
|     ['cos_video',          set_value],
 | |
|     ['sdpowner',           set_value('sdp_owner')],
 | |
|     ['sdpsession',         set_value('sdp_session')],
 | |
|     ['tonezone',           set_value('tone_zone')],
 | |
|     ['language',           set_value],
 | |
|     ['allowsubscribe',     set_value('allow_subscribe')],
 | |
|     ['subscribecontext',   set_value('subscribe_context')],
 | |
|     ['subminexpiry',       set_value('sub_min_expiry')],
 | |
|     ['rtp_engine',         set_value],
 | |
|     ['mailbox',            from_mailbox],
 | |
|     ['busylevel',          set_value('device_state_busy_at')],
 | |
|     ['secret',             setup_auth],
 | |
|     ['md5secret',          setup_auth],
 | |
|     ['type',               setup_ident],
 | |
|     ['dtlsenable',         from_dtlsenable],
 | |
|     ['dtlsverify',         set_value('dtls_verify')],
 | |
|     ['dtlsrekey',          set_value('dtls_rekey')],
 | |
|     ['dtlscertfile',       set_value('dtls_cert_file')],
 | |
|     ['dtlsprivatekey',     set_value('dtls_private_key')],
 | |
|     ['dtlscipher',         set_value('dtls_cipher')],
 | |
|     ['dtlscafile',         set_value('dtls_ca_file')],
 | |
|     ['dtlscapath',         set_value('dtls_ca_path')],
 | |
|     ['dtlssetup',          set_value('dtls_setup')],
 | |
|     ['encryption_taglen',  from_encryption_taglen],
 | |
|     ['setvar',             ignore],
 | |
| 
 | |
| ############################ maps to an aor ###################################
 | |
| 
 | |
|     ['host',               from_host],           # contact, max_contacts
 | |
|     ['qualifyfreq',        set_value('qualify_frequency', type='aor')],
 | |
|     ['maxexpiry',          set_value('maximum_expiration', type='aor')],
 | |
|     ['minexpiry',          set_value('minimum_expiration', type='aor')],
 | |
|     ['defaultexpiry',      set_value('default_expiration', type='aor')],
 | |
| 
 | |
| ############################# maps to auth#####################################
 | |
| #        type = auth
 | |
| #        username
 | |
| #        password
 | |
| #        md5_cred
 | |
| #        realm
 | |
| #        nonce_lifetime
 | |
| #        auth_type
 | |
| ######################### maps to acl/security ################################
 | |
| 
 | |
|     ['permit',             merge_value(type='acl', section_to='acl')],
 | |
|     ['deny',               merge_value(type='acl', section_to='acl')],
 | |
|     ['acl',                merge_value(type='acl', section_to='acl')],
 | |
|     ['contactpermit',      merge_value(type='acl', section_to='acl', key_to='contact_permit')],
 | |
|     ['contactdeny',        merge_value(type='acl', section_to='acl', key_to='contact_deny')],
 | |
|     ['contactacl',         merge_value(type='acl', section_to='acl', key_to='contact_acl')],
 | |
| 
 | |
| ########################### maps to transport #################################
 | |
| #        type = transport
 | |
| #        protocol
 | |
| #        bind
 | |
| #        async_operations
 | |
| #        ca_list_file
 | |
| #        ca_list_path
 | |
| #        cert_file
 | |
| #        privkey_file
 | |
| #        password
 | |
| #        external_signaling_address - externip & externhost
 | |
| #        external_signaling_port
 | |
| #        external_media_address
 | |
| #        domain
 | |
| #        verify_server
 | |
| #        verify_client
 | |
| #        require_client_cert
 | |
| #        method
 | |
| #        cipher
 | |
| #        localnet
 | |
| ######################### maps to domain_alias ################################
 | |
| #        type = domain_alias
 | |
| #        domain
 | |
| ######################### maps to registration ################################
 | |
| #        type = registration
 | |
| #        server_uri
 | |
| #        client_uri
 | |
| #        contact_user
 | |
| #        transport
 | |
| #        outbound_proxy
 | |
| #        expiration
 | |
| #        retry_interval
 | |
| #        max_retries
 | |
| #        auth_rejection_permanent
 | |
| #        outbound_auth
 | |
| ########################### maps to identify ##################################
 | |
| #        type = identify
 | |
| #        endpoint
 | |
| #        match
 | |
| ]
 | |
| 
 | |
| 
 | |
| def split_hostport(addr):
 | |
|     """
 | |
|     Given an address in the form 'host:port' separate the host and port
 | |
|     components.
 | |
|     Returns a two-tuple of strings, (host, port). If no port is present in the
 | |
|     string, then the port section of the tuple is None.
 | |
|     """
 | |
|     try:
 | |
|         socket.inet_pton(socket.AF_INET6, addr)
 | |
|         if not addr.startswith('['):
 | |
|             return (addr, None)
 | |
|     except socket.error:
 | |
|         pass
 | |
| 
 | |
|     # Literal IPv6 (like [::]), IPv4, or hostname
 | |
|     # does not work for IPv6 without brackets; case catched above
 | |
|     url = urlparse('sip://' + addr)
 | |
|     # TODO Does not compress IPv6, for example 0:0:0:0:0:0:0:0 should get [::]
 | |
|     return (url.hostname, url.port)
 | |
| 
 | |
| 
 | |
| def set_transport_common(section, sip, pjsip, protocol, nmapped):
 | |
|     """
 | |
|     sip.conf has several global settings that in pjsip.conf apply to individual
 | |
|     transports. This function adds these global settings to each individual
 | |
|     transport.
 | |
| 
 | |
|     The settings included are:
 | |
|     externaddr (or externip)
 | |
|     externhost
 | |
|     externtcpport for TCP
 | |
|     externtlsport for TLS
 | |
|     localnet
 | |
|     tos_sip
 | |
|     cos_sip
 | |
|     """
 | |
|     try:
 | |
|         extern_addr = sip.multi_get('general', ['externaddr', 'externip',
 | |
|                                                 'externhost'])[0]
 | |
|         host, port = split_hostport(extern_addr)
 | |
|         try:
 | |
|             port = sip.get('general', 'extern' + protocol + 'port')[0]
 | |
|         except LookupError:
 | |
|             pass
 | |
|         set_value('external_media_address', host, section, pjsip,
 | |
|                   nmapped, 'transport')
 | |
|         set_value('external_signaling_address', host, section, pjsip,
 | |
|                   nmapped, 'transport')
 | |
|         if port:
 | |
|             set_value('external_signaling_port', port, section, pjsip,
 | |
|                       nmapped, 'transport')
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         merge_value('localnet', sip.get('general', 'localnet')[0], 'general',
 | |
|                     pjsip, nmapped, 'transport', section, "local_net")
 | |
|     except LookupError:
 | |
|         # No localnet options configured. Move on.
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         set_value('tos', sip.get('general', 'tos_sip')[0], section, pjsip,
 | |
|                   nmapped, 'transport')
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         set_value('cos', sip.get('general', 'cos_sip')[0], section, pjsip,
 | |
|                   nmapped, 'transport')
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
| 
 | |
| def get_bind(sip, pjsip, protocol):
 | |
|     """
 | |
|     Given the protocol (udp, tcp, or tls), return
 | |
|     - the bind address, like [::] or 0.0.0.0
 | |
|     - name of the section to be created
 | |
|     """
 | |
|     section = 'transport-' + protocol
 | |
| 
 | |
|     # UDP cannot be disabled in chan_sip
 | |
|     if protocol != 'udp':
 | |
|         try:
 | |
|             enabled = sip.get('general', protocol + 'enable')[0]
 | |
|         except LookupError:
 | |
|             # No value means disabled by default. Don't create this transport
 | |
|             return (None, section)
 | |
|         if enabled != 'yes':
 | |
|             return (None, section)
 | |
| 
 | |
|     try:
 | |
|         bind = pjsip.get(section, 'bind')[0]
 | |
|         # The first run created an transport already but this
 | |
|         # server was not configured for IPv4/IPv6 Dual Stack
 | |
|         return (None, section)
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         bind = pjsip.get(section + '6', 'bind')[0]
 | |
|         # The first run created an IPv6 transport, because
 | |
|         # the server was configured with :: as bindaddr.
 | |
|         # Now, re-use its port and create the IPv4 transport
 | |
|         host, port = split_hostport(bind)
 | |
|         bind = '0.0.0.0'
 | |
|         if port:
 | |
|             bind += ':' + str(port)
 | |
|     except LookupError:
 | |
|         # This is the first run, no transport in pjsip exists.
 | |
|         try:
 | |
|             bind = sip.get('general', protocol + 'bindaddr')[0]
 | |
|         except LookupError:
 | |
|             if protocol == 'udp':
 | |
|                 try:
 | |
|                     bind = sip.get('general', 'bindaddr')[0]
 | |
|                 except LookupError:
 | |
|                     bind = '0.0.0.0'
 | |
|             else:
 | |
|                 try:
 | |
|                     bind = pjsip.get('transport-udp6', 'bind')[0]
 | |
|                 except LookupError:
 | |
|                     bind = pjsip.get('transport-udp', 'bind')[0]
 | |
|                 # Only TCP reuses host:port of UDP, others reuse just host
 | |
|                 if protocol == 'tls':
 | |
|                     bind, port = split_hostport(bind)
 | |
|         host, port = split_hostport(bind)
 | |
|         if host == '::':
 | |
|             section += '6'
 | |
| 
 | |
|     if protocol == 'udp':
 | |
|         host = build_host(sip, bind, 'general', 'bindport')
 | |
|     else:
 | |
|         host = build_host(sip, bind)
 | |
| 
 | |
|     return (host, section)
 | |
| 
 | |
| 
 | |
| def create_udp(sip, pjsip, nmapped):
 | |
|     """
 | |
|     Creates a 'transport-udp' section in the pjsip.conf file based
 | |
|     on the following settings from sip.conf:
 | |
| 
 | |
|     bindaddr (or udpbindaddr)
 | |
|     bindport
 | |
|     """
 | |
|     protocol = 'udp'
 | |
|     bind, section = get_bind(sip, pjsip, protocol)
 | |
| 
 | |
|     set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
 | |
|     set_value('bind', bind, section, pjsip, nmapped, 'transport')
 | |
|     set_transport_common(section, sip, pjsip, protocol, nmapped)
 | |
| 
 | |
| 
 | |
| def create_tcp(sip, pjsip, nmapped):
 | |
|     """
 | |
|     Creates a 'transport-tcp' section in the pjsip.conf file based
 | |
|     on the following settings from sip.conf:
 | |
| 
 | |
|     tcpenable
 | |
|     tcpbindaddr (or bindaddr)
 | |
|     """
 | |
|     protocol = 'tcp'
 | |
|     bind, section = get_bind(sip, pjsip, protocol)
 | |
|     if not bind:
 | |
|         return
 | |
| 
 | |
|     set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
 | |
|     set_value('bind', bind, section, pjsip, nmapped, 'transport')
 | |
|     set_transport_common(section, sip, pjsip, protocol, nmapped)
 | |
| 
 | |
| 
 | |
| def set_tls_cert_file(val, pjsip, section, nmapped):
 | |
|     """Sets cert_file based on sip.conf tlscertfile"""
 | |
|     set_value('cert_file', val, section, pjsip, nmapped,
 | |
|               'transport')
 | |
| 
 | |
| 
 | |
| def set_tls_private_key(val, pjsip, section, nmapped):
 | |
|     """Sets privkey_file based on sip.conf tlsprivatekey or sslprivatekey"""
 | |
|     set_value('priv_key_file', val, section, pjsip, nmapped,
 | |
|               'transport')
 | |
| 
 | |
| 
 | |
| def set_tls_cipher(val, pjsip, section, nmapped):
 | |
|     """Sets cipher based on sip.conf tlscipher or sslcipher"""
 | |
|     if val == 'ALL':
 | |
|         return
 | |
|     print('chan_sip ciphers do not match 1:1 with PJSIP ciphers.' \
 | |
|           ' You should manually review and adjust this.', file=sys.stderr)
 | |
| 
 | |
|     set_value('cipher', val, section, pjsip, nmapped, 'transport')
 | |
| 
 | |
| 
 | |
| def set_tls_cafile(val, pjsip, section, nmapped):
 | |
|     """Sets ca_list_file based on sip.conf tlscafile"""
 | |
|     set_value('ca_list_file', val, section, pjsip, nmapped,
 | |
|               'transport')
 | |
| 
 | |
| 
 | |
| def set_tls_capath(val, pjsip, section, nmapped):
 | |
|     """Sets ca_list_path based on sip.conf tlscapath"""
 | |
|     set_value('ca_list_path', val, section, pjsip, nmapped,
 | |
|               'transport')
 | |
| 
 | |
| 
 | |
| def set_tls_verifyclient(val, pjsip, section, nmapped):
 | |
|     """Sets verify_client based on sip.conf tlsverifyclient"""
 | |
|     set_value('verify_client', val, section, pjsip, nmapped,
 | |
|               'transport')
 | |
| 
 | |
| 
 | |
| def set_tls_verifyserver(val, pjsip, section, nmapped):
 | |
|     """Sets verify_server based on sip.conf tlsdontverifyserver"""
 | |
| 
 | |
|     if val == 'no':
 | |
|         set_value('verify_server', 'yes', section, pjsip, nmapped,
 | |
|                   'transport')
 | |
|     else:
 | |
|         set_value('verify_server', 'no', section, pjsip, nmapped,
 | |
|                   'transport')
 | |
| 
 | |
| 
 | |
| def create_tls(sip, pjsip, nmapped):
 | |
|     """
 | |
|     Creates a 'transport-tls' section in pjsip.conf based on the following
 | |
|     settings from sip.conf:
 | |
| 
 | |
|     tlsenable (or sslenable)
 | |
|     tlsbindaddr (or sslbindaddr or bindaddr)
 | |
|     tlsprivatekey (or sslprivatekey)
 | |
|     tlscipher (or sslcipher)
 | |
|     tlscafile
 | |
|     tlscapath (or tlscadir)
 | |
|     tlscertfile (or sslcert or tlscert)
 | |
|     tlsverifyclient
 | |
|     tlsdontverifyserver
 | |
|     tlsclientmethod (or sslclientmethod)
 | |
|     """
 | |
|     protocol = 'tls'
 | |
|     bind, section = get_bind(sip, pjsip, protocol)
 | |
|     if not bind:
 | |
|         return
 | |
| 
 | |
|     set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
 | |
|     set_value('bind', bind, section, pjsip, nmapped, 'transport')
 | |
|     set_transport_common(section, sip, pjsip, protocol, nmapped)
 | |
| 
 | |
|     tls_map = [
 | |
|         (['tlscertfile', 'sslcert', 'tlscert'], set_tls_cert_file),
 | |
|         (['tlsprivatekey', 'sslprivatekey'], set_tls_private_key),
 | |
|         (['tlscipher', 'sslcipher'], set_tls_cipher),
 | |
|         (['tlscafile'], set_tls_cafile),
 | |
|         (['tlscapath', 'tlscadir'], set_tls_capath),
 | |
|         (['tlsverifyclient'], set_tls_verifyclient),
 | |
|         (['tlsdontverifyserver'], set_tls_verifyserver)
 | |
|     ]
 | |
| 
 | |
|     for i in tls_map:
 | |
|         try:
 | |
|             i[1](sip.multi_get('general', i[0])[0], pjsip, section, nmapped)
 | |
|         except LookupError:
 | |
|             pass
 | |
| 
 | |
|     try:
 | |
|         method = sip.multi_get('general', ['tlsclientmethod',
 | |
|                                            'sslclientmethod'])[0]
 | |
|         if section != 'transport-' + protocol + '6':  # print only once
 | |
|             print('In chan_sip, you specified the TLS version. With chan_sip,' \
 | |
|                   ' this was just for outbound client connections. In' \
 | |
|                   ' chan_pjsip, this value is for client and server. Instead,' \
 | |
|                   ' consider not to specify \'tlsclientmethod\' for chan_sip' \
 | |
|                   ' and \'method = sslv23\' for chan_pjsip.', file=sys.stderr)
 | |
|     except LookupError:
 | |
|         """
 | |
|         OpenSSL emerged during the 90s. SSLv2 and SSLv3 were the only
 | |
|         existing methods at that time. The OpenSSL project continued. And as
 | |
|         of today (OpenSSL 1.0.2) this does not start SSLv2 and SSLv3 anymore
 | |
|         but TLSv1.0 and v1.2. Or stated differently: This method should
 | |
|         have been called 'method = secure' or 'method = automatic' back in
 | |
|         the 90s. The PJProject did not realize this and uses 'tlsv1' as
 | |
|         default when unspecified, which disables TLSv1.2. chan_sip used
 | |
|         'sslv23' as default when unspecified, which gives TLSv1.0 and v1.2.
 | |
|         """
 | |
|         method = 'sslv23'
 | |
|     set_value('method', method, section, pjsip, nmapped, 'transport')
 | |
| 
 | |
| 
 | |
| def map_transports(sip, pjsip, nmapped):
 | |
|     """
 | |
|     Finds options in sip.conf general section pertaining to
 | |
|     transport configuration and creates appropriate transport
 | |
|     configuration sections in pjsip.conf.
 | |
| 
 | |
|     sip.conf only allows a single UDP transport, TCP transport,
 | |
|     and TLS transport for each IP version. As such, the mapping
 | |
|     into PJSIP can be made consistent by defining six sections:
 | |
| 
 | |
|     transport-udp6
 | |
|     transport-udp
 | |
|     transport-tcp6
 | |
|     transport-tcp
 | |
|     transport-tls6
 | |
|     transport-tls
 | |
| 
 | |
|     To accommodate the default behaviors in sip.conf, we'll need to
 | |
|     create the UDP transports first, followed by the TCP and TLS transports.
 | |
|     """
 | |
| 
 | |
|     # First create a UDP transport. Even if no bind parameters were provided
 | |
|     # in sip.conf, chan_sip would always bind to UDP 0.0.0.0:5060
 | |
|     create_udp(sip, pjsip, nmapped)
 | |
|     create_udp(sip, pjsip, nmapped)
 | |
| 
 | |
|     # TCP settings may be dependent on UDP settings, so do it second.
 | |
|     create_tcp(sip, pjsip, nmapped)
 | |
|     create_tcp(sip, pjsip, nmapped)
 | |
|     create_tls(sip, pjsip, nmapped)
 | |
|     create_tls(sip, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def map_auth(sip, pjsip, nmapped):
 | |
|     """
 | |
|     Creates auth sections based on entries in the authentication section of
 | |
|     sip.conf. pjsip.conf section names consist of "auth_" followed by the name
 | |
|     of the realm.
 | |
|     """
 | |
|     try:
 | |
|         auths = sip.get('authentication', 'auth')
 | |
|     except LookupError:
 | |
|         return
 | |
| 
 | |
|     for i in auths:
 | |
|         creds, at, realm = i.partition('@')
 | |
|         if not at and not realm:
 | |
|             # Invalid. Move on
 | |
|             continue
 | |
|         user, colon, secret = creds.partition(':')
 | |
|         if not secret:
 | |
|             user, sharp, md5 = creds.partition('#')
 | |
|             if not md5:
 | |
|                 #Invalid. move on
 | |
|                 continue
 | |
|         section = "auth_" + realm
 | |
| 
 | |
|         set_value('realm', realm, section, pjsip, nmapped, 'auth')
 | |
|         set_value('username', user, section, pjsip, nmapped, 'auth')
 | |
|         if secret:
 | |
|             set_value('password', secret, section, pjsip, nmapped, 'auth')
 | |
|         else:
 | |
|             set_value('md5_cred', md5, section, pjsip, nmapped, 'auth')
 | |
|             set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
 | |
| 
 | |
| 
 | |
| class Registration:
 | |
|     """
 | |
|     Class for parsing and storing information in a register line in sip.conf.
 | |
|     """
 | |
|     def __init__(self, line, retry_interval, max_attempts, outbound_proxy):
 | |
|         self.retry_interval = retry_interval
 | |
|         self.max_attempts = max_attempts
 | |
|         self.outbound_proxy = outbound_proxy
 | |
|         self.parse(line)
 | |
| 
 | |
|     def parse(self, line):
 | |
|         """
 | |
|         Initial parsing routine for register lines in sip.conf.
 | |
| 
 | |
|         This splits the line into the part before the host, and the part
 | |
|         after the '@' symbol. These two parts are then passed to their
 | |
|         own parsing routines
 | |
|         """
 | |
| 
 | |
|         # register =>
 | |
|         # [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry]
 | |
| 
 | |
|         prehost, at, host_part = line.rpartition('@')
 | |
|         if not prehost:
 | |
|             raise
 | |
| 
 | |
|         self.parse_host_part(host_part)
 | |
|         self.parse_user_part(prehost)
 | |
| 
 | |
|     def parse_host_part(self, host_part):
 | |
|         """
 | |
|         Parsing routine for the part after the final '@' in a register line.
 | |
|         The strategy is to use partition calls to peel away the data starting
 | |
|         from the right and working to the left.
 | |
|         """
 | |
|         pre_expiry, sep, expiry = host_part.partition('~')
 | |
|         pre_extension, sep, self.extension = pre_expiry.partition('/')
 | |
|         self.host, sep, self.port = pre_extension.partition(':')
 | |
| 
 | |
|         self.expiry = expiry if expiry else '120'
 | |
| 
 | |
|     def parse_user_part(self, user_part):
 | |
|         """
 | |
|         Parsing routine for the part before the final '@' in a register line.
 | |
|         The only mandatory part of this line is the user portion. The strategy
 | |
|         here is to start by using partition calls to remove everything to
 | |
|         the right of the user, then finish by using rpartition calls to remove
 | |
|         everything to the left of the user.
 | |
|         """
 | |
|         self.peer = ''
 | |
|         self.protocol = 'udp'
 | |
|         protocols = ['udp', 'tcp', 'tls']
 | |
|         for protocol in protocols:
 | |
|             position = user_part.find(protocol + '://')
 | |
|             if -1 < position:
 | |
|                 post_transport = user_part[position + 6:]
 | |
|                 self.peer, sep, self.protocol = user_part[:position + 3].rpartition('?')
 | |
|                 user_part = post_transport
 | |
|                 break
 | |
| 
 | |
|         colons = user_part.count(':')
 | |
|         if (colons == 3):
 | |
|             # :domainport:secret:authuser
 | |
|             pre_auth, sep, port_auth = user_part.partition(':')
 | |
|             self.domainport, sep, auth = port_auth.partition(':')
 | |
|             self.secret, sep, self.authuser = auth.partition(':')
 | |
|         elif (colons == 2):
 | |
|             # :secret:authuser
 | |
|             pre_auth, sep, auth = user_part.partition(':')
 | |
|             self.secret, sep, self.authuser = auth.partition(':')
 | |
|         elif (colons == 1):
 | |
|             # :secret
 | |
|             pre_auth, sep, self.secret = user_part.partition(':')
 | |
|         elif (colons == 0):
 | |
|             # No port, secret, or authuser
 | |
|             pre_auth = user_part
 | |
|         else:
 | |
|             # Invalid setting
 | |
|             raise
 | |
| 
 | |
|         self.user, sep, self.domain = pre_auth.partition('@')
 | |
| 
 | |
|     def write(self, pjsip, nmapped):
 | |
|         """
 | |
|         Write parsed registration data into a section in pjsip.conf
 | |
| 
 | |
|         Most of the data in self will get written to a registration section.
 | |
|         However, there will also need to be an auth section created if a
 | |
|         secret or authuser is present.
 | |
| 
 | |
|         General mapping of values:
 | |
|         A combination of self.host and self.port is server_uri
 | |
|         A combination of self.user, self.domain, and self.domainport is
 | |
|           client_uri
 | |
|         self.expiry is expiration
 | |
|         self.extension is contact_user
 | |
|         self.protocol will map to one of the mapped transports
 | |
|         self.secret and self.authuser will result in a new auth section, and
 | |
|           outbound_auth will point to that section.
 | |
|         XXX self.peer really doesn't map to anything :(
 | |
|         """
 | |
| 
 | |
|         section = 'reg_' + self.host
 | |
| 
 | |
|         set_value('retry_interval', self.retry_interval, section, pjsip,
 | |
|                   nmapped, 'registration')
 | |
|         set_value('max_retries', self.max_attempts, section, pjsip, nmapped,
 | |
|                   'registration')
 | |
|         if self.extension:
 | |
|             set_value('contact_user', self.extension, section, pjsip, nmapped,
 | |
|                       'registration')
 | |
| 
 | |
|         set_value('expiration', self.expiry, section, pjsip, nmapped,
 | |
|                   'registration')
 | |
| 
 | |
|         if self.protocol == 'udp':
 | |
|             set_value('transport', 'transport-udp', section, pjsip, nmapped,
 | |
|                       'registration')
 | |
|         elif self.protocol == 'tcp':
 | |
|             set_value('transport', 'transport-tcp', section, pjsip, nmapped,
 | |
|                       'registration')
 | |
|         elif self.protocol == 'tls':
 | |
|             set_value('transport', 'transport-tls', section, pjsip, nmapped,
 | |
|                       'registration')
 | |
| 
 | |
|         auth_section = 'auth_reg_' + self.host
 | |
| 
 | |
|         if hasattr(self, 'secret') and self.secret:
 | |
|             set_value('password', self.secret, auth_section, pjsip, nmapped,
 | |
|                       'auth')
 | |
|             set_value('username', self.authuser if hasattr(self, 'authuser')
 | |
|                       else self.user, auth_section, pjsip, nmapped, 'auth')
 | |
|             set_value('outbound_auth', auth_section, section, pjsip, nmapped,
 | |
|                       'registration')
 | |
| 
 | |
|         client_uri = "sip:%s@" % self.user
 | |
|         if self.domain:
 | |
|             client_uri += self.domain
 | |
|         else:
 | |
|             client_uri += self.host
 | |
| 
 | |
|         if hasattr(self, 'domainport') and self.domainport:
 | |
|             client_uri += ":" + self.domainport
 | |
|         elif self.port:
 | |
|             client_uri += ":" + self.port
 | |
| 
 | |
|         set_value('client_uri', client_uri, section, pjsip, nmapped,
 | |
|                   'registration')
 | |
| 
 | |
|         server_uri = "sip:%s" % self.host
 | |
|         if self.port:
 | |
|             server_uri += ":" + self.port
 | |
| 
 | |
|         set_value('server_uri', server_uri, section, pjsip, nmapped,
 | |
|                   'registration')
 | |
| 
 | |
|         if self.outbound_proxy:
 | |
|             set_value('outboundproxy', self.outbound_proxy, section, pjsip,
 | |
|                       nmapped, 'registration')
 | |
| 
 | |
| 
 | |
| def map_registrations(sip, pjsip, nmapped):
 | |
|     """
 | |
|     Gathers all necessary outbound registration data in sip.conf and creates
 | |
|     corresponding registration sections in pjsip.conf
 | |
|     """
 | |
|     try:
 | |
|         regs = sip.get('general', 'register')
 | |
|     except LookupError:
 | |
|         return
 | |
| 
 | |
|     try:
 | |
|         retry_interval = sip.get('general', 'registertimeout')[0]
 | |
|     except LookupError:
 | |
|         retry_interval = '20'
 | |
| 
 | |
|     try:
 | |
|         max_attempts = sip.get('general', 'registerattempts')[0]
 | |
|     except LookupError:
 | |
|         max_attempts = '10'
 | |
| 
 | |
|     try:
 | |
|         outbound_proxy = sip.get('general', 'outboundproxy')[0]
 | |
|     except LookupError:
 | |
|         outbound_proxy = ''
 | |
| 
 | |
|     for i in regs:
 | |
|         reg = Registration(i, retry_interval, max_attempts, outbound_proxy)
 | |
|         reg.write(pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def map_setvars(sip, section, pjsip, nmapped):
 | |
|     """
 | |
|     Map all setvar in peer section to the appropriate endpoint set_var
 | |
|     """
 | |
|     try:
 | |
|         setvars = sip.section(section)[0].get('setvar')
 | |
|     except LookupError:
 | |
|         return
 | |
| 
 | |
|     for setvar in setvars:
 | |
|         set_value('set_var', setvar, section, pjsip, nmapped)
 | |
| 
 | |
| 
 | |
| def map_peer(sip, section, pjsip, nmapped):
 | |
|     """
 | |
|     Map the options from a peer section in sip.conf into the appropriate
 | |
|     sections in pjsip.conf
 | |
|     """
 | |
|     for i in peer_map:
 | |
|         try:
 | |
|             # coming from sip.conf the values should mostly be a list with a
 | |
|             # single value.  In the few cases that they are not a specialized
 | |
|             # function (see merge_value) is used to retrieve the values.
 | |
|             i[1](i[0], sip.get(section, i[0])[0], section, pjsip, nmapped)
 | |
|         except LookupError:
 | |
|             pass  # key not found in sip.conf
 | |
| 
 | |
|     setup_udptl(section, pjsip, nmapped)
 | |
| 
 | |
| def find_non_mapped(sections, nmapped):
 | |
|     """
 | |
|     Determine sip.conf options that were not properly mapped to pjsip.conf
 | |
|     options.
 | |
|     """
 | |
|     for section, sect in sections.iteritems():
 | |
|         try:
 | |
|             # since we are pulling from sip.conf this should always
 | |
|             # be a single value list
 | |
|             sect = sect[0]
 | |
|             # loop through the section and store any values that were not
 | |
|             # mapped
 | |
|             for key in sect.keys(True):
 | |
|                 for i in peer_map:
 | |
|                     if i[0] == key:
 | |
|                         break
 | |
|                 else:
 | |
|                     nmapped(section, key, sect[key])
 | |
|         except LookupError:
 | |
|             pass
 | |
| 
 | |
| 
 | |
| def map_system(sip, pjsip, nmapped):
 | |
|     section = 'system' # Just a label; you as user can change that
 | |
|     type = 'system' # Not a label, therefore not the same as section
 | |
| 
 | |
|     try:
 | |
|         user_agent = sip.get('general', 'useragent')[0]
 | |
|         set_value('user_agent', user_agent, 'global', pjsip, nmapped, 'global')
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
| 
 | |
|     try:
 | |
|         sipdebug = sip.get('general', 'sipdebug')[0]
 | |
|         set_value('debug', sipdebug, 'global', pjsip, nmapped, 'global')
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         useroption_parsing = sip.get('general', 'legacy_useroption_parsing')[0]
 | |
|         set_value('ignore_uri_user_options', useroption_parsing, 'global', pjsip, nmapped, 'global')
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         timer_t1 = sip.get('general', 'timert1')[0]
 | |
|         set_value('timer_t1', timer_t1, section, pjsip, nmapped, type)
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         timer_b = sip.get('general', 'timerb')[0]
 | |
|         set_value('timer_b', timer_b, section, pjsip, nmapped, type)
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
|     try:
 | |
|         compact_headers = sip.get('general', 'compactheaders')[0]
 | |
|         set_value('compact_headers', compact_headers, section, pjsip, nmapped, type)
 | |
|     except LookupError:
 | |
|         pass
 | |
| 
 | |
| 
 | |
| def convert(sip, filename, non_mappings, include):
 | |
|     """
 | |
|     Entry point for configuration file conversion. This
 | |
|     function will create a pjsip.conf object and begin to
 | |
|     map specific sections from sip.conf into it.
 | |
|     Returns the new pjsip.conf object once completed
 | |
|     """
 | |
|     pjsip = sip.__class__()
 | |
|     non_mappings[filename] = astdicts.MultiOrderedDict()
 | |
|     nmapped = non_mapped(non_mappings[filename])
 | |
|     if not include:
 | |
|         # Don't duplicate transport and registration configs
 | |
|         map_system(sip, pjsip, nmapped)
 | |
|         map_transports(sip, pjsip, nmapped)
 | |
|         map_registrations(sip, pjsip, nmapped)
 | |
|     map_auth(sip, pjsip, nmapped)
 | |
|     for section in sip.sections():
 | |
|         if section == 'authentication':
 | |
|             pass
 | |
|         else:
 | |
|             map_peer(sip, section, pjsip, nmapped)
 | |
|             map_setvars(sip, section, pjsip, nmapped)
 | |
| 
 | |
|     find_non_mapped(sip.defaults(), nmapped)
 | |
|     find_non_mapped(sip.sections(), nmapped)
 | |
| 
 | |
|     for key, val in sip.includes().iteritems():
 | |
|         pjsip.add_include(PREFIX + key, convert(val, PREFIX + key,
 | |
|                           non_mappings, True)[0])
 | |
|     return pjsip, non_mappings
 | |
| 
 | |
| 
 | |
| def write_pjsip(filename, pjsip, non_mappings):
 | |
|     """
 | |
|     Write pjsip.conf file to disk
 | |
|     """
 | |
|     try:
 | |
|         with open(filename, 'wt') as fp:
 | |
|             fp.write(';--\n')
 | |
|             fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
 | |
|             fp.write('Non mapped elements start\n')
 | |
|             fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n')
 | |
|             astconfigparser.write_dicts(fp, non_mappings[filename])
 | |
|             fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
 | |
|             fp.write('Non mapped elements end\n')
 | |
|             fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
 | |
|             fp.write('--;\n\n')
 | |
|             # write out include file(s)
 | |
|             pjsip.write(fp)
 | |
| 
 | |
|     except IOError:
 | |
|         print("Could not open file " + filename + " for writing", file=sys.stderr)
 | |
| 
 | |
| ###############################################################################
 | |
| 
 | |
| 
 | |
| def cli_options():
 | |
|     """
 | |
|     Parse command line options and apply them. If invalid input is given,
 | |
|     print usage information
 | |
|     """
 | |
|     global PREFIX
 | |
|     global QUIET
 | |
|     usage = "usage: %prog [options] [input-file [output-file]]\n\n" \
 | |
|         "Converts the chan_sip configuration input-file to the chan_pjsip output-file.\n" \
 | |
|         "The input-file defaults to 'sip.conf'.\n" \
 | |
|         "The output-file defaults to 'pjsip.conf'."
 | |
|     parser = optparse.OptionParser(usage=usage)
 | |
|     parser.add_option('-p', '--prefix', dest='prefix', default=PREFIX,
 | |
|                       help='output prefix for include files')
 | |
|     parser.add_option('-q', '--quiet', dest='quiet', default=False, action='store_true',
 | |
|                       help="don't print messages to stdout")
 | |
| 
 | |
|     options, args = parser.parse_args()
 | |
|     PREFIX = options.prefix
 | |
|     if options.quiet:
 | |
|         QUIET = True
 | |
| 
 | |
|     sip_filename = args[0] if len(args) else 'sip.conf'
 | |
|     pjsip_filename = args[1] if len(args) == 2 else 'pjsip.conf'
 | |
| 
 | |
|     return sip_filename, pjsip_filename
 | |
| 
 | |
| 
 | |
| def info(msg):
 | |
|     if QUIET:
 | |
|         return
 | |
|     print(msg)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     sip_filename, pjsip_filename = cli_options()
 | |
|     # configuration parser for sip.conf
 | |
|     sip = astconfigparser.MultiOrderedConfigParser()
 | |
|     info('Please, report any issue at:')
 | |
|     info('    https://github.com/asterisk/asterisk/issues/')
 | |
|     info('Reading ' + sip_filename)
 | |
|     sip.read(sip_filename)
 | |
|     info('Converting to PJSIP...')
 | |
|     pjsip, non_mappings = convert(sip, pjsip_filename, dict(), False)
 | |
|     info('Writing ' + pjsip_filename)
 | |
|     write_pjsip(pjsip_filename, pjsip, non_mappings)
 |