393 lines
16 KiB
Python
Executable File
393 lines
16 KiB
Python
Executable File
#!/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)
|