summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2013-10-31 22:09:47 +0000
committerMark Michelson <mmichelson@digium.com>2013-10-31 22:09:47 +0000
commitdd221c74c53113b0109f1ca1b392d43dd768cef5 (patch)
tree2f59a5e44d9f828c52c21d09183bb23bb60a56b9 /contrib
parente9fc32105353a65b89f546008ca98ffadf359704 (diff)
Update the conversion script from sip.conf to pjsip.conf
(closes issue ASTERISK-22374) Reported by Matt Jordan Review: https://reviewboard.asterisk.org/r/2846 ........ Merged revisions 402327 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@402328 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'contrib')
-rw-r--r--contrib/scripts/sip_to_pjsip/astconfigparser.py (renamed from contrib/scripts/sip_to_res_sip/astconfigparser.py)265
-rw-r--r--contrib/scripts/sip_to_pjsip/astdicts.py (renamed from contrib/scripts/sip_to_res_sip/astdicts.py)0
-rwxr-xr-xcontrib/scripts/sip_to_pjsip/sip_to_pjsip.py1151
-rwxr-xr-xcontrib/scripts/sip_to_res_sip/sip_to_res_sip.py392
4 files changed, 1320 insertions, 488 deletions
diff --git a/contrib/scripts/sip_to_res_sip/astconfigparser.py b/contrib/scripts/sip_to_pjsip/astconfigparser.py
index 4a324e2ec..c93173dee 100644
--- a/contrib/scripts/sip_to_res_sip/astconfigparser.py
+++ b/contrib/scripts/sip_to_pjsip/astconfigparser.py
@@ -3,11 +3,12 @@ import re
from astdicts import OrderedDict
from astdicts import MultiOrderedDict
+
def merge_values(left, right, key):
"""Merges values from right into left."""
if isinstance(left, list):
vals0 = left
- else: # assume dictionary
+ else: # assume dictionary
vals0 = left[key] if key in left else []
vals1 = right[key] if key in right else []
@@ -15,14 +16,16 @@ def merge_values(left, right, key):
###############################################################################
+
class Section(MultiOrderedDict):
- """A Section is a MultiOrderedDict itself that maintains a list of
- key/value options. However, in the case of an Asterisk config
- file a section may have other defaults sections that is can pull
- data from (i.e. templates). So when an option is looked up by key
- it first checks the base section and if not found looks in the
- added default sections. If not found at that point then a 'KeyError'
- exception is raised.
+ """
+ A Section is a MultiOrderedDict itself that maintains a list of
+ key/value options. However, in the case of an Asterisk config
+ file a section may have other defaults sections that is can pull
+ data from (i.e. templates). So when an option is looked up by key
+ it first checks the base section and if not found looks in the
+ added default sections. If not found at that point then a 'KeyError'
+ exception is raised.
"""
count = 0
@@ -35,9 +38,24 @@ class Section(MultiOrderedDict):
self._templates = [] if templates is None else templates
def __cmp__(self, other):
+ """
+ Use self.id as means of determining equality
+ """
return cmp(self.id, other.id)
- def get(self, key, from_self=True, from_templates=True, from_defaults=True):
+ def get(self, key, from_self=True, from_templates=True,
+ from_defaults=True):
+ """
+ Get the values corresponding to a given key. The parameters to this
+ function form a hierarchy that determines priority of the search.
+ from_self takes priority over from_templates, and from_templates takes
+ priority over from_defaults.
+
+ Parameters:
+ from_self - If True, search within the given section.
+ from_templates - If True, search in this section's templates.
+ from_defaults - If True, search within this section's defaults.
+ """
if from_self and key in self:
return MultiOrderedDict.__getitem__(self, key)
@@ -62,13 +80,19 @@ class Section(MultiOrderedDict):
raise KeyError(key)
def __getitem__(self, key):
- """Get the value for the given key. If it is not found in the 'self'
- then check inside templates and defaults before declaring raising
- a KeyError exception.
+ """
+ Get the value for the given key. If it is not found in the 'self'
+ then check inside templates and defaults before declaring raising
+ a KeyError exception.
"""
return self.get(key)
def keys(self, self_only=False):
+ """
+ Get the keys from this section. If self_only is True, then
+ keys from this section's defaults and templates are not
+ included in the returned value
+ """
res = MultiOrderedDict.keys(self)
if self_only:
return res
@@ -85,13 +109,21 @@ class Section(MultiOrderedDict):
return res
def add_defaults(self, defaults):
+ """
+ Add a list of defaults to the section. Defaults are
+ sections such as 'general'
+ """
defaults.sort()
for i in defaults:
self._defaults.insert(0, i)
def add_templates(self, templates):
- templates.sort(reverse=True);
- self._templates.extend(templates)
+ """
+ Add a list of templates to the section.
+ """
+ templates.sort()
+ for i in templates:
+ self._templates.insert(0, i)
def get_merged(self, key):
"""Return a list of values for a given key merged from default(s)"""
@@ -120,9 +152,11 @@ COMMENT_END = '--;'
DEFAULTSECT = 'general'
+
def remove_comment(line, is_comment):
"""Remove any commented elements from the line."""
- if not line: return line, is_comment
+ if not line:
+ return line, is_comment
if is_comment:
part = line.partition(COMMENT_END)
@@ -152,23 +186,21 @@ def remove_comment(line, is_comment):
# check for eol comment
return line.partition(COMMENT)[0].strip(), False
+
def try_include(line):
- """Checks to see if the given line is an include. If so return the
- included filename, otherwise None.
"""
- if not line.startswith('#'):
- return None
+ Checks to see if the given line is an include. If so return the
+ included filename, otherwise None.
+ """
+
+ match = re.match('^#include\s*[<"]?(.*)[>"]?$', line)
+ return match.group(1) if match else None
- # it is an include - get file name
- try:
- return line[line.index('"') + 1:line.rindex('"')]
- except ValueError:
- print "Invalid include - could not parse filename."
- return None
def try_section(line):
- """Checks to see if the given line is a section. If so return the section
- name, otherwise return 'None'.
+ """
+ Checks to see if the given line is a section. If so return the section
+ name, otherwise return 'None'.
"""
# leading spaces were stripped when checking for comments
if not line.startswith('['):
@@ -188,6 +220,7 @@ def try_section(line):
except:
return section[1:], False, templates
+
def try_option(line):
"""Parses the line as an option, returning the key/value pair."""
data = re.split('=>?', line)
@@ -196,30 +229,12 @@ def try_option(line):
###############################################################################
-def find_value(sections, key):
- """Given a list of sections, try to find value(s) for the given key."""
- # always start looking in the last one added
- sections.sort(reverse=True);
- for s in sections:
- try:
- # try to find in section and section's templates
- return s.get(key, from_defaults=False)
- except KeyError:
- pass
-
- # wasn't found in sections or a section's templates so check in defaults
- for s in sections:
- try:
- # try to find in section's defaultsects
- return s.get(key, from_self=False, from_templates=False)
- except KeyError:
- pass
-
- raise KeyError(key)
def find_dict(mdicts, key, val):
- """Given a list of mult-dicts, return the multi-dict that contains
- the given key/value pair."""
+ """
+ Given a list of mult-dicts, return the multi-dict that contains
+ the given key/value pair.
+ """
def found(d):
return key in d and val in d[key]
@@ -230,44 +245,25 @@ def find_dict(mdicts, key, val):
raise LookupError("Dictionary not located for key = %s, value = %s"
% (key, val))
-def get_sections(parser, key, attr='_sections', searched=None):
- if searched is None:
- searched = []
- if parser is None or parser in searched:
- return []
- try:
- sections = getattr(parser, attr)
- res = sections[key] if key in sections else []
- searched.append(parser)
- return res + get_sections(parser._includes, key, attr, searched) \
- + get_sections(parser._parent, key, attr, searched)
- except:
- # assume ordereddict of parsers
- res = []
- for p in parser.itervalues():
- res.extend(get_sections(p, key, attr, searched))
- return res
-
-def get_defaults(parser, key):
- return get_sections(parser, key, '_defaults')
-
-def write_dicts(file, mdicts):
+def write_dicts(config_file, mdicts):
+ """Write the contents of the mdicts to the specified config file"""
for section, sect_list in mdicts.iteritems():
# every section contains a list of dictionaries
for sect in sect_list:
- file.write("[%s]\n" % section)
+ config_file.write("[%s]\n" % section)
for key, val_list in sect.iteritems():
# every value is also a list
for v in val_list:
key_val = key
if v is not None:
key_val += " = " + str(v)
- file.write("%s\n" % (key_val))
- file.write("\n")
+ config_file.write("%s\n" % (key_val))
+ config_file.write("\n")
###############################################################################
+
class MultiOrderedConfigParser:
def __init__(self, parent=None):
self._parent = parent
@@ -275,16 +271,39 @@ class MultiOrderedConfigParser:
self._sections = MultiOrderedDict()
self._includes = OrderedDict()
+ def find_value(self, sections, key):
+ """Given a list of sections, try to find value(s) for the given key."""
+ # always start looking in the last one added
+ sections.sort(reverse=True)
+ for s in sections:
+ try:
+ # try to find in section and section's templates
+ return s.get(key, from_defaults=False)
+ except KeyError:
+ pass
+
+ # wasn't found in sections or a section's templates so check in
+ # defaults
+ for s in sections:
+ try:
+ # try to find in section's defaultsects
+ return s.get(key, from_self=False, from_templates=False)
+ except KeyError:
+ pass
+
+ raise KeyError(key)
+
def defaults(self):
return self._defaults
def default(self, key):
"""Retrieves a list of dictionaries for a default section."""
- return get_defaults(self, key)
+ return self.get_defaults(key)
def add_default(self, key, template_keys=None):
- """Adds a default section to defaults, returning the
- default Section object.
+ """
+ Adds a default section to defaults, returning the
+ default Section object.
"""
if template_keys is None:
template_keys = []
@@ -295,17 +314,47 @@ class MultiOrderedConfigParser:
def section(self, key):
"""Retrieves a list of dictionaries for a section."""
- return get_sections(self, key)
+ return self.get_sections(key)
+
+ def get_sections(self, key, attr='_sections', searched=None):
+ """
+ Retrieve a list of sections that have values for the given key.
+ The attr parameter can be used to control what part of the parser
+ to retrieve values from.
+ """
+ if searched is None:
+ searched = []
+ if self in searched:
+ return []
+
+ sections = getattr(self, attr)
+ res = sections[key] if key in sections else []
+ searched.append(self)
+ if self._includes:
+ res += self._includes.get_sections(key, attr, searched)
+ if self._parent:
+ res += self._parent.get_sections(key, attr, searched)
+ return res
+
+ def get_defaults(self, key):
+ """
+ Retrieve a list of defaults that have values for the given key.
+ """
+ return self.get_sections(key, '_defaults')
def add_section(self, key, template_keys=None, mdicts=None):
+ """
+ Create a new section in the configuration. The name of the
+ new section is the 'key' parameter.
+ """
if template_keys is None:
template_keys = []
if mdicts is None:
mdicts = self._sections
res = Section()
for t in template_keys:
- res.add_templates(get_defaults(self, t))
- res.add_defaults(get_defaults(self, DEFAULTSECT))
+ res.add_templates(self.get_defaults(t))
+ res.add_defaults(self.get_defaults(DEFAULTSECT))
mdicts.insert(0, key, res)
return res
@@ -313,29 +362,50 @@ class MultiOrderedConfigParser:
return self._includes
def add_include(self, filename, parser=None):
+ """
+ Add a new #include file to the configuration.
+ """
if filename in self._includes:
return self._includes[filename]
self._includes[filename] = res = \
- MultiOrderedConfigParser(self) if parser is None else parser
- return res;
+ MultiOrderedConfigParser(self) if parser is None else parser
+ return res
def get(self, section, key):
"""Retrieves the list of values from a section for a key."""
try:
# search for the value in the list of sections
- return find_value(self.section(section), key)
+ return self.find_value(self.section(section), key)
except KeyError:
pass
try:
# section may be a default section so, search
# for the value in the list of defaults
- return find_value(self.default(section), key)
+ return self.find_value(self.default(section), key)
except KeyError:
raise LookupError("key %r not found for section %r"
% (key, section))
+ def multi_get(self, section, key_list):
+ """
+ Retrieves the list of values from a section for a list of keys.
+ This method is intended to be used for equivalent keys. Thus, as soon
+ as any match is found for any key in the key_list, the match is
+ returned. This does not concatenate the lookups of all of the keys
+ together.
+ """
+ for i in key_list:
+ try:
+ return self.get(section, i)
+ except LookupError:
+ pass
+
+ # Making it here means all lookups failed.
+ raise LookupError("keys %r not found for section %r" %
+ (key_list, section))
+
def set(self, section, key, val):
"""Sets an option in the given section."""
# TODO - set in multiple sections? (for now set in first)
@@ -346,15 +416,17 @@ class MultiOrderedConfigParser:
self.defaults(section)[0][key] = val
def read(self, filename):
+ """Parse configuration information from a file"""
try:
- with open(filename, 'rt') as file:
- self._read(file, filename)
+ with open(filename, 'rt') as config_file:
+ self._read(config_file)
except IOError:
print "Could not open file ", filename, " for reading"
- def _read(self, file, filename):
- is_comment = False # used for multi-lined comments
- for line in file:
+ def _read(self, config_file):
+ """Parse configuration information from the config_file"""
+ is_comment = False # used for multi-lined comments
+ for line in config_file:
line, is_comment = remove_comment(line, is_comment)
if not line:
# line was empty or was a comment
@@ -377,18 +449,19 @@ class MultiOrderedConfigParser:
key, val = try_option(line)
sect[key] = val
- def write(self, f):
+ def write(self, config_file):
+ """Write configuration information out to a file"""
try:
for key, val in self._includes.iteritems():
val.write(key)
- f.write('#include "%s"\n' % key)
+ config_file.write('#include "%s"\n' % key)
- f.write('\n')
- write_dicts(f, self._defaults)
- write_dicts(f, self._sections)
+ config_file.write('\n')
+ write_dicts(config_file, self._defaults)
+ write_dicts(config_file, self._sections)
except:
try:
- with open(f, 'wt') as fp:
+ with open(config_file, 'wt') as fp:
self.write(fp)
except IOError:
- print "Could not open file ", f, " for writing"
+ print "Could not open file ", config_file, " for writing"
diff --git a/contrib/scripts/sip_to_res_sip/astdicts.py b/contrib/scripts/sip_to_pjsip/astdicts.py
index ae630755d..ae630755d 100644
--- a/contrib/scripts/sip_to_res_sip/astdicts.py
+++ b/contrib/scripts/sip_to_pjsip/astdicts.py
diff --git a/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py b/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py
new file mode 100755
index 000000000..96a9a02ad
--- /dev/null
+++ b/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py
@@ -0,0 +1,1151 @@
+#!/usr/bin/python
+
+import optparse
+import astdicts
+import astconfigparser
+import socket
+import re
+
+PREFIX = 'pjsip_'
+
+###############################################################################
+### 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 mult-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 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):
+ """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)
+
+ # 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, i, section_to if section_to else section,
+ pjsip, nmapped, type)
+
+
+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.
+ """
+ # available pjsip.conf values: rfc4733, inband, info, none
+ if val == 'inband' or val == 'info':
+ 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 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
+ if val == 'originate':
+ set_value('timers', 'always', section, pjsip, nmapped)
+ elif val == 'accept':
+ set_value('timers', 'required', section, pjsip, nmapped)
+ elif val == 'never':
+ 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 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, port_key):
+ """
+ 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 is only used
+ if the host does not already have a port set on it.
+ Throws a LookupError if the key does not exist
+ """
+ port = None
+
+ try:
+ socket.inet_pton(socket.AF_INET6, host)
+ if not host.startswith('['):
+ # SIP URI will need brackets.
+ host = '[' + host + ']'
+ else:
+ # If brackets are present, there may be a port as well
+ port = re.match('\[.*\]:(\d+)', host)
+ except socket.error:
+ # No biggie. It's just not an IPv6 address
+ port = re.match('.*:(\d+)', host)
+
+ result = host
+
+ if not port:
+ try:
+ port = config.get(section, port_key)[0]
+ result += ':' + port
+ except LookupError:
+ pass
+
+ return result
+
+
+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, rtp_ipv6, 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_value],
+ ['allow', merge_value],
+ ['nat', from_nat], # rtp_symmetric, force_rport,
+ # rewrite_contact
+ ['icesupport', set_value('ice_support')],
+ ['autoframing', set_value('use_ptime')],
+ ['outboundproxy', set_value('outbound_proxy')],
+ ['mohsuggest', set_value],
+ ['session-timers', set_timers], # timers
+ ['session-minse', set_value('timers_min_se')],
+ ['session-expires', set_value('timers_sess_expires')],
+ ['externip', set_value('external_media_address')],
+ ['externhost', set_value('external_media_address')],
+ # identify_by ?
+ ['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')],
+ ['trustpid', set_value('trust_id_inbound')],
+ ['sendrpid', from_sendrpid], # send_pai, send_rpid
+ ['send_diversion', set_value],
+ ['encrpytion', set_media_encryption],
+ ['avpf', set_value('use_avpf')],
+ ['recordonfeature', from_recordfeature], # automixon
+ ['recordofffeature', from_recordfeature], # automixon
+ ['progressinband', from_progressinband], # in_band_progress
+ ['callgroup', set_value],
+ ['pickupgroup', set_value],
+ ['namedcallgroup', set_value],
+ ['namedpickupgroup', set_value],
+ ['allowtransfer', set_value],
+ ['fromuser', set_value],
+ ['fromdomain', set_value],
+ ['mwifrom', set_value('mwifromuser')],
+ ['tos_audio', set_value],
+ ['tos_video', set_value],
+ ['cos_audio', set_value],
+ ['cos_video', set_value],
+ ['sdpowner', set_value],
+ ['sdpsession', set_value],
+ ['tonezone', set_value],
+ ['language', set_value],
+ ['allowsubscribe', set_value],
+ ['subminexpiry', set_value],
+ ['rtp_engine', set_value('rtpengine')],
+ ['mailbox', from_mailbox],
+ ['busylevel', set_value('devicestate_busy_at')],
+ ['secret', setup_auth],
+ ['md5secret', setup_auth],
+ ['type', setup_ident],
+ ['dtlsenable', from_dtlsenable],
+ ['dtlsverify', set_value],
+ ['dtlsrekey', set_value],
+ ['dtlscertfile', set_value],
+ ['dtlsprivatekey', set_value],
+ ['dtlscipher', set_value],
+ ['dtlscafile', set_value],
+ ['dtlscapath', set_value],
+ ['dtlssetup', set_value],
+ ['encryption_taglen', from_encryption_taglen],
+
+############################ maps to an aor ###################################
+
+ ['host', from_host], # contact, max_contacts
+ ['qualifyfreq', set_value('qualify_frequency', 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')],
+ ['contactdeny', merge_value(type='acl', section_to='acl')],
+ ['contactacl', merge_value(type='acl', section_to='acl')],
+
+########################### maps to transport #################################
+# type = transport
+# protocol
+# bind
+# async_operations
+# ca_list_file
+# 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 add_localnet(section, pjsip, nmapped):
+ """
+ Adds localnet values from sip.conf's general section to a transport in
+ pjsip.conf. Ideally, we would have just created a template with the
+ localnet sections, but because this is a script, it's not hard to add
+ the same thing on to every transport.
+ """
+ try:
+ merge_value('localnet', sip.get('general', 'localnet')[0], 'general',
+ pjsip, nmapped, 'transport', section)
+ except LookupError:
+ # No localnet options configured. No biggie!
+ pass
+
+
+def set_transport_common(section, pjsip, 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:
+ localnet
+ tos_sip
+ cos_sip
+ """
+
+ try:
+ merge_value('localnet', sip.get('general', 'localnet')[0], 'general',
+ pjsip, nmapped, 'transport', section)
+ except LookupError:
+ # No localnet options configured. Move on.
+ pass
+
+ try:
+ set_value('tos', sip.get('general', 'sip_tos')[0], 'general', pjsip,
+ nmapped, 'transport', section)
+ except LookupError:
+ pass
+
+ try:
+ set_value('cos', sip.get('general', 'sip_cos')[0], 'general', pjsip,
+ nmapped, 'transport', section)
+ except LookupError:
+ pass
+
+
+def split_hostport(addr):
+ """
+ Given an address in the form 'addr:port' separate the addr and port
+ components.
+ Returns a two-tuple of strings, (addr, 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)
+ else:
+ # If brackets are present, there may be a port as well
+ match = re.match('\[(.*\)]:(\d+)', addr)
+ if match:
+ return (match.group(1), match.group(2))
+ else:
+ return (addr, None)
+ except socket.error:
+ pass
+
+ # IPv4 address or hostname
+ host, sep, port = addr.rpartition(':')
+
+ if not sep and not port:
+ return (host, None)
+ else:
+ return (host, port)
+
+
+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
+ externaddr (or externip)
+ externhost
+ """
+
+ bind = sip.multi_get('general', ['udpbindaddr', 'bindaddr'])[0]
+ bind = build_host(sip, bind, 'general', 'bindport')
+
+ try:
+ extern_addr = sip.multi_get('general', ['externaddr', 'externip',
+ 'externhost'])[0]
+ host, port = split_hostport(extern_addr)
+ set_value('external_signaling_address', host, 'transport-udp', pjsip,
+ nmapped, 'transport')
+ if port:
+ set_value('external_signaling_port', port, 'transport-udp', pjsip,
+ nmapped, 'transport')
+ except LookupError:
+ pass
+
+ set_value('protocol', 'udp', 'transport-udp', pjsip, nmapped, 'transport')
+ set_value('bind', bind, 'transport-udp', pjsip, nmapped, 'transport')
+ set_transport_common('transport-udp', pjsip, 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
+ externtcpport
+ """
+
+ try:
+ enabled = sip.get('general', 'tcpenable')[0]
+ except:
+ # No value means disabled by default. No need for a tranport
+ return
+
+ if enabled == 'no':
+ return
+
+ try:
+ bind = sip.get('general', 'tcpbindaddr')[0]
+ bind = build_host(sip, bind, 'general', 'bindport')
+ except LookupError:
+ # No tcpbindaddr means to default to the udpbindaddr
+ bind = pjsip.get('transport-udp', 'bind')[0]
+
+ try:
+ extern_addr = sip.multi_get('general', ['externaddr', 'externip',
+ 'externhost'])[0]
+ host, port = split_hostport(extern_addr)
+ try:
+ tcpport = sip.get('general', 'externtcpport')[0]
+ except:
+ tcpport = port
+ set_value('external_signaling_address', host, 'transport-tcp', pjsip,
+ nmapped, 'transport')
+ if tcpport:
+ set_value('external_signaling_port', tcpport, 'transport-tcp',
+ pjsip, nmapped, 'transport')
+ except LookupError:
+ pass
+
+ set_value('protocol', 'tcp', 'transport-tcp', pjsip, nmapped, 'transport')
+ set_value('bind', bind, 'transport-tcp', pjsip, nmapped, 'transport')
+ set_transport_common('transport-tcp', pjsip, nmapped)
+
+
+def set_tls_bindaddr(val, pjsip, nmapped):
+ """
+ Creates the TCP bind address. This has two possible methods of
+ working:
+ Use the 'tlsbindaddr' option from sip.conf directly if it has both
+ an address and port. If no port is present, use 5061
+ If there is no 'tlsbindaddr' option present in sip.conf, use the
+ previously-established UDP bind address and port 5061
+ """
+ try:
+ bind = sip.get('general', 'tlsbindaddr')[0]
+ explicit = True
+ except LookupError:
+ # No tlsbindaddr means to default to the bindaddr but with standard TLS
+ # port
+ bind = pjsip.get('transport-udp', 'bind')[0]
+ explicit = False
+
+ matchv4 = re.match('\d+\.\d+\.\d+\.\d+:\d+', bind)
+ matchv6 = re.match('\[.*\]:d+', bind)
+ if matchv4 or matchv6:
+ if explicit:
+ # They provided a port. We'll just use it.
+ set_value('bind', bind, 'transport-tls', pjsip, nmapped,
+ 'transport')
+ return
+ else:
+ # Need to strip the port from the UDP address
+ index = bind.rfind(':')
+ bind = bind[:index]
+
+ # Reaching this point means either there was no port provided or we
+ # stripped the port off. We need to add on the default 5061 port
+
+ bind += ':5061'
+
+ set_value('bind', bind, 'transport-tls', pjsip, nmapped, 'transport')
+
+
+def set_tls_private_key(val, pjsip, nmapped):
+ """Sets privkey_file based on sip.conf tlsprivatekey or sslprivatekey"""
+ set_value('privkey_file', val, 'transport-tls', pjsip, nmapped,
+ 'transport')
+
+
+def set_tls_cipher(val, pjsip, nmapped):
+ """Sets cipher based on sip.conf tlscipher or sslcipher"""
+ set_value('cipher', val, 'transport-tls', pjsip, nmapped, 'transport')
+
+
+def set_tls_cafile(val, pjsip, nmapped):
+ """Sets ca_list_file based on sip.conf tlscafile"""
+ set_value('ca_list_file', val, 'transport-tls', pjsip, nmapped,
+ 'transport')
+
+
+def set_tls_verifyclient(val, pjsip, nmapped):
+ """Sets verify_client based on sip.conf tlsverifyclient"""
+ set_value('verify_client', val, 'transport-tls', pjsip, nmapped,
+ 'transport')
+
+
+def set_tls_verifyserver(val, pjsip, nmapped):
+ """Sets verify_server based on sip.conf tlsdontverifyserver"""
+
+ if val == 'no':
+ set_value('verify_server', 'yes', 'transport-tls', pjsip, nmapped,
+ 'transport')
+ else:
+ set_value('verify_server', 'no', 'transport-tls', pjsip, nmapped,
+ 'transport')
+
+
+def set_tls_method(val, pjsip, nmapped):
+ """Sets method based on sip.conf tlsclientmethod or sslclientmethod"""
+ set_value('method', val, 'transport-tls', 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)
+ tlsprivatekey (or sslprivatekey)
+ tlscipher (or sslcipher)
+ tlscafile
+ tlscapath (or tlscadir)
+ tlscertfile (or sslcert or tlscert)
+ tlsverifyclient
+ tlsdontverifyserver
+ tlsclientmethod (or sslclientmethod)
+ """
+
+ tls_map = [
+ (['tlsbindaddr', 'sslbindaddr'], set_tls_bindaddr),
+ (['tlsprivatekey', 'sslprivatekey'], set_tls_private_key),
+ (['tlscipher', 'sslcipher'], set_tls_cipher),
+ (['tlscafile'], set_tls_cafile),
+ (['tlsverifyclient'], set_tls_verifyclient),
+ (['tlsdontverifyserver'], set_tls_verifyserver),
+ (['tlsclientmethod', 'sslclientmethod'], set_tls_method)
+ ]
+
+ try:
+ enabled = sip.multi_get('general', ['tlsenable', 'sslenable'])[0]
+ except LookupError:
+ # Not enabled. Don't create a transport
+ return
+
+ if enabled == 'no':
+ return
+
+ set_value('protocol', 'tls', 'transport-tls', pjsip, nmapped, 'transport')
+
+ for i in tls_map:
+ try:
+ i[1](sip.multi_get('general', i[0])[0], pjsip, nmapped)
+ except LookupError:
+ pass
+
+ set_transport_common('transport-tls', pjsip, nmapped)
+ try:
+ extern_addr = sip.multi_get('general', ['externaddr', 'externip',
+ 'externhost'])[0]
+ host, port = split_hostport(extern_addr)
+ try:
+ tlsport = sip.get('general', 'externtlsport')[0]
+ except:
+ tlsport = port
+ set_value('external_signaling_address', host, 'transport-tls', pjsip,
+ nmapped, 'transport')
+ if tlsport:
+ set_value('external_signaling_port', tlsport, 'transport-tls',
+ pjsip, nmapped, 'transport')
+ except LookupError:
+ pass
+
+
+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. As such, the mapping into PJSIP can be made
+ consistent by defining three sections:
+
+ transport-udp
+ transport-tcp
+ transport-tls
+
+ To accommodate the default behaviors in sip.conf, we'll need to
+ create the UDP transport 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)
+
+ # TCP settings may be dependent on UDP settings, so do it second.
+ create_tcp(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.
+ """
+ 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
+
+ pre_domain, sep, self.domain = pre_auth.partition('@')
+ self.peer, sep, post_peer = pre_domain.rpartition('?')
+ transport, sep, self.user = post_peer.rpartition('://')
+
+ self.protocol = transport if transport else 'udp'
+
+ 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 self.secret:
+ set_value('password', self.secret, auth_section, pjsip, nmapped,
+ 'auth')
+ set_value('username', self.authuser or 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 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, 'registartion')
+
+
+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_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
+
+
+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 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 = astconfigparser.MultiOrderedConfigParser()
+ non_mappings[filename] = astdicts.MultiOrderedDict()
+ nmapped = non_mapped(non_mappings[filename])
+ if not include:
+ # Don't duplicate transport and registration configs
+ 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)
+
+ 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"
+
+###############################################################################
+
+
+def cli_options():
+ """
+ Parse command line options and apply them. If invalid input is given,
+ print usage information
+ """
+ global PREFIX
+ usage = "usage: %prog [options] [input-file [output-file]]\n\n" \
+ "input-file defaults to 'sip.conf'\n" \
+ "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')
+
+ options, args = parser.parse_args()
+ PREFIX = options.prefix
+
+ 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
+
+if __name__ == "__main__":
+ sip_filename, pjsip_filename = cli_options()
+ # configuration parser for sip.conf
+ sip = astconfigparser.MultiOrderedConfigParser()
+ sip.read(sip_filename)
+ pjsip, non_mappings = convert(sip, pjsip_filename, dict(), False)
+ write_pjsip(pjsip_filename, pjsip, non_mappings)
diff --git a/contrib/scripts/sip_to_res_sip/sip_to_res_sip.py b/contrib/scripts/sip_to_res_sip/sip_to_res_sip.py
deleted file mode 100755
index ca253bdd0..000000000
--- a/contrib/scripts/sip_to_res_sip/sip_to_res_sip.py
+++ /dev/null
@@ -1,392 +0,0 @@
-#!/usr/bin/python
-
-###############################################################################
-# TODO:
-# (1) There is more work to do here, at least for the sip.conf items that
-# aren't currently parsed. An issue will be created for that.
-# (2) All of the scripts should probably be passed through pylint and have
-# as many PEP8 issues fixed as possible
-# (3) A public review is probably warranted at that point of the entire script
-###############################################################################
-
-import optparse
-import astdicts
-import astconfigparser
-
-PREFIX = 'res_sip_'
-
-###############################################################################
-### some utility functions
-###############################################################################
-def section_by_type(section, res_sip, type):
- """Finds a section based upon the given type, adding it if not found."""
- try:
- return astconfigparser.find_dict(
- res_sip.section(section), 'type', type)
- except LookupError:
- # section for type doesn't exist, so add
- sect = res_sip.add_section(section)
- sect['type'] = type
- return sect
-
-def set_value(key=None, val=None, section=None, res_sip=None,
- nmapped=None, type='endpoint'):
- """Sets the key to the value within the section in res_sip.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, res_sip, type)[key] = \
- val[0] if isinstance(val, list) else val
-
-def merge_value(key=None, val=None, section=None, res_sip=None,
- nmapped=None, type='endpoint', section_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)
-
- # 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
- sect = sip.section(section)[0]
- # for each merged value add it to res_sip.conf
- for i in sect.get_merged(key):
- set_value(key, i, section_to if section_to else section,
- res_sip, nmapped, type)
-
-def is_in(s, sub):
- """Returns true if 'sub' is in 's'"""
- return s.find(sub) != -1
-
-def non_mapped(nmapped):
- 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 res_sip.conf
-###############################################################################
-
-def set_dtmfmode(key, val, section, res_sip, nmapped):
- """Sets the dtmfmode value. If value matches allowable option in res_sip
- then map it, otherwise set it to none.
- """
- # available res_sip.conf values: frc4733, inband, info, none
- if val != 'inband' or val != 'info':
- nmapped(section, key, val + " ; did not fully map - set to none")
- val = 'none'
- set_value(key, val, section, res_sip, nmapped)
-
-def from_nat(key, val, section, res_sip, nmapped):
- """Sets values from nat into the appropriate res_sip.conf options."""
- # nat from sip.conf can be comma separated list of values:
- # yes/no, [auto_]force_rport, [auto_]comedia
- if is_in(val, 'yes'):
- set_value('rtp_symmetric', 'yes', section, res_sip, nmapped)
- set_value('rewrite_contact', 'yes', section, res_sip, nmapped)
- if is_in(val, 'comedia'):
- set_value('rtp_symmetric', 'yes', section, res_sip, nmapped)
- if is_in(val, 'force_rport'):
- set_value('force_rport', 'yes', section, res_sip, nmapped)
- set_value('rewrite_contact', 'yes', section, res_sip, nmapped)
-
-def set_timers(key, val, section, res_sip, nmapped):
- """Sets the timers in res_sip.conf from the session-timers option
- found in sip.conf.
- """
- # res_sip.conf values can be yes/no, required, always
- if val == 'originate':
- set_value('timers', 'always', section, res_sip, nmapped)
- elif val == 'accept':
- set_value('timers', 'required', section, res_sip, nmapped)
- elif val == 'never':
- set_value('timers', 'no', section, res_sip, nmapped)
- else:
- set_value('timers', 'yes', section, res_sip, nmapped)
-
-def set_direct_media(key, val, section, res_sip, nmapped):
- """Maps values from the sip.conf comma separated direct_media option
- into res_sip.conf direct_media options.
- """
- if is_in(val, 'yes'):
- set_value('direct_media', 'yes', section, res_sip, nmapped)
- if is_in(val, 'update'):
- set_value('direct_media_method', 'update', section, res_sip, nmapped)
- if is_in(val, 'outgoing'):
- set_value('directed_media_glare_mitigation', 'outgoing', section, res_sip, nmapped)
- if is_in(val, 'nonat'):
- set_value('disable_directed_media_on_nat','yes', section, res_sip, nmapped)
- if (val == 'no'):
- set_value('direct_media', 'no', section, res_sip, nmapped)
-
-def from_sendrpid(key, val, section, res_sip, nmapped):
- """Sets the send_rpid/pai values in res_sip.conf."""
- if val == 'yes' or val == 'rpid':
- set_value('send_rpid', 'yes', section, res_sip, nmapped)
- elif val == 'pai':
- set_value('send_pai', 'yes', section, res_sip, nmapped)
-
-def set_media_encryption(key, val, section, res_sip, nmapped):
- """Sets the media_encryption value in res_sip.conf"""
- if val == 'yes':
- set_value('media_encryption', 'sdes', section, res_sip, nmapped)
-
-def from_recordfeature(key, val, section, res_sip, nmapped):
- """If record on/off feature is set to automixmon then set
- one_touch_recording, otherwise it can't be mapped.
- """
- if val == 'automixmon':
- set_value('one_touch_recording', 'yes', section, res_sip, nmapped)
- else:
- nmapped(section, key, val + " ; could not be fully mapped")
-
-def from_progressinband(key, val, section, res_sip, nmapped):
- """Sets the inband_progress value in res_sip.conf"""
- # progressinband can = yes/no/never
- if val == 'never':
- val = 'no'
- set_value('inband_progress', val, section, res_sip, nmapped)
-
-def from_host(key, val, section, res_sip, nmapped):
- """Sets contact info in an AOR section in in res_sip.conf using 'host'
- data from sip.conf
- """
- # all aors have the same name as the endpoint so makes
- # it easy to endpoint's 'aors' value
- set_value('aors', section, section, res_sip, nmapped)
- if val != 'dynamic':
- set_value('contact', val, section, res_sip, nmapped, 'aor')
- else:
- set_value('max_contacts', 1, section, res_sip, nmapped, 'aor')
-
-def from_subscribemwi(key, val, section, res_sip, nmapped):
- """Checks the subscribemwi value in sip.conf. If yes places the
- mailbox value in mailboxes within the endpoint, otherwise puts
- it in the aor.
- """
- mailboxes = sip.get('mailbox', section, res_sip)
- type = 'endpoint' if val == 'yes' else 'aor'
- set_value('mailboxes', mailboxes, section, res_sip, nmapped, type)
-
-###############################################################################
-
-# options in res_sip.conf on an endpoint that have no sip.conf equivalent:
-# type, rtp_ipv6, 100rel, trust_id_outbound, aggregate_mwi,
-# connected_line_method
-
-# known sip.conf peer keys that can be mapped to a res_sip.conf section/key
-peer_map = [
- # sip.conf option mapping function res_sip.conf option(s)
- ###########################################################################
- ['context', set_value],
- ['dtmfmode', set_dtmfmode],
- ['disallow', merge_value],
- ['allow', merge_value],
- ['nat', from_nat], # rtp_symmetric, force_rport,
- # rewrite_contact
- ['icesupport', set_value('ice_support')],
- ['autoframing', set_value('use_ptime')],
- ['outboundproxy', set_value('outbound_proxy')],
- ['mohsuggest', set_value],
- ['session-timers', set_timers], # timers
- ['session-minse', set_value('timers_min_se')],
- ['session-expires', set_value('timers_sess_expires')],
- ['externip', set_value('external_media_address')],
- ['externhost', set_value('external_media_address')],
- # identify_by ?
- ['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')],
- ['trustpid', set_value('trust_id_inbound')],
- ['sendrpid', from_sendrpid], # send_pai, send_rpid
- ['send_diversion', set_value],
- ['encrpytion', set_media_encryption],
- ['use_avpf', set_value],
- ['recordonfeature', from_recordfeature], # automixon
- ['recordofffeature', from_recordfeature], # automixon
- ['progressinband', from_progressinband], # in_band_progress
- ['callgroup', set_value],
- ['pickupgroup', set_value],
- ['namedcallgroup', set_value],
- ['namedpickupgroup', set_value],
- ['busylevel', set_value('devicestate_busy_at')],
-
-############################ maps to an aor ###################################
-
- ['host', from_host], # contact, max_contacts
- ['subscribemwi', from_subscribemwi], # mailboxes
- ['qualifyfreq', set_value('qualify_frequency', type='aor')],
-
-############################# maps to auth#####################################
-# type = auth
-# username
-# password
-# md5_cred
-# realm
-# nonce_lifetime
-# auth_type
-######################### maps to acl/security ################################
-
- ['permit', merge_value(type='security', section_to='acl')],
- ['deny', merge_value(type='security', section_to='acl')],
- ['acl', merge_value(type='security', section_to='acl')],
- ['contactpermit', merge_value(type='security', section_to='acl')],
- ['contactdeny', merge_value(type='security', section_to='acl')],
- ['contactacl', merge_value(type='security', section_to='acl')],
-
-########################### maps to transport #################################
-# type = transport
-# protocol
-# bind
-# async_operations
-# ca_list_file
-# 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 map_peer(sip, section, res_sip, nmapped):
- 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, res_sip, nmapped)
- except LookupError:
- pass # key not found in sip.conf
-
-def find_non_mapped(sections, nmapped):
- 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 convert(sip, filename, non_mappings):
- res_sip = astconfigparser.MultiOrderedConfigParser()
- non_mappings[filename] = astdicts.MultiOrderedDict()
- nmapped = non_mapped(non_mappings[filename])
- for section in sip.sections():
- if section == 'authentication':
- pass
- else:
- map_peer(sip, section, res_sip, nmapped)
-
- find_non_mapped(sip.defaults(), nmapped)
- find_non_mapped(sip.sections(), nmapped)
-
- for key, val in sip.includes().iteritems():
- res_sip.add_include(PREFIX + key, convert(val, PREFIX + key, non_mappings)[0])
- return res_sip, non_mappings
-
-def write_res_sip(filename, res_sip, non_mappings):
- 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)
- for key, val in res_sip.includes().iteritems():
- write_res_sip(key, val, non_mappings)
- fp.write('#include "%s"\n' % key)
- fp.write('\n')
- # write out mapped data elements
- astconfigparser.write_dicts(fp, res_sip.defaults())
- astconfigparser.write_dicts(fp, res_sip.sections())
-
- except IOError:
- print "Could not open file ", filename, " for writing"
-
-###############################################################################
-
-def cli_options():
- global PREFIX
- usage = "usage: %prog [options] [input-file [output-file]]\n\n" \
- "input-file defaults to 'sip.conf'\n" \
- "output-file defaults to 'res_sip.conf'"
- parser = optparse.OptionParser(usage=usage)
- parser.add_option('-p', '--prefix', dest='prefix', default=PREFIX,
- help='output prefix for include files')
-
- options, args = parser.parse_args()
- PREFIX = options.prefix
-
- sip_filename = args[0] if len(args) else 'sip.conf'
- res_sip_filename = args[1] if len(args) == 2 else 'res_sip.conf'
-
- return sip_filename, res_sip_filename
-
-if __name__ == "__main__":
- sip_filename, res_sip_filename = cli_options()
- # configuration parser for sip.conf
- sip = astconfigparser.MultiOrderedConfigParser()
- sip.read(sip_filename)
- res_sip, non_mappings = convert(sip, res_sip_filename, dict())
- write_res_sip(res_sip_filename, res_sip, non_mappings)