PSJIP - sip.conf to res_sip.conf script
authorKevin Harwell <kharwell@digium.com>
Wed, 10 Jul 2013 22:26:13 +0000 (22:26 +0000)
committerKevin Harwell <kharwell@digium.com>
Wed, 10 Jul 2013 22:26:13 +0000 (22:26 +0000)
** This script is in no way finished.

Started the initial "cut" at converting a sip.conf file to a res_sip.conf file.
Hopefully the bulk of the framework is in place and only a few minor adjustments
need to be made when an option mapping is added that "doesn't fit".  This script
and supporting files should be executable against python version 2.5.

An OrderedDict class (backported from a newer version of python) is included.
A MultiOrderedDict class is implemented so options, when added, should be able
to be added in order and allowed to have multiple values.

Currently the scripts supports the majority of endpoint options found in
res_sip.conf.  Support has also been added for Aor(s) and the ACL/security
sections.  Inside the sip_to_res_sip.py file one can see a list of options
that still need to be mapped.

Also items that still need to be done: templates, includes, parsing '=>'
delimiter.  Note that some code is hopefully in place already to support
templates (e.g. lookup/retrieving defaults from them).  However, the
parsing of and adding of the section needs to be done.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@394024 65c4cc65-6c06-0410-ace0-fbb531ad65f3

contrib/scripts/sip_to_res_sip/astconfigparser.py [new file with mode: 0644]
contrib/scripts/sip_to_res_sip/astdicts.py [new file with mode: 0644]
contrib/scripts/sip_to_res_sip/sip_to_res_sip.py [new file with mode: 0755]

