11 ###############################################################################
12 ### some utility functions
13 ###############################################################################
16 def section_by_type(section, pjsip, type):
17 """Finds a section based upon the given type, adding it if not found."""
18 def __find_dict(mdicts, key, val):
19 """Given a list of mult-dicts, return the multi-dict that contains
20 the given key/value pair."""
23 return key in d and val in d[key]
26 return [d for d in mdicts if found(d)][0]
28 raise LookupError("Dictionary not located for key = %s, value = %s"
32 return __find_dict(pjsip.section(section), 'type', type)
34 # section for type doesn't exist, so add
35 sect = pjsip.add_section(section)
40 def set_value(key=None, val=None, section=None, pjsip=None,
41 nmapped=None, type='endpoint'):
42 """Sets the key to the value within the section in pjsip.conf"""
43 def _set_value(k, v, s, r, n):
44 set_value(key if key else k, v, s, r, n, type)
46 # if no value or section return the set_value
47 # function with the enclosed key and type
48 if not val and not section:
51 # otherwise try to set the value
52 section_by_type(section, pjsip, type)[key] = \
53 val[0] if isinstance(val, list) else val
56 def merge_value(key=None, val=None, section=None, pjsip=None,
57 nmapped=None, type='endpoint', section_to=None):
58 """Merge values from the given section with those from the default."""
59 def _merge_value(k, v, s, r, n):
60 merge_value(key if key else k, v, s, r, n, type, section_to)
62 # if no value or section return the merge_value
63 # function with the enclosed key and type
64 if not val and not section:
67 # should return a single value section list
69 sect = sip.section(section)[0]
71 sect = sip.default(section)[0]
72 # for each merged value add it to pjsip.conf
73 for i in sect.get_merged(key):
74 set_value(key, i, section_to if section_to else section,
78 def non_mapped(nmapped):
79 """Write non-mapped sip.conf values to the non-mapped object"""
80 def _non_mapped(section, key, val):
81 """Writes a non-mapped value from sip.conf to the non-mapped object."""
82 if section not in nmapped:
83 nmapped[section] = astconfigparser.Section()
84 if isinstance(val, list):
86 # since coming from sip.conf we can assume
87 # single section lists
88 nmapped[section][0][key] = v
90 nmapped[section][0][key] = val
93 ###############################################################################
94 ### mapping functions -
95 ### define f(key, val, section) where key/val are the key/value pair to
96 ### write to given section in pjsip.conf
97 ###############################################################################
100 def set_dtmfmode(key, val, section, pjsip, nmapped):
102 Sets the dtmfmode value. If value matches allowable option in pjsip
103 then map it, otherwise set it to none.
106 # available pjsip.conf values: rfc4733, inband, info, none
107 if val == 'inband' or val == 'info':
108 set_value(key, val, section, pjsip, nmapped)
109 elif val == 'rfc2833':
110 set_value(key, 'rfc4733', section, pjsip, nmapped)
112 nmapped(section, key, val + " ; did not fully map - set to none")
113 set_value(key, 'none', section, pjsip, nmapped)
116 def from_nat(key, val, section, pjsip, nmapped):
117 """Sets values from nat into the appropriate pjsip.conf options."""
118 # nat from sip.conf can be comma separated list of values:
119 # yes/no, [auto_]force_rport, [auto_]comedia
121 set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
122 set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
124 set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
125 if 'force_rport' in val:
126 set_value('force_rport', 'yes', section, pjsip, nmapped)
127 set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
130 def set_timers(key, val, section, pjsip, nmapped):
132 Sets the timers in pjsip.conf from the session-timers option
135 # pjsip.conf values can be yes/no, required, always
136 if val == 'originate':
137 set_value('timers', 'always', section, pjsip, nmapped)
138 elif val == 'accept':
139 set_value('timers', 'required', section, pjsip, nmapped)
141 set_value('timers', 'no', section, pjsip, nmapped)
143 set_value('timers', 'yes', section, pjsip, nmapped)
146 def set_direct_media(key, val, section, pjsip, nmapped):
148 Maps values from the sip.conf comma separated direct_media option
149 into pjsip.conf direct_media options.
152 set_value('direct_media', 'yes', section, pjsip, nmapped)
154 set_value('direct_media_method', 'update', section, pjsip, nmapped)
155 if 'outgoing' in val:
156 set_value('directed_media_glare_mitigation', 'outgoing', section,
159 set_value('disable_directed_media_on_nat', 'yes', section, pjsip,
162 set_value('direct_media', 'no', section, pjsip, nmapped)
165 def from_sendrpid(key, val, section, pjsip, nmapped):
166 """Sets the send_rpid/pai values in pjsip.conf."""
167 if val == 'yes' or val == 'rpid':
168 set_value('send_rpid', 'yes', section, pjsip, nmapped)
170 set_value('send_pai', 'yes', section, pjsip, nmapped)
173 def set_media_encryption(key, val, section, pjsip, nmapped):
174 """Sets the media_encryption value in pjsip.conf"""
176 dtls = sip.get(section, 'dtlsenable')[0]
178 # If DTLS is enabled, then that overrides SDES encryption.
184 set_value('media_encryption', 'sdes', section, pjsip, nmapped)
187 def from_recordfeature(key, val, section, pjsip, nmapped):
189 If record on/off feature is set to automixmon then set
190 one_touch_recording, otherwise it can't be mapped.
192 set_value('one_touch_recording', 'yes', section, pjsip, nmapped)
193 set_value(key, val, section, pjsip, nmapped)
195 def set_record_on_feature(key, val, section, pjsip, nmapped):
196 """Sets the record_on_feature in pjsip.conf"""
197 from_recordfeature('record_on_feature', val, section, pjsip, nmapped)
199 def set_record_off_feature(key, val, section, pjsip, nmapped):
200 """Sets the record_off_feature in pjsip.conf"""
201 from_recordfeature('record_off_feature', val, section, pjsip, nmapped)
203 def from_progressinband(key, val, section, pjsip, nmapped):
204 """Sets the inband_progress value in pjsip.conf"""
205 # progressinband can = yes/no/never
208 set_value('inband_progress', val, section, pjsip, nmapped)
211 def build_host(config, host, section, port_key):
213 Returns a string composed of a host:port. This assumes that the host
214 may have a port as part of the initial value. The port_key is only used
215 if the host does not already have a port set on it.
216 Throws a LookupError if the key does not exist
221 socket.inet_pton(socket.AF_INET6, host)
222 if not host.startswith('['):
223 # SIP URI will need brackets.
224 host = '[' + host + ']'
226 # If brackets are present, there may be a port as well
227 port = re.match('\[.*\]:(\d+)', host)
229 # No biggie. It's just not an IPv6 address
230 port = re.match('.*:(\d+)', host)
236 port = config.get(section, port_key)[0]
244 def from_host(key, val, section, pjsip, nmapped):
246 Sets contact info in an AOR section in pjsip.conf using 'host'
247 and 'port' data from sip.conf
249 # all aors have the same name as the endpoint so makes
250 # it easy to set endpoint's 'aors' value
251 set_value('aors', section, section, pjsip, nmapped)
253 # Easy case. Just set the max_contacts on the aor and we're done
254 set_value('max_contacts', 1, section, pjsip, nmapped, 'aor')
259 # More difficult case. The host will be either a hostname or
260 # IP address and may or may not have a port specified. pjsip.conf
261 # expects the contact to be a SIP URI.
266 user = sip.multi_get(section, ['defaultuser', 'username'])[0]
269 # It's fine if there's no user name
272 result += build_host(sip, val, section, 'port')
274 set_value('contact', result, section, pjsip, nmapped, 'aor')
277 def from_mailbox(key, val, section, pjsip, nmapped):
279 Determines whether a mailbox configured in sip.conf should map to
280 an endpoint or aor in pjsip.conf. If subscribemwi is true, then the
281 mailboxes are set on an aor. Otherwise the mailboxes are set on the
286 subscribemwi = sip.get(section, 'subscribemwi')[0]
288 # No subscribemwi option means default it to 'no'
291 set_value('mailboxes', val, section, pjsip, nmapped, 'aor'
292 if subscribemwi == 'yes' else 'endpoint')
295 def setup_auth(key, val, section, pjsip, nmapped):
297 Sets up authentication information for a specific endpoint based on the
298 'secret' setting on a peer in sip.conf
300 set_value('username', section, section, pjsip, nmapped, 'auth')
301 # In chan_sip, if a secret and an md5secret are both specified on a peer,
302 # then in practice, only the md5secret is used. If both are encountered
303 # then we build an auth section that has both an md5_cred and password.
304 # However, the auth_type will indicate to authenticators to use the
305 # md5_cred, so like with sip.conf, the password will be there but have
308 set_value('password', val, section, pjsip, nmapped, 'auth')
310 set_value('md5_cred', val, section, pjsip, nmapped, 'auth')
311 set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
315 auths = sip.get('authentication', 'auth')
317 user, at, realm = i.partition('@')
322 realm_str = ','.join(realms)
324 set_value('auth', section, section, pjsip, nmapped)
325 set_value('outbound_auth', realm_str, section, pjsip, nmapped)
328 def setup_ident(key, val, section, pjsip, nmapped):
330 Examines the 'type' field for a sip.conf peer and creates an identify
331 section if the type is either 'peer' or 'friend'. The identify section uses
332 either the host or defaultip field of the sip.conf peer.
334 if val != 'peer' and val != 'friend':
338 ip = sip.get(section, 'host')[0]
344 ip = sip.get(section, 'defaultip')[0]
348 set_value('endpoint', section, section, pjsip, nmapped, 'identify')
349 set_value('match', ip, section, pjsip, nmapped, 'identify')
352 def from_encryption_taglen(key, val, section, pjsip, nmapped):
353 """Sets the srtp_tag32 option based on sip.conf encryption_taglen"""
355 set_value('srtp_tag_32', 'yes', section, pjsip, nmapped)
358 def from_dtlsenable(key, val, section, pjsip, nmapped):
359 """Optionally sets media_encryption=dtls based on sip.conf dtlsenable"""
361 set_value('media_encryption', 'dtls', section, pjsip, nmapped)
363 ###############################################################################
365 # options in pjsip.conf on an endpoint that have no sip.conf equivalent:
366 # type, rtp_ipv6, 100rel, trust_id_outbound, aggregate_mwi,
367 # connected_line_method
369 # known sip.conf peer keys that can be mapped to a pjsip.conf section/key
371 # sip.conf option mapping function pjsip.conf option(s)
372 ###########################################################################
373 ['context', set_value],
374 ['dtmfmode', set_dtmfmode],
375 ['disallow', merge_value],
376 ['allow', merge_value],
377 ['nat', from_nat], # rtp_symmetric, force_rport,
379 ['icesupport', set_value('ice_support')],
380 ['autoframing', set_value('use_ptime')],
381 ['outboundproxy', set_value('outbound_proxy')],
382 ['mohsuggest', set_value('moh_suggest')],
383 ['session-timers', set_timers], # timers
384 ['session-minse', set_value('timers_min_se')],
385 ['session-expires', set_value('timers_sess_expires')],
386 ['externip', set_value('external_media_address')],
387 ['externhost', set_value('external_media_address')],
389 ['directmedia', set_direct_media], # direct_media
390 # direct_media_method
391 # directed_media_glare_mitigation
392 # disable_directed_media_on_nat
393 ['callerid', set_value], # callerid
394 ['callingpres', set_value('callerid_privacy')],
395 ['cid_tag', set_value('callerid_tag')],
396 ['trustpid', set_value('trust_id_inbound')],
397 ['sendrpid', from_sendrpid], # send_pai, send_rpid
398 ['send_diversion', set_value],
399 ['encrpytion', set_media_encryption],
400 ['avpf', set_value('use_avpf')],
401 ['recordonfeature', set_record_on_feature], # automixon
402 ['recordofffeature', set_record_off_feature], # automixon
403 ['progressinband', from_progressinband], # in_band_progress
404 ['callgroup', set_value('call_group')],
405 ['pickupgroup', set_value('pickup_group')],
406 ['namedcallgroup', set_value('named_call_group')],
407 ['namedpickupgroup', set_value('named_pickup_group')],
408 ['allowtransfer', set_value('allow_transfer')],
409 ['fromuser', set_value('from_user')],
410 ['fromdomain', set_value('from_domain')],
411 ['mwifrom', set_value('mwi_from_user')],
412 ['tos_audio', set_value],
413 ['tos_video', set_value],
414 ['cos_audio', set_value],
415 ['cos_video', set_value],
416 ['sdpowner', set_value('sdp_owner')],
417 ['sdpsession', set_value('sdp_session')],
418 ['tonezone', set_value('tone_zone')],
419 ['language', set_value],
420 ['allowsubscribe', set_value('allow_subscribe')],
421 ['subminexpiry', set_value('sub_min_expiry')],
422 ['rtp_engine', set_value],
423 ['mailbox', from_mailbox],
424 ['busylevel', set_value('device_state_busy_at')],
425 ['secret', setup_auth],
426 ['md5secret', setup_auth],
427 ['type', setup_ident],
428 ['dtlsenable', from_dtlsenable],
429 ['dtlsverify', set_value('dtls_verify')],
430 ['dtlsrekey', set_value('dtls_rekey')],
431 ['dtlscertfile', set_value('dtls_cert_file')],
432 ['dtlsprivatekey', set_value('dtls_private_key')],
433 ['dtlscipher', set_value('dtls_cipher')],
434 ['dtlscafile', set_value('dtls_ca_file')],
435 ['dtlscapath', set_value('dtls_ca_path')],
436 ['dtlssetup', set_value('dtls_setup')],
437 ['encryption_taglen', from_encryption_taglen],
439 ############################ maps to an aor ###################################
441 ['host', from_host], # contact, max_contacts
442 ['qualifyfreq', set_value('qualify_frequency', type='aor')],
444 ############################# maps to auth#####################################
452 ######################### maps to acl/security ################################
454 ['permit', merge_value(type='acl', section_to='acl')],
455 ['deny', merge_value(type='acl', section_to='acl')],
456 ['acl', merge_value(type='acl', section_to='acl')],
457 ['contactpermit', merge_value('contact_permit', type='acl', section_to='acl')],
458 ['contactdeny', merge_value('contact_deny', type='acl', section_to='acl')],
459 ['contactacl', merge_value('contact_acl', type='acl', section_to='acl')],
461 ########################### maps to transport #################################
470 # external_signaling_address - externip & externhost
471 # external_signaling_port
472 # external_media_address
476 # require_client_cert
480 ######################### maps to domain_alias ################################
481 # type = domain_alias
483 ######################### maps to registration ################################
484 # type = registration
493 # auth_rejection_permanent
495 ########################### maps to identify ##################################
502 def add_localnet(section, pjsip, nmapped):
504 Adds localnet values from sip.conf's general section to a transport in
505 pjsip.conf. Ideally, we would have just created a template with the
506 localnet sections, but because this is a script, it's not hard to add
507 the same thing on to every transport.
510 merge_value('local_net', sip.get('general', 'localnet')[0], 'general',
511 pjsip, nmapped, 'transport', section)
513 # No localnet options configured. No biggie!
517 def set_transport_common(section, pjsip, nmapped):
519 sip.conf has several global settings that in pjsip.conf apply to individual
520 transports. This function adds these global settings to each individual
523 The settings included are:
530 merge_value('local_net', sip.get('general', 'localnet')[0], 'general',
531 pjsip, nmapped, 'transport', section)
533 # No localnet options configured. Move on.
537 set_value('tos', sip.get('general', 'sip_tos')[0], 'general', pjsip,
538 nmapped, 'transport', section)
543 set_value('cos', sip.get('general', 'sip_cos')[0], 'general', pjsip,
544 nmapped, 'transport', section)
549 def split_hostport(addr):
551 Given an address in the form 'addr:port' separate the addr and port
553 Returns a two-tuple of strings, (addr, port). If no port is present in the
554 string, then the port section of the tuple is None.
557 socket.inet_pton(socket.AF_INET6, addr)
558 if not addr.startswith('['):
561 # If brackets are present, there may be a port as well
562 match = re.match('\[(.*\)]:(\d+)', addr)
564 return (match.group(1), match.group(2))
570 # IPv4 address or hostname
571 host, sep, port = addr.rpartition(':')
573 if not sep and not port:
579 def create_udp(sip, pjsip, nmapped):
581 Creates a 'transport-udp' section in the pjsip.conf file based
582 on the following settings from sip.conf:
584 bindaddr (or udpbindaddr)
586 externaddr (or externip)
590 bind = sip.multi_get('general', ['udpbindaddr', 'bindaddr'])[0]
591 bind = build_host(sip, bind, 'general', 'bindport')
594 extern_addr = sip.multi_get('general', ['externaddr', 'externip',
596 host, port = split_hostport(extern_addr)
597 set_value('external_signaling_address', host, 'transport-udp', pjsip,
598 nmapped, 'transport')
600 set_value('external_signaling_port', port, 'transport-udp', pjsip,
601 nmapped, 'transport')
605 set_value('protocol', 'udp', 'transport-udp', pjsip, nmapped, 'transport')
606 set_value('bind', bind, 'transport-udp', pjsip, nmapped, 'transport')
607 set_transport_common('transport-udp', pjsip, nmapped)
610 def create_tcp(sip, pjsip, nmapped):
612 Creates a 'transport-tcp' section in the pjsip.conf file based
613 on the following settings from sip.conf:
621 enabled = sip.get('general', 'tcpenable')[0]
623 # No value means disabled by default. No need for a tranport
630 bind = sip.get('general', 'tcpbindaddr')[0]
631 bind = build_host(sip, bind, 'general', 'bindport')
633 # No tcpbindaddr means to default to the udpbindaddr
634 bind = pjsip.get('transport-udp', 'bind')[0]
637 extern_addr = sip.multi_get('general', ['externaddr', 'externip',
639 host, port = split_hostport(extern_addr)
641 tcpport = sip.get('general', 'externtcpport')[0]
644 set_value('external_signaling_address', host, 'transport-tcp', pjsip,
645 nmapped, 'transport')
647 set_value('external_signaling_port', tcpport, 'transport-tcp',
648 pjsip, nmapped, 'transport')
652 set_value('protocol', 'tcp', 'transport-tcp', pjsip, nmapped, 'transport')
653 set_value('bind', bind, 'transport-tcp', pjsip, nmapped, 'transport')
654 set_transport_common('transport-tcp', pjsip, nmapped)
657 def set_tls_bindaddr(val, pjsip, nmapped):
659 Creates the TCP bind address. This has two possible methods of
661 Use the 'tlsbindaddr' option from sip.conf directly if it has both
662 an address and port. If no port is present, use 5061
663 If there is no 'tlsbindaddr' option present in sip.conf, use the
664 previously-established UDP bind address and port 5061
667 bind = sip.get('general', 'tlsbindaddr')[0]
670 # No tlsbindaddr means to default to the bindaddr but with standard TLS
672 bind = pjsip.get('transport-udp', 'bind')[0]
675 matchv4 = re.match('\d+\.\d+\.\d+\.\d+:\d+', bind)
676 matchv6 = re.match('\[.*\]:d+', bind)
677 if matchv4 or matchv6:
679 # They provided a port. We'll just use it.
680 set_value('bind', bind, 'transport-tls', pjsip, nmapped,
684 # Need to strip the port from the UDP address
685 index = bind.rfind(':')
688 # Reaching this point means either there was no port provided or we
689 # stripped the port off. We need to add on the default 5061 port
693 set_value('bind', bind, 'transport-tls', pjsip, nmapped, 'transport')
696 def set_tls_private_key(val, pjsip, nmapped):
697 """Sets privkey_file based on sip.conf tlsprivatekey or sslprivatekey"""
698 set_value('priv_key_file', val, 'transport-tls', pjsip, nmapped,
702 def set_tls_cipher(val, pjsip, nmapped):
703 """Sets cipher based on sip.conf tlscipher or sslcipher"""
704 set_value('cipher', val, 'transport-tls', pjsip, nmapped, 'transport')
707 def set_tls_cafile(val, pjsip, nmapped):
708 """Sets ca_list_file based on sip.conf tlscafile"""
709 set_value('ca_list_file', val, 'transport-tls', pjsip, nmapped,
713 def set_tls_verifyclient(val, pjsip, nmapped):
714 """Sets verify_client based on sip.conf tlsverifyclient"""
715 set_value('verify_client', val, 'transport-tls', pjsip, nmapped,
719 def set_tls_verifyserver(val, pjsip, nmapped):
720 """Sets verify_server based on sip.conf tlsdontverifyserver"""
723 set_value('verify_server', 'yes', 'transport-tls', pjsip, nmapped,
726 set_value('verify_server', 'no', 'transport-tls', pjsip, nmapped,
730 def set_tls_method(val, pjsip, nmapped):
731 """Sets method based on sip.conf tlsclientmethod or sslclientmethod"""
732 set_value('method', val, 'transport-tls', pjsip, nmapped, 'transport')
735 def create_tls(sip, pjsip, nmapped):
737 Creates a 'transport-tls' section in pjsip.conf based on the following
738 settings from sip.conf:
740 tlsenable (or sslenable)
741 tlsbindaddr (or sslbindaddr)
742 tlsprivatekey (or sslprivatekey)
743 tlscipher (or sslcipher)
745 tlscapath (or tlscadir)
746 tlscertfile (or sslcert or tlscert)
749 tlsclientmethod (or sslclientmethod)
753 (['tlsbindaddr', 'sslbindaddr'], set_tls_bindaddr),
754 (['tlsprivatekey', 'sslprivatekey'], set_tls_private_key),
755 (['tlscipher', 'sslcipher'], set_tls_cipher),
756 (['tlscafile'], set_tls_cafile),
757 (['tlsverifyclient'], set_tls_verifyclient),
758 (['tlsdontverifyserver'], set_tls_verifyserver),
759 (['tlsclientmethod', 'sslclientmethod'], set_tls_method)
763 enabled = sip.multi_get('general', ['tlsenable', 'sslenable'])[0]
765 # Not enabled. Don't create a transport
771 set_value('protocol', 'tls', 'transport-tls', pjsip, nmapped, 'transport')
775 i[1](sip.multi_get('general', i[0])[0], pjsip, nmapped)
779 set_transport_common('transport-tls', pjsip, nmapped)
781 extern_addr = sip.multi_get('general', ['externaddr', 'externip',
783 host, port = split_hostport(extern_addr)
785 tlsport = sip.get('general', 'externtlsport')[0]
788 set_value('external_signaling_address', host, 'transport-tls', pjsip,
789 nmapped, 'transport')
791 set_value('external_signaling_port', tlsport, 'transport-tls',
792 pjsip, nmapped, 'transport')
797 def map_transports(sip, pjsip, nmapped):
799 Finds options in sip.conf general section pertaining to
800 transport configuration and creates appropriate transport
801 configuration sections in pjsip.conf.
803 sip.conf only allows a single UDP transport, TCP transport,
804 and TLS transport. As such, the mapping into PJSIP can be made
805 consistent by defining three sections:
811 To accommodate the default behaviors in sip.conf, we'll need to
812 create the UDP transport first, followed by the TCP and TLS transports.
815 # First create a UDP transport. Even if no bind parameters were provided
816 # in sip.conf, chan_sip would always bind to UDP 0.0.0.0:5060
817 create_udp(sip, pjsip, nmapped)
819 # TCP settings may be dependent on UDP settings, so do it second.
820 create_tcp(sip, pjsip, nmapped)
821 create_tls(sip, pjsip, nmapped)
824 def map_auth(sip, pjsip, nmapped):
826 Creates auth sections based on entries in the authentication section of
827 sip.conf. pjsip.conf section names consist of "auth_" followed by the name
831 auths = sip.get('authentication', 'auth')
836 creds, at, realm = i.partition('@')
837 if not at and not realm:
840 user, colon, secret = creds.partition(':')
842 user, sharp, md5 = creds.partition('#')
846 section = "auth_" + realm
848 set_value('realm', realm, section, pjsip, nmapped, 'auth')
849 set_value('username', user, section, pjsip, nmapped, 'auth')
851 set_value('password', secret, section, pjsip, nmapped, 'auth')
853 set_value('md5_cred', md5, section, pjsip, nmapped, 'auth')
854 set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
859 Class for parsing and storing information in a register line in sip.conf.
861 def __init__(self, line, retry_interval, max_attempts, outbound_proxy):
862 self.retry_interval = retry_interval
863 self.max_attempts = max_attempts
864 self.outbound_proxy = outbound_proxy
867 def parse(self, line):
869 Initial parsing routine for register lines in sip.conf.
871 This splits the line into the part before the host, and the part
872 after the '@' symbol. These two parts are then passed to their
877 # [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry]
879 prehost, at, host_part = line.rpartition('@')
883 self.parse_host_part(host_part)
884 self.parse_user_part(prehost)
886 def parse_host_part(self, host_part):
888 Parsing routine for the part after the final '@' in a register line.
889 The strategy is to use partition calls to peel away the data starting
890 from the right and working to the left.
892 pre_expiry, sep, expiry = host_part.partition('~')
893 pre_extension, sep, self.extension = pre_expiry.partition('/')
894 self.host, sep, self.port = pre_extension.partition(':')
896 self.expiry = expiry if expiry else '120'
898 def parse_user_part(self, user_part):
900 Parsing routine for the part before the final '@' in a register line.
901 The only mandatory part of this line is the user portion. The strategy
902 here is to start by using partition calls to remove everything to
903 the right of the user, then finish by using rpartition calls to remove
904 everything to the left of the user.
906 colons = user_part.count(':')
908 # :domainport:secret:authuser
909 pre_auth, sep, port_auth = user_part.partition(':')
910 self.domainport, sep, auth = port_auth.partition(':')
911 self.secret, sep, self.authuser = auth.partition(':')
914 pre_auth, sep, auth = user_part.partition(':')
915 self.secret, sep, self.authuser = auth.partition(':')
918 pre_auth, sep, self.secret = user_part.partition(':')
920 # No port, secret, or authuser
926 pre_domain, sep, self.domain = pre_auth.partition('@')
927 self.peer, sep, post_peer = pre_domain.rpartition('?')
928 transport, sep, self.user = post_peer.rpartition('://')
930 self.protocol = transport if transport else 'udp'
932 def write(self, pjsip, nmapped):
934 Write parsed registration data into a section in pjsip.conf
936 Most of the data in self will get written to a registration section.
937 However, there will also need to be an auth section created if a
938 secret or authuser is present.
940 General mapping of values:
941 A combination of self.host and self.port is server_uri
942 A combination of self.user, self.domain, and self.domainport is
944 self.expiry is expiration
945 self.extension is contact_user
946 self.protocol will map to one of the mapped transports
947 self.secret and self.authuser will result in a new auth section, and
948 outbound_auth will point to that section.
949 XXX self.peer really doesn't map to anything :(
952 section = 'reg_' + self.host
954 set_value('retry_interval', self.retry_interval, section, pjsip,
955 nmapped, 'registration')
956 set_value('max_retries', self.max_attempts, section, pjsip, nmapped,
959 set_value('contact_user', self.extension, section, pjsip, nmapped,
962 set_value('expiration', self.expiry, section, pjsip, nmapped,
965 if self.protocol == 'udp':
966 set_value('transport', 'transport-udp', section, pjsip, nmapped,
968 elif self.protocol == 'tcp':
969 set_value('transport', 'transport-tcp', section, pjsip, nmapped,
971 elif self.protocol == 'tls':
972 set_value('transport', 'transport-tls', section, pjsip, nmapped,
975 auth_section = 'auth_reg_' + self.host
978 set_value('password', self.secret, auth_section, pjsip, nmapped,
980 set_value('username', self.authuser or self.user, auth_section,
981 pjsip, nmapped, 'auth')
982 set_value('outbound_auth', auth_section, section, pjsip, nmapped,
985 client_uri = "sip:%s@" % self.user
987 client_uri += self.domain
989 client_uri += self.host
992 client_uri += ":" + self.domainport
994 client_uri += ":" + self.port
996 set_value('client_uri', client_uri, section, pjsip, nmapped,
999 server_uri = "sip:%s" % self.host
1001 server_uri += ":" + self.port
1003 set_value('server_uri', server_uri, section, pjsip, nmapped,
1006 if self.outbound_proxy:
1007 set_value('outboundproxy', self.outbound_proxy, section, pjsip,
1008 nmapped, 'registartion')
1011 def map_registrations(sip, pjsip, nmapped):
1013 Gathers all necessary outbound registration data in sip.conf and creates
1014 corresponding registration sections in pjsip.conf
1017 regs = sip.get('general', 'register')
1022 retry_interval = sip.get('general', 'registertimeout')[0]
1024 retry_interval = '20'
1027 max_attempts = sip.get('general', 'registerattempts')[0]
1032 outbound_proxy = sip.get('general', 'outboundproxy')[0]
1037 reg = Registration(i, retry_interval, max_attempts, outbound_proxy)
1038 reg.write(pjsip, nmapped)
1041 def map_peer(sip, section, pjsip, nmapped):
1043 Map the options from a peer section in sip.conf into the appropriate
1044 sections in pjsip.conf
1048 # coming from sip.conf the values should mostly be a list with a
1049 # single value. In the few cases that they are not a specialized
1050 # function (see merge_value) is used to retrieve the values.
1051 i[1](i[0], sip.get(section, i[0])[0], section, pjsip, nmapped)
1053 pass # key not found in sip.conf
1056 def find_non_mapped(sections, nmapped):
1058 Determine sip.conf options that were not properly mapped to pjsip.conf
1061 for section, sect in sections.iteritems():
1063 # since we are pulling from sip.conf this should always
1064 # be a single value list
1066 # loop through the section and store any values that were not
1068 for key in sect.keys(True):
1073 nmapped(section, key, sect[key])
1078 def convert(sip, filename, non_mappings, include):
1080 Entry point for configuration file conversion. This
1081 function will create a pjsip.conf object and begin to
1082 map specific sections from sip.conf into it.
1083 Returns the new pjsip.conf object once completed
1085 pjsip = astconfigparser.MultiOrderedConfigParser()
1086 non_mappings[filename] = astdicts.MultiOrderedDict()
1087 nmapped = non_mapped(non_mappings[filename])
1089 # Don't duplicate transport and registration configs
1090 map_transports(sip, pjsip, nmapped)
1091 map_registrations(sip, pjsip, nmapped)
1092 map_auth(sip, pjsip, nmapped)
1093 for section in sip.sections():
1094 if section == 'authentication':
1097 map_peer(sip, section, pjsip, nmapped)
1099 find_non_mapped(sip.defaults(), nmapped)
1100 find_non_mapped(sip.sections(), nmapped)
1102 for key, val in sip.includes().iteritems():
1103 pjsip.add_include(PREFIX + key, convert(val, PREFIX + key,
1104 non_mappings, True)[0])
1105 return pjsip, non_mappings
1108 def write_pjsip(filename, pjsip, non_mappings):
1110 Write pjsip.conf file to disk
1113 with open(filename, 'wt') as fp:
1115 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
1116 fp.write('Non mapped elements start\n')
1117 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n')
1118 astconfigparser.write_dicts(fp, non_mappings[filename])
1119 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
1120 fp.write('Non mapped elements end\n')
1121 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
1123 # write out include file(s)
1127 print "Could not open file ", filename, " for writing"
1129 ###############################################################################
1134 Parse command line options and apply them. If invalid input is given,
1135 print usage information
1138 usage = "usage: %prog [options] [input-file [output-file]]\n\n" \
1139 "input-file defaults to 'sip.conf'\n" \
1140 "output-file defaults to 'pjsip.conf'"
1141 parser = optparse.OptionParser(usage=usage)
1142 parser.add_option('-p', '--prefix', dest='prefix', default=PREFIX,
1143 help='output prefix for include files')
1145 options, args = parser.parse_args()
1146 PREFIX = options.prefix
1148 sip_filename = args[0] if len(args) else 'sip.conf'
1149 pjsip_filename = args[1] if len(args) == 2 else 'pjsip.conf'
1151 return sip_filename, pjsip_filename
1153 if __name__ == "__main__":
1154 sip_filename, pjsip_filename = cli_options()
1155 # configuration parser for sip.conf
1156 sip = astconfigparser.MultiOrderedConfigParser()
1157 sip.read(sip_filename)
1158 pjsip, non_mappings = convert(sip, pjsip_filename, dict(), False)
1159 write_pjsip(pjsip_filename, pjsip, non_mappings)