diff options
-rw-r--r-- | contrib/scripts/sip_to_res_sip/astconfigparser.py | 242 | ||||
-rw-r--r-- | contrib/scripts/sip_to_res_sip/astdicts.py | 306 | ||||
-rwxr-xr-x | contrib/scripts/sip_to_res_sip/sip_to_res_sip.py | 304 |
3 files changed, 852 insertions, 0 deletions
diff --git a/contrib/scripts/sip_to_res_sip/astconfigparser.py b/contrib/scripts/sip_to_res_sip/astconfigparser.py new file mode 100644 index 000000000..f5baf13be --- /dev/null +++ b/contrib/scripts/sip_to_res_sip/astconfigparser.py @@ -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 index 000000000..2a43c1163 --- /dev/null +++ b/contrib/scripts/sip_to_res_sip/astdicts.py @@ -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 index 000000000..13444a95c --- /dev/null +++ b/contrib/scripts/sip_to_res_sip/sip_to_res_sip.py @@ -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') |