diff --git a/contrib/scripts/sip_to_res_sip/astconfigparser.py b/contrib/scripts/sip_to_res_sip/astconfigparser.py
new file mode 100644 (file)
index 0000000..f5baf13
--- /dev/null
@@ -0,0 +1,242 @@
+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
+        vals0 = left[key] if key in left else []
+    vals1 = right[key] if key in right else []
+
+    return vals0 + [i for i in vals1 if i not in vals0]
+
+###############################################################################
+
+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.
+    """
+    def __init__(self, defaults = []):
+        MultiOrderedDict.__init__(self)
+        self._defaults = defaults
+
+    def __getitem__(self, key):
+        """Get the value for the given key. If it is not found in the 'self'
+           then check inside the defaults before declaring unable to locate."""
+        if key in self:
+            return MultiOrderedDict.__getitem__(self, key)
+
+        for default in self._defaults:
+            if key in default:
+                return default[key]
+
+        raise KeyError(key)
+
+    def keys(self):
+        res = MultiOrderedDict.keys(self)
+        for d in self._defaults:
+            for key in d.keys():
+                if key not in res:
+                    res.append(key)
+        return res
+
+    def add_default(self, default):
+        self._defaults.append(default)
+
+    def get_merged(self, key):
+        """Return a list of values for a given key merged from default(s)"""
+        # first merge key/values from defaults together
+        merged = []
+        for i in self._defaults:
+            if not merged:
+                merged = i
+                continue
+            merged = merge_values(merged, i, key)
+        # then merge self in
+        return merge_values(merged, self, key)
+
+###############################################################################
+
+def remove_comment(line):
+    """Remove any commented elements from the given line"""
+    line = line.partition(COMMENT)[0]
+    return line.rstrip()
+
+def try_section(line):
+    """Checks to see if the given line is a section. If so return the section
+       name, otherwise return 'None'.
+    """
+    if not line.startswith('['):
+        return None
+
+    first, second, third = line.partition(']')
+    # TODO - third may contain template, parse to see if it is a template
+    #        or is a list of templates...return?
+    return first[1:]
+
+def try_option(line):
+    """Parses the line as an option, returning the key/value pair."""
+    first, second, third = line.partition('=')
+    return first.strip(), third.strip()
+
+###############################################################################
+
+def get_value(mdict, key, index=-1):
+    """Given a multi-dict, retrieves a value for the given key. If the key only
+       holds a single value return that value. If the key holds more than one
+       value and an index is given that is greater than or equal to zero then
+       return the value at the index. Otherwise return the list of values."""
+    vals = mdict[key]
+    if len(vals) == 1:
+        return vals[0]
+    if index >= 0:
+        return vals[index]
+    return vals
+
+def find_value(mdicts, key, index=-1):
+    """Given a list of multi-dicts, try to find value(s) for the given key."""
+    if not isinstance(mdicts, list):
+        # given a single multi-dict
+        return get_value(mdicts, key, index)
+
+    for d in mdicts:
+        if key in d:
+            return d[key]
+    # not found, throw error
+    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."""
+
+    def found(d):
+        # just check the first value of the key
+        return key in d and d[key][0] == val
+
+    if isinstance(mdicts, list):
+        try:
+            return [d for d in mdicts if found(d)][0]
+        except IndexError:
+            pass
+    elif found(mdicts):
+        return mdicts
+
+    raise LookupError("Dictionary not located for key = %s, value = %s"
+                      % (key, val))
+
+###############################################################################
+
+COMMENT = ';'
+DEFAULTSECT = 'general'
+
+class MultiOrderedConfigParser:
+    def __init__(self):
+        self._default = MultiOrderedDict()
+        # sections contain dictionaries of dictionaries
+        self._sections = MultiOrderedDict()
+
+    def default(self):
+        return self._default
+
+    def sections(self):
+        return self._sections
+
+    def section(self, section, index=-1):
+        """Retrieves a section dictionary for the given section. If the section
+           holds only a single section dictionary return that dictionary. If
+           the section holds more than one dictionary and an index is given
+           that is greater than or equal to zero then return the dictionary at
+           the index. Otherwise return the list of dictionaries for the given
+           section name."""
+        try:
+            return get_value(self._sections, section, index)
+        except KeyError:
+            raise LookupError("section %r not found" % section)
+
+    def add_section(self, section, defaults=[]):
+        """Adds a section with the given name and defaults."""
+        self._sections[section] = res = Section(defaults)
+        return res
+
+    def get(self, key, section=DEFAULTSECT, index=-1):
+        """Retrieves a value for the given key from the given section. If the
+           key only holds a single value return that value. If the key holds
+           more than one value and an index is given that is greater than or
+           equal to zero then return the value at the index. Otherwise return
+           the list of values."""
+        try:
+            if section == DEFAULTSECT:
+                return get_value(self._default, key, index)
+
+            # search section(s)
+            return find_value(self.section(section), key, index)
+        except KeyError:
+            # check default section if we haven't already
+            if section != DEFAULTSECT:
+                return self.get(key, DEFAULTSECT, index)
+            raise LookupError("key %r not found in section %r"
+                              % (key, section))
+
+    def set(self, key, val, section=DEFAULTSECT):
+        """Sets an option in the given section."""
+        if section == DEFAULTSECT:
+            self._default[key] = val
+        else:
+            # for now only set value in first section
+            self.section(section, 0)[key] = val
+
+    def read(self, filename):
+        try:
+            with open(filename, 'rt') as file:
+                self._read(file, filename)
+        except IOError:
+            print "Could not open file ", filename, " for reading"
+
+    def _read(self, file, filename):
+        for line in file:
+            line = remove_comment(line)
+            if not line:
+                # line was empty or was a comment
+                continue
+
+            section = try_section(line)
+            if section:
+                if section == DEFAULTSECT:
+                    sect = self._default
+                else:
+                    self._sections[section] = sect = Section([self._default])
+                    # TODO - if section has templates add those
+                    #        with sect.add_default
+                continue
+
+            key, val = try_option(line)
+            sect[key] = val
+
+    def write(self, filename):
+        try:
+            with open(filename, 'wt') as file:
+                self._write(file)
+        except IOError:
+            print "Could not open file ", filename, " for writing"
+        pass
+
+    def _write(self, file):
+        # TODO - need to write out default section, but right now in
+        #        our case res_sip.conf has not default/general section
+        for section, sect_list in self._sections.iteritems():
+            # every section contains a list of dictionaries
+            for sect in sect_list:
+                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")
diff --git a/contrib/scripts/sip_to_res_sip/astdicts.py b/contrib/scripts/sip_to_res_sip/astdicts.py
new file mode 100644 (file)
index 0000000..2a43c11
--- /dev/null
@@ -0,0 +1,306 @@
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+# copied from http://code.activestate.com/recipes/576693/
+
+try:
+    from thread import get_ident as _get_ident
+except ImportError:
+    from dummy_thread import get_ident as _get_ident
+
+try:
+    from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+    pass
+
+
+class OrderedDict(dict):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
+
+    def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the linked
+        # list, and the inherited dictionary is updated with the new key/value pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, key = self.__map.pop(key)
+        link_prev[1] = link_next
+        link_next[0] = link_prev
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        root = self.__root
+        curr = root[1]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[1]
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        root = self.__root
+        curr = root[0]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[0]
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        try:
+            for node in self.__map.itervalues():
+                del node[:]
+            root = self.__root
+            root[:] = [root, root, None]
+            self.__map.clear()
+        except AttributeError:
+            pass
+        dict.clear(self)
+
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
+        if not self:
+            raise KeyError('dictionary is empty')
+        root = self.__root
+        if last:
+            link = root[0]
+            link_prev = link[0]
+            link_prev[1] = root
+            root[0] = link_prev
+        else:
+            link = root[1]
+            link_next = link[1]
+            root[1] = link_next
+            link_next[0] = root
+        key = link[2]
+        del self.__map[key]
+        value = dict.pop(self, key)
+        return key, value
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        'od.keys() -> list of keys in od'
+        return list(self)
+
+    def values(self):
+        'od.values() -> list of values in od'
+        return [self[key] for key in self]
+
+    def items(self):
+        'od.items() -> list of (key, value) pairs in od'
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        'od.iterkeys() -> an iterator over the keys in od'
+        return iter(self)
+
+    def itervalues(self):
+        'od.itervalues -> an iterator over the values in od'
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        'od.iteritems -> an iterator over the (key, value) items in od'
+        for k in self:
+            yield (k, self[k])
+
+    def update(*args, **kwds):
+        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
+
+        If E is a dict instance, does:           for k in E: od[k] = E[k]
+        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
+        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
+        In either case, this is followed by:     for k, v in F.items(): od[k] = v
+
+        '''
+        if len(args) > 2:
+            raise TypeError('update() takes at most 2 positional '
+                            'arguments (%d given)' % (len(args),))
+        elif not args:
+            raise TypeError('update() takes at least 1 argument (0 given)')
+        self = args[0]
+        # Make progressively weaker assumptions about "other"
+        other = ()
+        if len(args) == 2:
+            other = args[1]
+        if isinstance(other, dict):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, 'keys'):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    __update = update  # let subclasses override update without breaking __init__
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised.
+
+        '''
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return len(self)==len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # -- the following methods are only used in Python 2.7 --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
+
+###############################################################################
+### MultiOrderedDict
+###############################################################################
+class MultiOrderedDict(OrderedDict):
+    def __init__(self, *args, **kwds):
+        OrderedDict.__init__(self, *args, **kwds)
+
+    def __setitem__(self, key, val):
+        if key not in self:
+            OrderedDict.__setitem__(self, key, [val])
+        elif val not in self[key]:
+            self[key].append(val)
+
+    def copy(self):
+        # TODO - find out why for some reason copies
+        #        the [] as an [[]], so do manually
+        c = MultiOrderedDict() #self.__class__(self)
+        for key, val in self.iteritems():
+            for v in val:
+                c[key] = v
+        return c
+
+    # def update(self, other=None, **kwds):
+    #     if other is None:
+    #         pass
+
+    #     if isinstance(other, list):
+    #         for val in other:
+    #             update(self, val)
+    #         return
+
+    #     for key, val in other.iteritems():
+    #         # key = [ v1, v2, ...n ]
+    #         if key in self and len(self[key]) > 1:
+    #             # merge values adding only those not already in list
+    #             val = self[key] + [v for v in val if v not in self[key]]
+    #         OrderedDict.__setitem__(self, key, val)
+    #     # if hasattr(other, 'keys'):
+    #     #         other = other.keys()
+    #     # for (key, val) in obj.iteritems():
+    #     #     if key in self and len(self[key]) > 1:
+    #     #         # add only values not already in list
+    #     #         val = self[key] + [v for v in val if v not in self[key]]
+    #     #     OrderedDict.__setitem__(self, key, val)
+    #     if kwds:
+    #         self.update(kwds)
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
new file mode 100755 (executable)
index 0000000..13444a9
--- /dev/null
@@ -0,0 +1,304 @@
+#!/usr/bin/python
+
+import astconfigparser
+
+# configuration parser for sip.conf
+sip = astconfigparser.MultiOrderedConfigParser()
+
+# configuration writer for res_sip.conf
+res_sip = astconfigparser.MultiOrderedConfigParser()
+
+###############################################################################
+### some utility functions
+###############################################################################
+def section_by_type(section, type='endpoint'):
+    """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, type='endpoint'):
+    """Sets the key to the value within the section in res_sip.conf"""
+    def _set_value(k, v, s):
+        set_value(key if key else k, v, s, 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, type)[key] = val
+
+def merge_value(key=None, val=None, section=None,
+                type='endpoint', section_to=None):
+    """Merge values from the given section with those from the default."""
+    def _merge_value(k, v, s):
+        merge_value(key if key else k, v, s, 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 single section
+    sect = sip.section(section)
+    # 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, type)
+
+def is_in(s, sub):
+    """Returns true if 'sub' is in 's'"""
+    return s.find(sub) != -1
+
+###############################################################################
+### 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):
+    """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':
+        print "sip.conf: dtmfmode = %s did not fully map into " \
+              "res_sip.conf - setting to 'none'" % val
+        val = 'none'
+    set_value(key, val, section)
+
+def from_nat(key, val, section):
+    """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)
+        set_value('rewrite_contact', 'yes', section)
+    if is_in(val, 'comedia'):
+        set_value('rtp_symmetric', 'yes', section)
+    if is_in(val, 'force_rport'):
+        set_value('force_rport', 'yes', section)
+        set_value('rewrite_contact', 'yes', section)
+
+def set_timers(key, val, section):
+    """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)
+    elif val == 'accept':
+        set_value('timers', 'required', section)
+    elif val == 'never':
+        set_value('timers', 'no', section)
+    else:
+        set_value('timers', 'yes', section)
+
+def set_direct_media(key, val, section):
+    """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)
+    if is_in(val, 'update'):
+        set_value('direct_media_method', 'update', section)
+    if is_in(val, 'outgoing'):
+        set_value('directed_media_glare_mitigation', 'outgoing', section)
+    if is_in(val, 'nonat'):
+        set_value('disable_directed_media_on_nat', 'yes', section)
+
+def from_sendrpid(key, val, section):
+    """Sets the send_rpid/pai values in res_sip.conf."""
+    if val == 'yes' or val == 'rpid':
+        set_value('send_rpid', 'yes', section)
+    elif val == 'pai':
+        set_value('send_pai', 'yes', section)
+
+def set_media_encryption(key, val, section):
+    """Sets the media_encryption value in res_sip.conf"""
+    if val == 'yes':
+        set_value('media_encryption', 'sdes', section)
+
+def from_recordfeature(key, val, section):
+    """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)
+    else:
+        print "sip.conf: %s = %s could not be fully map " \
+              "one_touch_recording not set in res_sip.conf" % (key, val)
+
+def from_progressinband(key, val, section):
+    """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)
+
+def from_host(key, val, section):
+    """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)
+    if val != 'dynamic':
+        set_value('contact', val, section, 'aor')
+    else:
+        set_value('max_contacts', 1, section, 'aor')
+
+def from_subscribemwi(key, val, section):
+    """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)
+    type = 'endpoint' if val == 'yes' else 'aor'
+    set_value('mailboxes', mailboxes, section, 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 ?
+    'direct_media':       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(section):
+    for key, fun in peer_map.iteritems():
+        try:
+            fun(key, sip.get(key, section), section)
+        except LookupError:
+            pass
+#            print "%s not found for section %s - putting nothing in res_sip.conf" % (key, section)
+
+    # since we are pulling from sip.conf this should always return
+    # a single peer value and never a list of peers
+    peer = sip.section(section)
+    # loop through the peer and print out any that can't be mapped
+    # for key in peer.keys():
+    #     if key not in peer_map:
+    #         print "Peer: [{}] {} could not be mapped".format(section, key)
+
+def convert():
+    for section in sip.sections():
+        if section == 'authentication':
+            pass
+        elif section != 'general':
+            map_peer(section)
+
+###############################################################################
+
+if __name__ == "__main__":
+    sip.read('sip.conf')
+    convert()
+    res_sip.write('res_sip.conf')