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.
105 # available pjsip.conf values: rfc4733, inband, info, none
106 if val == 'inband' or val == 'info':
107 set_value(key, val, section, pjsip, nmapped)
108 elif val == 'rfc2833':
109 set_value(key, 'rfc4733', section, pjsip, nmapped)
111 nmapped(section, key, val + " ; did not fully map - set to none")
112 set_value(key, 'none', section, pjsip, nmapped)
115 def from_nat(key, val, section, pjsip, nmapped):
116 """Sets values from nat into the appropriate pjsip.conf options."""
117 # nat from sip.conf can be comma separated list of values:
118 # yes/no, [auto_]force_rport, [auto_]comedia
120 set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
121 set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
123 set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
124 if 'force_rport' in val:
125 set_value('force_rport', 'yes', section, pjsip, nmapped)
126 set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
129 def set_timers(key, val, section, pjsip, nmapped):
131 Sets the timers in pjsip.conf from the session-timers option
134 # pjsip.conf values can be yes/no, required, always
135 if val == 'originate':
136 set_value('timers', 'always', section, pjsip, nmapped)
137 elif val == 'accept':
138 set_value('timers', 'required', section, pjsip, nmapped)
140 set_value('timers', 'no', section, pjsip, nmapped)
142 set_value('timers', 'yes', section, pjsip, nmapped)
145 def set_direct_media(key, val, section, pjsip, nmapped):
147 Maps values from the sip.conf comma separated direct_media option
148 into pjsip.conf direct_media options.
151 set_value('direct_media', 'yes', section, pjsip, nmapped)
153 set_value('direct_media_method', 'update', section, pjsip, nmapped)
154 if 'outgoing' in val:
155 set_value('directed_media_glare_mitigation', 'outgoing', section,
158 set_value('disable_directed_media_on_nat', 'yes', section, pjsip,
161 set_value('direct_media', 'no', section, pjsip, nmapped)
164 def from_sendrpid(key, val, section, pjsip, nmapped):
165 """Sets the send_rpid/pai values in pjsip.conf."""
166 if val == 'yes' or val == 'rpid':
167 set_value('send_rpid', 'yes', section, pjsip, nmapped)
169 set_value('send_pai', 'yes', section, pjsip, nmapped)
172 def set_media_encryption(key, val, section, pjsip, nmapped):
173 """Sets the media_encryption value in pjsip.conf"""
175 dtls = sip.get(section, 'dtlsenable')[0]
177 # If DTLS is enabled, then that overrides SDES encryption.
183 set_value('media_encryption', 'sdes', section, pjsip, nmapped)
186 def from_recordfeature(key, val, section, pjsip, nmapped):
188 If record on/off feature is set to automixmon then set
189 one_touch_recording, otherwise it can't be mapped.
191 set_value('one_touch_recording', 'yes', section, pjsip, nmapped)
192 set_value(key, val, section, pjsip, nmapped)
195 def from_progressinband(key, val, section, pjsip, nmapped):
196 """Sets the inband_progress value in pjsip.conf"""
197 # progressinband can = yes/no/never
200 set_value('inband_progress', val, section, pjsip, nmapped)
203 def build_host(config, host, section, port_key):
205 Returns a string composed of a host:port. This assumes that the host
206 may have a port as part of the initial value. The port_key is only used
207 if the host does not already have a port set on it.
208 Throws a LookupError if the key does not exist
213 socket.inet_pton(socket.AF_INET6, host)
214 if not host.startswith('['):
215 # SIP URI will need brackets.
216 host = '[' + host + ']'
218 # If brackets are present, there may be a port as well
219 port = re.match('\[.*\]:(\d+)', host)
221 # No biggie. It's just not an IPv6 address
222 port = re.match('.*:(\d+)', host)
228 port = config.get(section, port_key)[0]
236 def from_host(key, val, section, pjsip, nmapped):
238 Sets contact info in an AOR section in pjsip.conf using 'host'
239 and 'port' data from sip.conf
241 # all aors have the same name as the endpoint so makes
242 # it easy to set endpoint's 'aors' value
243 set_value('aors', section, section, pjsip, nmapped)
245 # Easy case. Just set the max_contacts on the aor and we're done
246 set_value('max_contacts', 1, section, pjsip, nmapped, 'aor')
251 # More difficult case. The host will be either a hostname or
252 # IP address and may or may not have a port specified. pjsip.conf
253 # expects the contact to be a SIP URI.
258 user = sip.multi_get(section, ['defaultuser', 'username'])[0]
261 # It's fine if there's no user name
264 result += build_host(sip, val, section, 'port')
266 set_value('contact', result, section, pjsip, nmapped, 'aor')
269 def from_mailbox(key, val, section, pjsip, nmapped):
271 Determines whether a mailbox configured in sip.conf should map to
272 an endpoint or aor in pjsip.conf. If subscribemwi is true, then the
273 mailboxes are set on an aor. Otherwise the mailboxes are set on the
278 subscribemwi = sip.get(section, 'subscribemwi')[0]
280 # No subscribemwi option means default it to 'no'
283 set_value('mailboxes', val, section, pjsip, nmapped, 'aor'
284 if subscribemwi == 'yes' else 'endpoint')
287 def setup_auth(key, val, section, pjsip, nmapped):
289 Sets up authentication information for a specific endpoint based on the
290 'secret' setting on a peer in sip.conf
292 set_value('username', section, section, pjsip, nmapped, 'auth')
293 # In chan_sip, if a secret and an md5secret are both specified on a peer,
294 # then in practice, only the md5secret is used. If both are encountered
295 # then we build an auth section that has both an md5_cred and password.
296 # However, the auth_type will indicate to authenticators to use the
297 # md5_cred, so like with sip.conf, the password will be there but have
300 set_value('password', val, section, pjsip, nmapped, 'auth')
302 set_value('md5_cred', val, section, pjsip, nmapped, 'auth')
303 set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
307 auths = sip.get('authentication', 'auth')
309 user, at, realm = i.partition('@')
314 realm_str = ','.join(realms)
316 set_value('auth', section, section, pjsip, nmapped)
317 set_value('outbound_auth', realm_str, section, pjsip, nmapped)
320 def setup_ident(key, val, section, pjsip, nmapped):
322 Examines the 'type' field for a sip.conf peer and creates an identify
323 section if the type is either 'peer' or 'friend'. The identify section uses
324 either the host or defaultip field of the sip.conf peer.
326 if val != 'peer' and val != 'friend':
330 ip = sip.get(section, 'host')[0]
336 ip = sip.get(section, 'defaultip')[0]
340 set_value('endpoint', section, section, pjsip, nmapped, 'identify')
341 set_value('match', ip, section, pjsip, nmapped, 'identify')
344 def from_encryption_taglen(key, val, section, pjsip, nmapped):
345 """Sets the srtp_tag32 option based on sip.conf encryption_taglen"""
347 set_value('srtp_tag_32', 'yes', section, pjsip, nmapped)
350 def from_dtlsenable(key, val, section, pjsip, nmapped):
351 """Optionally sets media_encryption=dtls based on sip.conf dtlsenable"""
353 set_value('media_encryption', 'dtls', section, pjsip, nmapped)
355 ###############################################################################
357 # options in pjsip.conf on an endpoint that have no sip.conf equivalent:
358 # type, rtp_ipv6, 100rel, trust_id_outbound, aggregate_mwi,
359 # connected_line_method
361 # known sip.conf peer keys that can be mapped to a pjsip.conf section/key
363 # sip.conf option mapping function pjsip.conf option(s)
364 ###########################################################################
365 ['context', set_value],
366 ['dtmfmode', set_dtmfmode],
367 ['disallow', merge_value],
368 ['allow', merge_value],
369 ['nat', from_nat], # rtp_symmetric, force_rport,
371 ['icesupport', set_value('ice_support')],
372 ['autoframing', set_value('use_ptime')],
373 ['outboundproxy', set_value('outbound_proxy')],
374 ['mohsuggest', set_value],
375 ['session-timers', set_timers], # timers
376 ['session-minse', set_value('timers_min_se')],
377 ['session-expires', set_value('timers_sess_expires')],
378 ['externip', set_value('external_media_address')],
379 ['externhost', set_value('external_media_address')],
381 ['directmedia', set_direct_media], # direct_media
382 # direct_media_method
383 # directed_media_glare_mitigation
384 # disable_directed_media_on_nat
385 ['callerid', set_value], # callerid
386 ['callingpres', set_value('callerid_privacy')],
387 ['cid_tag', set_value('callerid_tag')],
388 ['trustpid', set_value('trust_id_inbound')],
389 ['sendrpid', from_sendrpid], # send_pai, send_rpid
390 ['send_diversion', set_value],
391 ['encrpytion', set_media_encryption],
392 ['avpf', set_value('use_avpf')],
393 ['recordonfeature', from_recordfeature], # automixon
394 ['recordofffeature', from_recordfeature], # automixon
395 ['progressinband', from_progressinband], # in_band_progress
396 ['callgroup', set_value],
397 ['pickupgroup', set_value],
398 ['namedcallgroup', set_value],
399 ['namedpickupgroup', set_value],
400 ['allowtransfer', set_value],
401 ['fromuser', set_value],
402 ['fromdomain', set_value],
403 ['mwifrom', set_value('mwifromuser')],
404 ['tos_audio', set_value],
405 ['tos_video', set_value],
406 ['cos_audio', set_value],
407 ['cos_video', set_value],
408 ['sdpowner', set_value],
409 ['sdpsession', set_value],
410 ['tonezone', set_value],
411 ['language', set_value],
412 ['allowsubscribe', set_value],
413 ['subminexpiry', set_value],
414 ['rtp_engine', set_value('rtpengine')],
415 ['mailbox', from_mailbox],
416 ['busylevel', set_value('devicestate_busy_at')],
417 ['secret', setup_auth],
418 ['md5secret', setup_auth],
419 ['type', setup_ident],
420 ['dtlsenable', from_dtlsenable],
421 ['dtlsverify', set_value],
422 ['dtlsrekey', set_value],
423 ['dtlscertfile', set_value],
424 ['dtlsprivatekey', set_value],
425 ['dtlscipher', set_value],
426 ['dtlscafile', set_value],
427 ['dtlscapath', set_value],
428 ['dtlssetup', set_value],
429 ['encryption_taglen', from_encryption_taglen],
431 ############################ maps to an aor ###################################
433 ['host', from_host], # contact, max_contacts
434 ['qualifyfreq', set_value('qualify_frequency', type='aor')],
436 ############################# maps to auth#####################################
444 ######################### maps to acl/security ################################
446 ['permit', merge_value(type='acl', section_to='acl')],
447 ['deny', merge_value(type='acl', section_to='acl')],
448 ['acl', merge_value(type='acl', section_to='acl')],
449 ['contactpermit', merge_value(type='acl', section_to='acl')],
450 ['contactdeny', merge_value(type='acl', section_to='acl')],
451 ['contactacl', merge_value(type='acl', section_to='acl')],
453 ########################### maps to transport #################################
462 # external_signaling_address - externip & externhost
463 # external_signaling_port
464 # external_media_address
468 # require_client_cert
472 ######################### maps to domain_alias ################################
473 # type = domain_alias
475 ######################### maps to registration ################################
476 # type = registration
485 # auth_rejection_permanent
487 ########################### maps to identify ##################################
494 def add_localnet(section, pjsip, nmapped):
496 Adds localnet values from sip.conf's general section to a transport in
497 pjsip.conf. Ideally, we would have just created a template with the
498 localnet sections, but because this is a script, it's not hard to add
499 the same thing on to every transport.
502 merge_value('localnet', sip.get('general', 'localnet')[0], 'general',
503 pjsip, nmapped, 'transport', section)
505 # No localnet options configured. No biggie!
509 def set_transport_common(section, pjsip, nmapped):
511 sip.conf has several global settings that in pjsip.conf apply to individual
512 transports. This function adds these global settings to each individual
515 The settings included are:
522 merge_value('localnet', sip.get('general', 'localnet')[0], 'general',
523 pjsip, nmapped, 'transport', section)
525 # No localnet options configured. Move on.
529 set_value('tos', sip.get('general', 'sip_tos')[0], 'general', pjsip,
530 nmapped, 'transport', section)
535 set_value('cos', sip.get('general', 'sip_cos')[0], 'general', pjsip,
536 nmapped, 'transport', section)
541 def split_hostport(addr):
543 Given an address in the form 'addr:port' separate the addr and port
545 Returns a two-tuple of strings, (addr, port). If no port is present in the
546 string, then the port section of the tuple is None.
549 socket.inet_pton(socket.AF_INET6, addr)
550 if not addr.startswith('['):
553 # If brackets are present, there may be a port as well
554 match = re.match('\[(.*\)]:(\d+)', addr)
556 return (match.group(1), match.group(2))
562 # IPv4 address or hostname
563 host, sep, port = addr.rpartition(':')
565 if not sep and not port:
571 def create_udp(sip, pjsip, nmapped):
573 Creates a 'transport-udp' section in the pjsip.conf file based
574 on the following settings from sip.conf:
576 bindaddr (or udpbindaddr)
578 externaddr (or externip)
582 bind = sip.multi_get('general', ['udpbindaddr', 'bindaddr'])[0]
583 bind = build_host(sip, bind, 'general', 'bindport')
586 extern_addr = sip.multi_get('general', ['externaddr', 'externip',
588 host, port = split_hostport(extern_addr)
589 set_value('external_signaling_address', host, 'transport-udp', pjsip,
590 nmapped, 'transport')
592 set_value('external_signaling_port', port, 'transport-udp', pjsip,
593 nmapped, 'transport')
597 set_value('protocol', 'udp', 'transport-udp', pjsip, nmapped, 'transport')
598 set_value('bind', bind, 'transport-udp', pjsip, nmapped, 'transport')
599 set_transport_common('transport-udp', pjsip, nmapped)
602 def create_tcp(sip, pjsip, nmapped):
604 Creates a 'transport-tcp' section in the pjsip.conf file based
605 on the following settings from sip.conf:
613 enabled = sip.get('general', 'tcpenable')[0]
615 # No value means disabled by default. No need for a tranport
622 bind = sip.get('general', 'tcpbindaddr')[0]
623 bind = build_host(sip, bind, 'general', 'bindport')
625 # No tcpbindaddr means to default to the udpbindaddr
626 bind = pjsip.get('transport-udp', 'bind')[0]
629 extern_addr = sip.multi_get('general', ['externaddr', 'externip',
631 host, port = split_hostport(extern_addr)
633 tcpport = sip.get('general', 'externtcpport')[0]
636 set_value('external_signaling_address', host, 'transport-tcp', pjsip,
637 nmapped, 'transport')
639 set_value('external_signaling_port', tcpport, 'transport-tcp',
640 pjsip, nmapped, 'transport')
644 set_value('protocol', 'tcp', 'transport-tcp', pjsip, nmapped, 'transport')
645 set_value('bind', bind, 'transport-tcp', pjsip, nmapped, 'transport')
646 set_transport_common('transport-tcp', pjsip, nmapped)
649 def set_tls_bindaddr(val, pjsip, nmapped):
651 Creates the TCP bind address. This has two possible methods of
653 Use the 'tlsbindaddr' option from sip.conf directly if it has both
654 an address and port. If no port is present, use 5061
655 If there is no 'tlsbindaddr' option present in sip.conf, use the
656 previously-established UDP bind address and port 5061
659 bind = sip.get('general', 'tlsbindaddr')[0]
662 # No tlsbindaddr means to default to the bindaddr but with standard TLS
664 bind = pjsip.get('transport-udp', 'bind')[0]
667 matchv4 = re.match('\d+\.\d+\.\d+\.\d+:\d+', bind)
668 matchv6 = re.match('\[.*\]:d+', bind)
669 if matchv4 or matchv6:
671 # They provided a port. We'll just use it.
672 set_value('bind', bind, 'transport-tls', pjsip, nmapped,
676 # Need to strip the port from the UDP address
677 index = bind.rfind(':')
680 # Reaching this point means either there was no port provided or we
681 # stripped the port off. We need to add on the default 5061 port
685 set_value('bind', bind, 'transport-tls', pjsip, nmapped, 'transport')
688 def set_tls_private_key(val, pjsip, nmapped):
689 """Sets privkey_file based on sip.conf tlsprivatekey or sslprivatekey"""
690 set_value('privkey_file', val, 'transport-tls', pjsip, nmapped,
694 def set_tls_cipher(val, pjsip, nmapped):
695 """Sets cipher based on sip.conf tlscipher or sslcipher"""
696 set_value('cipher', val, 'transport-tls', pjsip, nmapped, 'transport')
699 def set_tls_cafile(val, pjsip, nmapped):
700 """Sets ca_list_file based on sip.conf tlscafile"""
701 set_value('ca_list_file', val, 'transport-tls', pjsip, nmapped,
705 def set_tls_verifyclient(val, pjsip, nmapped):
706 """Sets verify_client based on sip.conf tlsverifyclient"""
707 set_value('verify_client', val, 'transport-tls', pjsip, nmapped,
711 def set_tls_verifyserver(val, pjsip, nmapped):
712 """Sets verify_server based on sip.conf tlsdontverifyserver"""
715 set_value('verify_server', 'yes', 'transport-tls', pjsip, nmapped,
718 set_value('verify_server', 'no', 'transport-tls', pjsip, nmapped,
722 def set_tls_method(val, pjsip, nmapped):
723 """Sets method based on sip.conf tlsclientmethod or sslclientmethod"""
724 set_value('method', val, 'transport-tls', pjsip, nmapped, 'transport')
727 def create_tls(sip, pjsip, nmapped):
729 Creates a 'transport-tls' section in pjsip.conf based on the following
730 settings from sip.conf:
732 tlsenable (or sslenable)
733 tlsbindaddr (or sslbindaddr)
734 tlsprivatekey (or sslprivatekey)
735 tlscipher (or sslcipher)
737 tlscapath (or tlscadir)
738 tlscertfile (or sslcert or tlscert)
741 tlsclientmethod (or sslclientmethod)
745 (['tlsbindaddr', 'sslbindaddr'], set_tls_bindaddr),
746 (['tlsprivatekey', 'sslprivatekey'], set_tls_private_key),
747 (['tlscipher', 'sslcipher'], set_tls_cipher),
748 (['tlscafile'], set_tls_cafile),
749 (['tlsverifyclient'], set_tls_verifyclient),
750 (['tlsdontverifyserver'], set_tls_verifyserver),
751 (['tlsclientmethod', 'sslclientmethod'], set_tls_method)
755 enabled = sip.multi_get('general', ['tlsenable', 'sslenable'])[0]
757 # Not enabled. Don't create a transport
763 set_value('protocol', 'tls', 'transport-tls', pjsip, nmapped, 'transport')
767 i[1](sip.multi_get('general', i[0])[0], pjsip, nmapped)
771 set_transport_common('transport-tls', pjsip, nmapped)
773 extern_addr = sip.multi_get('general', ['externaddr', 'externip',
775 host, port = split_hostport(extern_addr)
777 tlsport = sip.get('general', 'externtlsport')[0]
780 set_value('external_signaling_address', host, 'transport-tls', pjsip,
781 nmapped, 'transport')
783 set_value('external_signaling_port', tlsport, 'transport-tls',
784 pjsip, nmapped, 'transport')
789 def map_transports(sip, pjsip, nmapped):
791 Finds options in sip.conf general section pertaining to
792 transport configuration and creates appropriate transport
793 configuration sections in pjsip.conf.
795 sip.conf only allows a single UDP transport, TCP transport,
796 and TLS transport. As such, the mapping into PJSIP can be made
797 consistent by defining three sections:
803 To accommodate the default behaviors in sip.conf, we'll need to
804 create the UDP transport first, followed by the TCP and TLS transports.
807 # First create a UDP transport. Even if no bind parameters were provided
808 # in sip.conf, chan_sip would always bind to UDP 0.0.0.0:5060
809 create_udp(sip, pjsip, nmapped)
811 # TCP settings may be dependent on UDP settings, so do it second.
812 create_tcp(sip, pjsip, nmapped)
813 create_tls(sip, pjsip, nmapped)
816 def map_auth(sip, pjsip, nmapped):
818 Creates auth sections based on entries in the authentication section of
819 sip.conf. pjsip.conf section names consist of "auth_" followed by the name
823 auths = sip.get('authentication', 'auth')
828 creds, at, realm = i.partition('@')
829 if not at and not realm:
832 user, colon, secret = creds.partition(':')
834 user, sharp, md5 = creds.partition('#')
838 section = "auth_" + realm
840 set_value('realm', realm, section, pjsip, nmapped, 'auth')
841 set_value('username', user, section, pjsip, nmapped, 'auth')
843 set_value('password', secret, section, pjsip, nmapped, 'auth')
845 set_value('md5_cred', md5, section, pjsip, nmapped, 'auth')
846 set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
851 Class for parsing and storing information in a register line in sip.conf.
853 def __init__(self, line, retry_interval, max_attempts, outbound_proxy):
854 self.retry_interval = retry_interval
855 self.max_attempts = max_attempts
856 self.outbound_proxy = outbound_proxy
859 def parse(self, line):
861 Initial parsing routine for register lines in sip.conf.
863 This splits the line into the part before the host, and the part
864 after the '@' symbol. These two parts are then passed to their
869 # [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry]
871 prehost, at, host_part = line.rpartition('@')
875 self.parse_host_part(host_part)
876 self.parse_user_part(prehost)
878 def parse_host_part(self, host_part):
880 Parsing routine for the part after the final '@' in a register line.
881 The strategy is to use partition calls to peel away the data starting
882 from the right and working to the left.
884 pre_expiry, sep, expiry = host_part.partition('~')
885 pre_extension, sep, self.extension = pre_expiry.partition('/')
886 self.host, sep, self.port = pre_extension.partition(':')
888 self.expiry = expiry if expiry else '120'
890 def parse_user_part(self, user_part):
892 Parsing routine for the part before the final '@' in a register line.
893 The only mandatory part of this line is the user portion. The strategy
894 here is to start by using partition calls to remove everything to
895 the right of the user, then finish by using rpartition calls to remove
896 everything to the left of the user.
898 colons = user_part.count(':')
900 # :domainport:secret:authuser
901 pre_auth, sep, port_auth = user_part.partition(':')
902 self.domainport, sep, auth = port_auth.partition(':')
903 self.secret, sep, self.authuser = auth.partition(':')
906 pre_auth, sep, auth = user_part.partition(':')
907 self.secret, sep, self.authuser = auth.partition(':')
910 pre_auth, sep, self.secret = user_part.partition(':')
912 # No port, secret, or authuser
918 pre_domain, sep, self.domain = pre_auth.partition('@')
919 self.peer, sep, post_peer = pre_domain.rpartition('?')
920 transport, sep, self.user = post_peer.rpartition('://')
922 self.protocol = transport if transport else 'udp'
924 def write(self, pjsip, nmapped):
926 Write parsed registration data into a section in pjsip.conf
928 Most of the data in self will get written to a registration section.
929 However, there will also need to be an auth section created if a
930 secret or authuser is present.
932 General mapping of values:
933 A combination of self.host and self.port is server_uri
934 A combination of self.user, self.domain, and self.domainport is
936 self.expiry is expiration
937 self.extension is contact_user
938 self.protocol will map to one of the mapped transports
939 self.secret and self.authuser will result in a new auth section, and
940 outbound_auth will point to that section.
941 XXX self.peer really doesn't map to anything :(
944 section = 'reg_' + self.host
946 set_value('retry_interval', self.retry_interval, section, pjsip,
947 nmapped, 'registration')
948 set_value('max_retries', self.max_attempts, section, pjsip, nmapped,
951 set_value('contact_user', self.extension, section, pjsip, nmapped,
954 set_value('expiration', self.expiry, section, pjsip, nmapped,
957 if self.protocol == 'udp':
958 set_value('transport', 'transport-udp', section, pjsip, nmapped,
960 elif self.protocol == 'tcp':
961 set_value('transport', 'transport-tcp', section, pjsip, nmapped,
963 elif self.protocol == 'tls':
964 set_value('transport', 'transport-tls', section, pjsip, nmapped,
967 auth_section = 'auth_reg_' + self.host
970 set_value('password', self.secret, auth_section, pjsip, nmapped,
972 set_value('username', self.authuser or self.user, auth_section,
973 pjsip, nmapped, 'auth')
974 set_value('outbound_auth', auth_section, section, pjsip, nmapped,
977 client_uri = "sip:%s@" % self.user
979 client_uri += self.domain
981 client_uri += self.host
984 client_uri += ":" + self.domainport
986 client_uri += ":" + self.port
988 set_value('client_uri', client_uri, section, pjsip, nmapped,
991 server_uri = "sip:%s" % self.host
993 server_uri += ":" + self.port
995 set_value('server_uri', server_uri, section, pjsip, nmapped,
998 if self.outbound_proxy:
999 set_value('outboundproxy', self.outbound_proxy, section, pjsip,
1000 nmapped, 'registartion')
1003 def map_registrations(sip, pjsip, nmapped):
1005 Gathers all necessary outbound registration data in sip.conf and creates
1006 corresponding registration sections in pjsip.conf
1009 regs = sip.get('general', 'register')
1014 retry_interval = sip.get('general', 'registertimeout')[0]
1016 retry_interval = '20'
1019 max_attempts = sip.get('general', 'registerattempts')[0]
1024 outbound_proxy = sip.get('general', 'outboundproxy')[0]
1029 reg = Registration(i, retry_interval, max_attempts, outbound_proxy)
1030 reg.write(pjsip, nmapped)
1033 def map_peer(sip, section, pjsip, nmapped):
1035 Map the options from a peer section in sip.conf into the appropriate
1036 sections in pjsip.conf
1040 # coming from sip.conf the values should mostly be a list with a
1041 # single value. In the few cases that they are not a specialized
1042 # function (see merge_value) is used to retrieve the values.
1043 i[1](i[0], sip.get(section, i[0])[0], section, pjsip, nmapped)
1045 pass # key not found in sip.conf
1048 def find_non_mapped(sections, nmapped):
1050 Determine sip.conf options that were not properly mapped to pjsip.conf
1053 for section, sect in sections.iteritems():
1055 # since we are pulling from sip.conf this should always
1056 # be a single value list
1058 # loop through the section and store any values that were not
1060 for key in sect.keys(True):
1065 nmapped(section, key, sect[key])
1070 def convert(sip, filename, non_mappings, include):
1072 Entry point for configuration file conversion. This
1073 function will create a pjsip.conf object and begin to
1074 map specific sections from sip.conf into it.
1075 Returns the new pjsip.conf object once completed
1077 pjsip = astconfigparser.MultiOrderedConfigParser()
1078 non_mappings[filename] = astdicts.MultiOrderedDict()
1079 nmapped = non_mapped(non_mappings[filename])
1081 # Don't duplicate transport and registration configs
1082 map_transports(sip, pjsip, nmapped)
1083 map_registrations(sip, pjsip, nmapped)
1084 map_auth(sip, pjsip, nmapped)
1085 for section in sip.sections():
1086 if section == 'authentication':
1089 map_peer(sip, section, pjsip, nmapped)
1091 find_non_mapped(sip.defaults(), nmapped)
1092 find_non_mapped(sip.sections(), nmapped)
1094 for key, val in sip.includes().iteritems():
1095 pjsip.add_include(PREFIX + key, convert(val, PREFIX + key,
1096 non_mappings, True)[0])
1097 return pjsip, non_mappings
1100 def write_pjsip(filename, pjsip, non_mappings):
1102 Write pjsip.conf file to disk
1105 with open(filename, 'wt') as fp:
1107 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
1108 fp.write('Non mapped elements start\n')
1109 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n')
1110 astconfigparser.write_dicts(fp, non_mappings[filename])
1111 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
1112 fp.write('Non mapped elements end\n')
1113 fp.write(';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n')
1115 # write out include file(s)
1119 print "Could not open file ", filename, " for writing"
1121 ###############################################################################
1126 Parse command line options and apply them. If invalid input is given,
1127 print usage information
1130 usage = "usage: %prog [options] [input-file [output-file]]\n\n" \
1131 "input-file defaults to 'sip.conf'\n" \
1132 "output-file defaults to 'pjsip.conf'"
1133 parser = optparse.OptionParser(usage=usage)
1134 parser.add_option('-p', '--prefix', dest='prefix', default=PREFIX,
1135 help='output prefix for include files')
1137 options, args = parser.parse_args()
1138 PREFIX = options.prefix
1140 sip_filename = args[0] if len(args) else 'sip.conf'
1141 pjsip_filename = args[1] if len(args) == 2 else 'pjsip.conf'
1143 return sip_filename, pjsip_filename
1145 if __name__ == "__main__":
1146 sip_filename, pjsip_filename = cli_options()
1147 # configuration parser for sip.conf
1148 sip = astconfigparser.MultiOrderedConfigParser()
1149 sip.read(sip_filename)
1150 pjsip, non_mappings = convert(sip, pjsip_filename, dict(), False)
1151 write_pjsip(pjsip_filename, pjsip, non_mappings)