PSJIP - sip.conf to res_sip.conf script

** 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
changes/78/78/1
Kevin Harwell 12 years ago
parent c232a1f821
commit 5d9ac58d8e

@ -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")

@ -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)

@ -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')
Loading…
Cancel
Save