1
|
#!/usr/bin/env python
|
2
|
|
3
|
from __future__ import print_function
|
4
|
|
5
|
import sys
|
6
|
import optparse
|
7
|
import socket
|
8
|
try:
|
9
|
from urllib.parse import urlparse
|
10
|
except ImportError:
|
11
|
from urlparse import urlparse # Python 2.7 required for Literal IPv6 Addresses
|
12
|
import xivo_confgen.generators.sip2pjsip.astdicts as astdicts
|
13
|
import xivo_confgen.generators.sip2pjsip.astconfigparser as astconfigparser
|
14
|
from xivo_dao.resources.endpoint_sip import dao as sip_dao
|
15
|
|
16
|
PREFIX = 'pjsip_'
|
17
|
QUIET = False
|
18
|
|
19
|
###############################################################################
|
20
|
### some utility functions
|
21
|
###############################################################################
|
22
|
|
23
|
|
24
|
def section_by_type(section, pjsip, type):
|
25
|
"""Finds a section based upon the given type, adding it if not found."""
|
26
|
def __find_dict(mdicts, key, val):
|
27
|
"""Given a list of multi-dicts, return the multi-dict that contains
|
28
|
the given key/value pair."""
|
29
|
|
30
|
def found(d):
|
31
|
return key in d and val in d[key]
|
32
|
|
33
|
try:
|
34
|
return [d for d in mdicts if found(d)][0]
|
35
|
except IndexError:
|
36
|
raise LookupError("Dictionary not located for key = %s, value = %s"
|
37
|
% (key, val))
|
38
|
|
39
|
try:
|
40
|
return __find_dict(pjsip.section(section), 'type', type)
|
41
|
except LookupError:
|
42
|
# section for type doesn't exist, so add
|
43
|
sect = pjsip.add_section(section)
|
44
|
sect['type'] = type
|
45
|
return sect
|
46
|
|
47
|
|
48
|
def ignore(key=None, val=None, section=None, pjsip=None,
|
49
|
nmapped=None, type='endpoint'):
|
50
|
"""Ignore a key and mark it as mapped"""
|
51
|
|
52
|
|
53
|
def set_value(key=None, val=None, section=None, pjsip=None,
|
54
|
nmapped=None, type='endpoint'):
|
55
|
"""Sets the key to the value within the section in pjsip.conf"""
|
56
|
def _set_value(k, v, s, r, n):
|
57
|
set_value(key if key else k, v, s, r, n, type)
|
58
|
|
59
|
# if no value or section return the set_value
|
60
|
# function with the enclosed key and type
|
61
|
if not val and not section:
|
62
|
return _set_value
|
63
|
|
64
|
# otherwise try to set the value
|
65
|
section_by_type(section, pjsip, type)[key] = \
|
66
|
val[0] if isinstance(val, list) else val
|
67
|
|
68
|
|
69
|
def merge_value(key=None, val=None, section=None, pjsip=None,
|
70
|
nmapped=None, type='endpoint', section_to=None,
|
71
|
key_to=None):
|
72
|
"""Merge values from the given section with those from the default."""
|
73
|
def _merge_value(k, v, s, r, n):
|
74
|
merge_value(key if key else k, v, s, r, n, type, section_to, key_to)
|
75
|
|
76
|
# if no value or section return the merge_value
|
77
|
# function with the enclosed key and type
|
78
|
if not val and not section:
|
79
|
return _merge_value
|
80
|
|
81
|
# should return a single value section list
|
82
|
try:
|
83
|
sect = sip.section(section)[0]
|
84
|
except LookupError:
|
85
|
sect = sip.default(section)[0]
|
86
|
# for each merged value add it to pjsip.conf
|
87
|
for i in sect.get_merged(key):
|
88
|
set_value(key_to if key_to else key, i,
|
89
|
section_to if section_to else section,
|
90
|
pjsip, nmapped, type)
|
91
|
|
92
|
def merge_codec_value(key=None, val=None, section=None, pjsip=None,
|
93
|
nmapped=None, type='endpoint', section_to=None,
|
94
|
key_to=None):
|
95
|
"""Merge values from allow/deny with those from the default. Special treatment for all"""
|
96
|
def _merge_codec_value(k, v, s, r, n):
|
97
|
merge_codec_value(key if key else k, v, s, r, n, type, section_to, key_to)
|
98
|
|
99
|
# if no value or section return the merge_codec_value
|
100
|
# function with the enclosed key and type
|
101
|
if not val and not section:
|
102
|
return _merge_codec_value
|
103
|
|
104
|
if key == 'allow':
|
105
|
try:
|
106
|
disallow = sip.get(section, 'disallow')[0]
|
107
|
if disallow == 'all':
|
108
|
#don't inherit
|
109
|
for i in sip.get(section, 'allow'):
|
110
|
set_value(key, i, section, pjsip, nmapped, type)
|
111
|
else:
|
112
|
merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
|
113
|
except LookupError:
|
114
|
print("lookup error", file=sys.stderr)
|
115
|
merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
|
116
|
return
|
117
|
elif key == 'disallow':
|
118
|
try:
|
119
|
allow = sip.get(section, 'allow')[0]
|
120
|
if allow == 'all':
|
121
|
#don't inherit
|
122
|
for i in sip.get(section, 'disallow'):
|
123
|
set_value(key, i, section, pjsip, nmapped, type)
|
124
|
else:
|
125
|
merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
|
126
|
except LookupError:
|
127
|
merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
|
128
|
return
|
129
|
else:
|
130
|
merge_value(key, val, section, pjsip, nmapped, type, section_to, key_to)
|
131
|
|
132
|
|
133
|
def non_mapped(nmapped):
|
134
|
"""Write non-mapped sip.conf values to the non-mapped object"""
|
135
|
def _non_mapped(section, key, val):
|
136
|
"""Writes a non-mapped value from sip.conf to the non-mapped object."""
|
137
|
if section not in nmapped:
|
138
|
nmapped[section] = astconfigparser.Section()
|
139
|
if isinstance(val, list):
|
140
|
for v in val:
|
141
|
# since coming from sip.conf we can assume
|
142
|
# single section lists
|
143
|
nmapped[section][0][key] = v
|
144
|
else:
|
145
|
nmapped[section][0][key] = val
|
146
|
return _non_mapped
|
147
|
|
148
|
###############################################################################
|
149
|
### mapping functions -
|
150
|
### define f(key, val, section) where key/val are the key/value pair to
|
151
|
### write to given section in pjsip.conf
|
152
|
###############################################################################
|
153
|
|
154
|
|
155
|
def set_dtmfmode(key, val, section, pjsip, nmapped):
|
156
|
"""
|
157
|
Sets the dtmfmode value. If value matches allowable option in pjsip
|
158
|
then map it, otherwise set it to none.
|
159
|
"""
|
160
|
key = 'dtmf_mode'
|
161
|
# available pjsip.conf values: rfc4733, inband, info, none
|
162
|
if val == 'inband' or val == 'info' or val == 'auto':
|
163
|
set_value(key, val, section, pjsip, nmapped)
|
164
|
elif val == 'rfc2833':
|
165
|
set_value(key, 'rfc4733', section, pjsip, nmapped)
|
166
|
else:
|
167
|
nmapped(section, key, val + " ; did not fully map - set to none")
|
168
|
set_value(key, 'none', section, pjsip, nmapped)
|
169
|
|
170
|
|
171
|
def set_qualify_frequency(key, val, section, pjsip, nmapped):
|
172
|
"""
|
173
|
Sets the qualify_frequency value only if qualify is activated in sip
|
174
|
"""
|
175
|
sip_qualify_value = sip.get(section, 'qualify')[0]
|
176
|
type = 'aor'
|
177
|
if sip_qualify_value.isdigit() or sip_qualify_value == 'yes':
|
178
|
set_value('qualify_frequency', val, section, pjsip, nmapped, type)
|
179
|
|
180
|
def set_qualify_timeout(key, val, section, pjsip, nmapped):
|
181
|
"""
|
182
|
Sets the qualify_timeout value only if qualify is activated in sip with a custom timeout (default 3 seconds)
|
183
|
"""
|
184
|
type = 'aor'
|
185
|
if val.isdigit():
|
186
|
val = str(float(val)/1000)
|
187
|
set_value('qualify_timeout', val, section, pjsip, nmapped, type)
|
188
|
|
189
|
def setup_udptl(section, pjsip, nmapped):
|
190
|
"""Sets values from udptl into the appropriate pjsip.conf options."""
|
191
|
try:
|
192
|
val = sip.get(section, 't38pt_udptl')[0]
|
193
|
except LookupError:
|
194
|
try:
|
195
|
val = sip.get('general', 't38pt_udptl')[0]
|
196
|
except LookupError:
|
197
|
return
|
198
|
|
199
|
ec = 'none'
|
200
|
if 'yes' in val:
|
201
|
set_value('t38_udptl', 'yes', section, pjsip, nmapped)
|
202
|
if 'no' in val:
|
203
|
set_value('t38_udptl', 'no', section, pjsip, nmapped)
|
204
|
if 'redundancy' in val:
|
205
|
ec = 'redundancy'
|
206
|
if 'fec' in val:
|
207
|
ec = 'fec'
|
208
|
set_value('t38_udptl_ec', ec, section, pjsip, nmapped)
|
209
|
|
210
|
def from_nat(key, val, section, pjsip, nmapped):
|
211
|
"""Sets values from nat into the appropriate pjsip.conf options."""
|
212
|
# nat from sip.conf can be comma separated list of values:
|
213
|
# yes/no, [auto_]force_rport, [auto_]comedia
|
214
|
if 'yes' in val:
|
215
|
set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
|
216
|
set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
|
217
|
if 'comedia' in val:
|
218
|
set_value('rtp_symmetric', 'yes', section, pjsip, nmapped)
|
219
|
if 'force_rport' in val:
|
220
|
set_value('force_rport', 'yes', section, pjsip, nmapped)
|
221
|
set_value('rewrite_contact', 'yes', section, pjsip, nmapped)
|
222
|
|
223
|
def set_notify(key, val, section, pjsip, nmapped):
|
224
|
"""
|
225
|
Sets the notify_early_inuse_ringing value from notifyringing
|
226
|
"""
|
227
|
pjsip_key_name = 'notify_early_inuse_ringing'
|
228
|
if 'yes' in val:
|
229
|
set_value(pjsip_key_name, 'yes', section, pjsip, nmapped)
|
230
|
else:
|
231
|
set_value(pjsip_key_name, 'no', section, pjsip, nmapped)
|
232
|
|
233
|
def set_timers(key, val, section, pjsip, nmapped):
|
234
|
"""
|
235
|
Sets the timers in pjsip.conf from the session-timers option
|
236
|
found in sip.conf.
|
237
|
"""
|
238
|
# pjsip.conf values can be yes/no, required, always
|
239
|
# 'required' is a new feature of chan_pjsip, which rejects
|
240
|
# all SIP clients not supporting Session Timers
|
241
|
# 'Accept' is the default value of chan_sip and maps to 'yes'
|
242
|
# chan_sip ignores the case, for example 'session-timers=Refuse'
|
243
|
val = val.lower()
|
244
|
if val == 'originate':
|
245
|
set_value('timers', 'always', section, pjsip, nmapped)
|
246
|
elif val == 'refuse':
|
247
|
set_value('timers', 'no', section, pjsip, nmapped)
|
248
|
else:
|
249
|
set_value('timers', 'yes', section, pjsip, nmapped)
|
250
|
|
251
|
|
252
|
def set_direct_media(key, val, section, pjsip, nmapped):
|
253
|
"""
|
254
|
Maps values from the sip.conf comma separated direct_media option
|
255
|
into pjsip.conf direct_media options.
|
256
|
"""
|
257
|
if 'yes' in val:
|
258
|
set_value('direct_media', 'yes', section, pjsip, nmapped)
|
259
|
if 'update' in val:
|
260
|
set_value('direct_media_method', 'update', section, pjsip, nmapped)
|
261
|
if 'outgoing' in val:
|
262
|
set_value('direct_media_glare_mitigation', 'outgoing', section,
|
263
|
pjsip, nmapped)
|
264
|
if 'nonat' in val:
|
265
|
set_value('disable_direct_media_on_nat', 'yes', section, pjsip,
|
266
|
nmapped)
|
267
|
if 'no' in val:
|
268
|
set_value('direct_media', 'no', section, pjsip, nmapped)
|
269
|
|
270
|
|
271
|
def from_sendrpid(key, val, section, pjsip, nmapped):
|
272
|
"""Sets the send_rpid/pai values in pjsip.conf."""
|
273
|
if val == 'yes' or val == 'rpid':
|
274
|
set_value('send_rpid', 'yes', section, pjsip, nmapped)
|
275
|
elif val == 'pai':
|
276
|
set_value('send_pai', 'yes', section, pjsip, nmapped)
|
277
|
|
278
|
|
279
|
def set_media_encryption(key, val, section, pjsip, nmapped):
|
280
|
"""Sets the media_encryption value in pjsip.conf"""
|
281
|
try:
|
282
|
dtls = sip.get(section, 'dtlsenable')[0]
|
283
|
if dtls == 'yes':
|
284
|
# If DTLS is enabled, then that overrides SDES encryption.
|
285
|
return
|
286
|
except LookupError:
|
287
|
pass
|
288
|
|
289
|
if val == 'yes':
|
290
|
set_value('media_encryption', 'sdes', section, pjsip, nmapped)
|
291
|
|
292
|
|
293
|
def from_recordfeature(key, val, section, pjsip, nmapped):
|
294
|
"""
|
295
|
If record on/off feature is set to automixmon then set
|
296
|
one_touch_recording, otherwise it can't be mapped.
|
297
|
"""
|
298
|
set_value('one_touch_recording', 'yes', section, pjsip, nmapped)
|
299
|
set_value(key, val, section, pjsip, nmapped)
|
300
|
|
301
|
def set_record_on_feature(key, val, section, pjsip, nmapped):
|
302
|
"""Sets the record_on_feature in pjsip.conf"""
|
303
|
from_recordfeature('record_on_feature', val, section, pjsip, nmapped)
|
304
|
|
305
|
def set_record_off_feature(key, val, section, pjsip, nmapped):
|
306
|
"""Sets the record_off_feature in pjsip.conf"""
|
307
|
from_recordfeature('record_off_feature', val, section, pjsip, nmapped)
|
308
|
|
309
|
def from_progressinband(key, val, section, pjsip, nmapped):
|
310
|
"""Sets the inband_progress value in pjsip.conf"""
|
311
|
# progressinband can = yes/no/never
|
312
|
if val == 'never':
|
313
|
val = 'no'
|
314
|
set_value('inband_progress', val, section, pjsip, nmapped)
|
315
|
|
316
|
|
317
|
def build_host(config, host, section='general', port_key=None):
|
318
|
"""
|
319
|
Returns a string composed of a host:port. This assumes that the host
|
320
|
may have a port as part of the initial value. The port_key overrides
|
321
|
a port in host, see parameter 'bindport' in chan_sip.
|
322
|
"""
|
323
|
try:
|
324
|
socket.inet_pton(socket.AF_INET6, host)
|
325
|
if not host.startswith('['):
|
326
|
# SIP URI will need brackets.
|
327
|
host = '[' + host + ']'
|
328
|
except socket.error:
|
329
|
pass
|
330
|
|
331
|
# Literal IPv6 (like [::]), IPv4, or hostname
|
332
|
# does not work for IPv6 without brackets; case catched above
|
333
|
url = urlparse('sip://' + host)
|
334
|
|
335
|
if port_key:
|
336
|
try:
|
337
|
port = config.get(section, port_key)[0]
|
338
|
host = url.hostname # no port, but perhaps no brackets
|
339
|
try:
|
340
|
socket.inet_pton(socket.AF_INET6, host)
|
341
|
if not host.startswith('['):
|
342
|
# SIP URI will need brackets.
|
343
|
host = '[' + host + ']'
|
344
|
except socket.error:
|
345
|
pass
|
346
|
return host + ':' + port
|
347
|
except LookupError:
|
348
|
pass
|
349
|
|
350
|
# Returns host:port, in brackets if required
|
351
|
# TODO Does not compress IPv6, for example 0:0:0:0:0:0:0:0 should get [::]
|
352
|
return url.netloc
|
353
|
|
354
|
|
355
|
def from_host(key, val, section, pjsip, nmapped):
|
356
|
"""
|
357
|
Sets contact info in an AOR section in pjsip.conf using 'host'
|
358
|
and 'port' data from sip.conf
|
359
|
"""
|
360
|
# all aors have the same name as the endpoint so makes
|
361
|
# it easy to set endpoint's 'aors' value
|
362
|
set_value('aors', section, section, pjsip, nmapped)
|
363
|
if val == 'dynamic':
|
364
|
# Easy case. Just set the max_contacts on the aor and we're done
|
365
|
max_contacts = 1
|
366
|
# Retrieve cust_max_contacts if set
|
367
|
# cust_max_contacts is set in the WEBRTC_OPTIONS user in pjsip.py
|
368
|
# to handle the MobileApp & WebApp registration
|
369
|
try:
|
370
|
max_contacts = int(sip.get(section, 'cust_max_contacts')[0])
|
371
|
except LookupError:
|
372
|
max_contacts = 1
|
373
|
|
374
|
set_value('max_contacts', max_contacts, section, pjsip, nmapped, 'aor')
|
375
|
# Set remove_existing to 1 to mimick chan_sip default behavior
|
376
|
set_value('remove_existing', 1, section, pjsip, nmapped, 'aor')
|
377
|
return
|
378
|
|
379
|
result = 'sip:'
|
380
|
|
381
|
# More difficult case. The host will be either a hostname or
|
382
|
# IP address and may or may not have a port specified. pjsip.conf
|
383
|
# expects the contact to be a SIP URI.
|
384
|
|
385
|
user = None
|
386
|
|
387
|
try:
|
388
|
user = sip.multi_get(section, ['defaultuser', 'username'])[0]
|
389
|
result += user + '@'
|
390
|
except LookupError:
|
391
|
# It's fine if there's no user name
|
392
|
pass
|
393
|
|
394
|
result += build_host(sip, val, section, 'port')
|
395
|
|
396
|
set_value('contact', result, section, pjsip, nmapped, 'aor')
|
397
|
|
398
|
|
399
|
def from_mailbox(key, val, section, pjsip, nmapped):
|
400
|
"""
|
401
|
Determines whether a mailbox configured in sip.conf should map to
|
402
|
an endpoint or aor in pjsip.conf. If subscribemwi is true, then the
|
403
|
mailboxes are set on an aor. Otherwise the mailboxes are set on the
|
404
|
endpoint.
|
405
|
"""
|
406
|
|
407
|
try:
|
408
|
subscribemwi = sip.get(section, 'subscribemwi')[0]
|
409
|
except LookupError:
|
410
|
# No subscribemwi option means default it to 'no'
|
411
|
subscribemwi = 'no'
|
412
|
|
413
|
set_value('mailboxes', val, section, pjsip, nmapped, 'aor'
|
414
|
if subscribemwi == 'yes' else 'endpoint')
|
415
|
|
416
|
|
417
|
def setup_auth(key, val, section, pjsip, nmapped):
|
418
|
"""
|
419
|
Sets up authentication information for a specific endpoint based on the
|
420
|
'secret' setting on a peer in sip.conf
|
421
|
"""
|
422
|
try:
|
423
|
username = sip.get(section, 'username')[0]
|
424
|
set_value('username', username, section, pjsip, nmapped, 'auth')
|
425
|
except LookupError:
|
426
|
set_value('username', section, section, pjsip, nmapped, 'auth')
|
427
|
|
428
|
# In chan_sip, if a secret and an md5secret are both specified on a peer,
|
429
|
# then in practice, only the md5secret is used. If both are encountered
|
430
|
# then we build an auth section that has both an md5_cred and password.
|
431
|
# However, the auth_type will indicate to authenticators to use the
|
432
|
# md5_cred, so like with sip.conf, the password will be there but have
|
433
|
# no purpose.
|
434
|
if key == 'secret':
|
435
|
set_value('password', val, section, pjsip, nmapped, 'auth')
|
436
|
else:
|
437
|
set_value('md5_cred', val, section, pjsip, nmapped, 'auth')
|
438
|
set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
|
439
|
|
440
|
realms = [section]
|
441
|
try:
|
442
|
auths = sip.get('authentication', 'auth')
|
443
|
for i in auths:
|
444
|
user, at, realm = i.partition('@')
|
445
|
realms.append(realm)
|
446
|
except LookupError:
|
447
|
pass
|
448
|
|
449
|
realm_str = ','.join(realms)
|
450
|
|
451
|
"""
|
452
|
Based on ticket #5928:
|
453
|
In case we have one of these settings: 'insecure = port,invite', 'insecure = invite'; for a trunk to a provider
|
454
|
then in pjsip peer 'auth' section should be empty to avoid challenging inbound calls and also 'outbound_auth'
|
455
|
should be set to keep challenging outbound calls as it is required.
|
456
|
"""
|
457
|
substring = 'invite'
|
458
|
insecure_option = sip.get(section, 'insecure')[0]
|
459
|
if insecure_option != None and substring not in insecure_option :
|
460
|
set_value('auth', section, section, pjsip, nmapped)
|
461
|
|
462
|
set_value('outbound_auth', realm_str, section, pjsip, nmapped)
|
463
|
|
464
|
|
465
|
def setup_ident(key, val, section, pjsip, nmapped):
|
466
|
"""
|
467
|
Examines the 'type' field for a sip.conf peer and creates an identify
|
468
|
section if the type is either 'peer' or 'friend'. The identify section uses
|
469
|
either the host or defaultip field of the sip.conf peer.
|
470
|
"""
|
471
|
if val != 'peer' and val != 'friend':
|
472
|
return
|
473
|
|
474
|
try:
|
475
|
ip = sip.get(section, 'host')[0]
|
476
|
except LookupError:
|
477
|
return
|
478
|
|
479
|
if ip == 'dynamic':
|
480
|
try:
|
481
|
ip = sip.get(section, 'defaultip')[0]
|
482
|
except LookupError:
|
483
|
return
|
484
|
|
485
|
set_value('endpoint', section, section, pjsip, nmapped, 'identify')
|
486
|
set_value('match', ip, section, pjsip, nmapped, 'identify')
|
487
|
|
488
|
|
489
|
def from_encryption_taglen(key, val, section, pjsip, nmapped):
|
490
|
"""Sets the srtp_tag32 option based on sip.conf encryption_taglen"""
|
491
|
if val == '32':
|
492
|
set_value('srtp_tag_32', 'yes', section, pjsip, nmapped)
|
493
|
|
494
|
|
495
|
def from_dtlsenable(key, val, section, pjsip, nmapped):
|
496
|
"""Optionally sets media_encryption=dtls based on sip.conf dtlsenable"""
|
497
|
if val == 'yes':
|
498
|
set_value('media_encryption', 'dtls', section, pjsip, nmapped)
|
499
|
|
500
|
def from_transport(key, val, section, pjsip, nmapped):
|
501
|
"""Sets pjsip transport based on sip.conf transport (assuming proper transport section exist in pjsip)"""
|
502
|
if val in ('udp', 'ws'):
|
503
|
set_value('transport', 'transport-' + val, section, pjsip, nmapped)
|
504
|
|
505
|
###############################################################################
|
506
|
|
507
|
# options in pjsip.conf on an endpoint that have no sip.conf equivalent:
|
508
|
# type, 100rel, trust_id_outbound, aggregate_mwi, connected_line_method
|
509
|
|
510
|
# known sip.conf peer keys that can be mapped to a pjsip.conf section/key
|
511
|
peer_map = [
|
512
|
# sip.conf option mapping function pjsip.conf option(s)
|
513
|
###########################################################################
|
514
|
['context', set_value],
|
515
|
['dtmfmode', set_dtmfmode],
|
516
|
['disallow', merge_codec_value],
|
517
|
['allow', merge_codec_value],
|
518
|
['nat', from_nat], # rtp_symmetric, force_rport,
|
519
|
# rewrite_contact
|
520
|
['rtptimeout', set_value('rtp_timeout')],
|
521
|
['rtcp_mux', set_value('rtcp_mux')],
|
522
|
['icesupport', set_value('ice_support')],
|
523
|
['autoframing', set_value('use_ptime')],
|
524
|
['outboundproxy', set_value('outbound_proxy')],
|
525
|
['mohsuggest', set_value('moh_suggest')],
|
526
|
['session-timers', set_timers], # timers
|
527
|
['session-minse', set_value('timers_min_se')],
|
528
|
['session-expires', set_value('timers_sess_expires')],
|
529
|
# identify_by ?
|
530
|
['canreinvite', set_direct_media], # direct_media alias
|
531
|
['directmedia', set_direct_media], # direct_media
|
532
|
# direct_media_method
|
533
|
# directed_media_glare_mitigation
|
534
|
# disable_directed_media_on_nat
|
535
|
['callerid', set_value], # callerid
|
536
|
['callingpres', set_value('callerid_privacy')],
|
537
|
['cid_tag', set_value('callerid_tag')],
|
538
|
['notifyringing', set_notify],
|
539
|
['trustrpid', set_value('trust_id_inbound')],
|
540
|
['sendrpid', from_sendrpid], # send_pai, send_rpid
|
541
|
['send_diversion', set_value],
|
542
|
['encryption', set_media_encryption],
|
543
|
['avpf', set_value('use_avpf')],
|
544
|
['recordonfeature', set_record_on_feature], # automixon
|
545
|
['recordofffeature', set_record_off_feature], # automixon
|
546
|
['progressinband', from_progressinband], # in_band_progress
|
547
|
['callgroup', set_value('call_group')],
|
548
|
['pickupgroup', set_value('pickup_group')],
|
549
|
['namedcallgroup', set_value('named_call_group')],
|
550
|
['namedpickupgroup', set_value('named_pickup_group')],
|
551
|
['allowtransfer', set_value('allow_transfer')],
|
552
|
['fromuser', set_value('from_user')],
|
553
|
['fromdomain', set_value('from_domain')],
|
554
|
['mwifrom', set_value('mwi_from_user')],
|
555
|
['tos_audio', set_value],
|
556
|
['tos_video', set_value],
|
557
|
['cos_audio', set_value],
|
558
|
['cos_video', set_value],
|
559
|
['sdpowner', set_value('sdp_owner')],
|
560
|
['sdpsession', set_value('sdp_session')],
|
561
|
['tonezone', set_value('tone_zone')],
|
562
|
['language', set_value],
|
563
|
['allowsubscribe', set_value('allow_subscribe')],
|
564
|
['subscribecontext', set_value('subscribe_context')],
|
565
|
['subminexpiry', set_value('sub_min_expiry')],
|
566
|
['rtp_engine', set_value],
|
567
|
['mailbox', from_mailbox],
|
568
|
['busylevel', set_value('device_state_busy_at')],
|
569
|
['secret', setup_auth],
|
570
|
['md5secret', setup_auth],
|
571
|
['type', setup_ident],
|
572
|
['dtlsenable', from_dtlsenable],
|
573
|
['dtlsverify', set_value('dtls_verify')],
|
574
|
['dtlsrekey', set_value('dtls_rekey')],
|
575
|
['dtlscertfile', set_value('dtls_cert_file')],
|
576
|
['dtlsprivatekey', set_value('dtls_private_key')],
|
577
|
['dtlscipher', set_value('dtls_cipher')],
|
578
|
['dtlscafile', set_value('dtls_ca_file')],
|
579
|
['dtlscapath', set_value('dtls_ca_path')],
|
580
|
['dtlssetup', set_value('dtls_setup')],
|
581
|
['encryption_taglen', from_encryption_taglen],
|
582
|
['transport', from_transport],
|
583
|
['setvar', ignore],
|
584
|
|
585
|
############################ maps to an aor ###################################
|
586
|
|
587
|
['host', from_host], # contact, max_contacts
|
588
|
['qualifyfreq', set_qualify_frequency],
|
589
|
['qualify', set_qualify_timeout],
|
590
|
['maxexpiry', set_value('maximum_expiration', type='aor')],
|
591
|
['minexpiry', set_value('minimum_expiration', type='aor')],
|
592
|
['defaultexpiry', set_value('default_expiration', type='aor')],
|
593
|
|
594
|
############################# maps to auth#####################################
|
595
|
# type = auth
|
596
|
# username
|
597
|
# password
|
598
|
# md5_cred
|
599
|
# realm
|
600
|
# nonce_lifetime
|
601
|
# auth_type
|
602
|
######################### maps to acl/security ################################
|
603
|
|
604
|
['permit', merge_value(type='acl', section_to='acl')],
|
605
|
['deny', merge_value(type='acl', section_to='acl')],
|
606
|
['acl', merge_value(type='acl', section_to='acl')],
|
607
|
['contactpermit', merge_value(type='acl', section_to='acl', key_to='contact_permit')],
|
608
|
['contactdeny', merge_value(type='acl', section_to='acl', key_to='contact_deny')],
|
609
|
['contactacl', merge_value(type='acl', section_to='acl', key_to='contact_acl')],
|
610
|
|
611
|
########################### maps to transport #################################
|
612
|
# type = transport
|
613
|
# protocol
|
614
|
# bind
|
615
|
# async_operations
|
616
|
# ca_list_file
|
617
|
# ca_list_path
|
618
|
# cert_file
|
619
|
# privkey_file
|
620
|
# password
|
621
|
# external_signaling_address - externip & externhost
|
622
|
# external_signaling_port
|
623
|
# external_media_address
|
624
|
# domain
|
625
|
# verify_server
|
626
|
# verify_client
|
627
|
# require_client_cert
|
628
|
# method
|
629
|
# cipher
|
630
|
# localnet
|
631
|
######################### maps to domain_alias ################################
|
632
|
# type = domain_alias
|
633
|
# domain
|
634
|
######################### maps to registration ################################
|
635
|
# type = registration
|
636
|
# server_uri
|
637
|
# client_uri
|
638
|
# contact_user
|
639
|
# transport
|
640
|
# outbound_proxy
|
641
|
# expiration
|
642
|
# retry_interval
|
643
|
# max_retries
|
644
|
# auth_rejection_permanent
|
645
|
# outbound_auth
|
646
|
########################### maps to identify ##################################
|
647
|
# type = identify
|
648
|
# endpoint
|
649
|
# match
|
650
|
]
|
651
|
|
652
|
|
653
|
def split_hostport(addr):
|
654
|
"""
|
655
|
Given an address in the form 'host:port' separate the host and port
|
656
|
components.
|
657
|
Returns a two-tuple of strings, (host, port). If no port is present in the
|
658
|
string, then the port section of the tuple is None.
|
659
|
"""
|
660
|
try:
|
661
|
socket.inet_pton(socket.AF_INET6, addr)
|
662
|
if not addr.startswith('['):
|
663
|
return (addr, None)
|
664
|
except socket.error:
|
665
|
pass
|
666
|
|
667
|
# Literal IPv6 (like [::]), IPv4, or hostname
|
668
|
# does not work for IPv6 without brackets; case catched above
|
669
|
url = urlparse('sip://' + addr)
|
670
|
# TODO Does not compress IPv6, for example 0:0:0:0:0:0:0:0 should get [::]
|
671
|
return (url.hostname, url.port)
|
672
|
|
673
|
|
674
|
def set_transport_common(section, sip, pjsip, protocol, nmapped):
|
675
|
"""
|
676
|
sip.conf has several global settings that in pjsip.conf apply to individual
|
677
|
transports. This function adds these global settings to each individual
|
678
|
transport.
|
679
|
|
680
|
The settings included are:
|
681
|
externaddr (or externip)
|
682
|
externhost
|
683
|
externtcpport for TCP
|
684
|
externtlsport for TLS
|
685
|
localnet
|
686
|
tos_sip
|
687
|
cos_sip
|
688
|
"""
|
689
|
try:
|
690
|
extern_addr = sip.multi_get('general', ['externaddr', 'externip',
|
691
|
'externhost'])[0]
|
692
|
host, port = split_hostport(extern_addr)
|
693
|
try:
|
694
|
port = sip.get('general', 'extern' + protocol + 'port')[0]
|
695
|
except LookupError:
|
696
|
pass
|
697
|
set_value('external_media_address', host, section, pjsip,
|
698
|
nmapped, 'transport')
|
699
|
set_value('external_signaling_address', host, section, pjsip,
|
700
|
nmapped, 'transport')
|
701
|
if port:
|
702
|
set_value('external_signaling_port', port, section, pjsip,
|
703
|
nmapped, 'transport')
|
704
|
except LookupError:
|
705
|
pass
|
706
|
|
707
|
try:
|
708
|
merge_value('localnet', sip.get('general', 'localnet')[0], 'general',
|
709
|
pjsip, nmapped, 'transport', section, "local_net")
|
710
|
except LookupError:
|
711
|
# No localnet options configured. Move on.
|
712
|
pass
|
713
|
|
714
|
try:
|
715
|
set_value('tos', sip.get('general', 'tos_sip')[0], section, pjsip,
|
716
|
nmapped, 'transport')
|
717
|
except LookupError:
|
718
|
pass
|
719
|
|
720
|
try:
|
721
|
set_value('cos', sip.get('general', 'cos_sip')[0], section, pjsip,
|
722
|
nmapped, 'transport')
|
723
|
except LookupError:
|
724
|
pass
|
725
|
|
726
|
|
727
|
def get_bind(sip, pjsip, protocol):
|
728
|
"""
|
729
|
Given the protocol (udp, tcp, or tls), return
|
730
|
- the bind address, like [::] or 0.0.0.0
|
731
|
- name of the section to be created
|
732
|
"""
|
733
|
section = 'transport-' + protocol
|
734
|
|
735
|
# UDP cannot be disabled in chan_sip
|
736
|
# (and we want to force the creation of a WS transport)
|
737
|
if protocol != 'udp' and protocol != 'ws':
|
738
|
try:
|
739
|
enabled = sip.get('general', protocol + 'enable')[0]
|
740
|
except LookupError:
|
741
|
# No value means disabled by default. Don't create this transport
|
742
|
return (None, section)
|
743
|
if enabled != 'yes':
|
744
|
return (None, section)
|
745
|
|
746
|
try:
|
747
|
bind = pjsip.get(section, 'bind')[0]
|
748
|
# The first run created an transport already but this
|
749
|
# server was not configured for IPv4/IPv6 Dual Stack
|
750
|
return (None, section)
|
751
|
except LookupError:
|
752
|
pass
|
753
|
|
754
|
try:
|
755
|
bind = pjsip.get(section + '6', 'bind')[0]
|
756
|
# The first run created an IPv6 transport, because
|
757
|
# the server was configured with :: as bindaddr.
|
758
|
# Now, re-use its port and create the IPv4 transport
|
759
|
host, port = split_hostport(bind)
|
760
|
bind = '0.0.0.0'
|
761
|
if port:
|
762
|
bind += ':' + str(port)
|
763
|
except LookupError:
|
764
|
# This is the first run, no transport in pjsip exists.
|
765
|
try:
|
766
|
bind = sip.get('general', protocol + 'bindaddr')[0]
|
767
|
except LookupError:
|
768
|
if protocol == 'udp':
|
769
|
try:
|
770
|
bind = sip.get('general', 'bindaddr')[0]
|
771
|
except LookupError:
|
772
|
bind = '0.0.0.0'
|
773
|
else:
|
774
|
try:
|
775
|
bind = pjsip.get('transport-udp6', 'bind')[0]
|
776
|
except LookupError:
|
777
|
bind = pjsip.get('transport-udp', 'bind')[0]
|
778
|
# Only TCP reuses host:port of UDP, others reuse just host
|
779
|
if protocol == 'tls':
|
780
|
bind, port = split_hostport(bind)
|
781
|
host, port = split_hostport(bind)
|
782
|
if host == '::':
|
783
|
section += '6'
|
784
|
|
785
|
# We want the same bind for WS section as for UDP
|
786
|
if protocol == 'udp' or protocol == 'ws':
|
787
|
host = build_host(sip, bind, 'general', 'bindport')
|
788
|
else:
|
789
|
host = build_host(sip, bind)
|
790
|
|
791
|
return (host, section)
|
792
|
|
793
|
|
794
|
def create_udp(sip, pjsip, nmapped):
|
795
|
"""
|
796
|
Creates a 'transport-udp' section in the pjsip.conf file based
|
797
|
on the following settings from sip.conf:
|
798
|
|
799
|
bindaddr (or udpbindaddr)
|
800
|
bindport
|
801
|
"""
|
802
|
protocol = 'udp'
|
803
|
bind, section = get_bind(sip, pjsip, protocol)
|
804
|
|
805
|
set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
|
806
|
set_value('bind', bind, section, pjsip, nmapped, 'transport')
|
807
|
set_transport_common(section, sip, pjsip, protocol, nmapped)
|
808
|
|
809
|
def create_ws(sip, pjsip, nmapped):
|
810
|
"""
|
811
|
Creates a default 'transport-ws' section in the pjsip.conf file based
|
812
|
on the following settings from sip.conf:
|
813
|
|
814
|
bindaddr (or udpbindaddr)
|
815
|
bindport
|
816
|
"""
|
817
|
protocol = 'ws'
|
818
|
bind, section = get_bind(sip, pjsip, protocol)
|
819
|
|
820
|
set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
|
821
|
set_value('bind', '0.0.0.0:5060', section, pjsip, nmapped, 'transport')
|
822
|
|
823
|
def create_tcp(sip, pjsip, nmapped):
|
824
|
"""
|
825
|
Creates a 'transport-tcp' section in the pjsip.conf file based
|
826
|
on the following settings from sip.conf:
|
827
|
|
828
|
tcpenable
|
829
|
tcpbindaddr (or bindaddr)
|
830
|
"""
|
831
|
protocol = 'tcp'
|
832
|
bind, section = get_bind(sip, pjsip, protocol)
|
833
|
if not bind:
|
834
|
return
|
835
|
|
836
|
set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
|
837
|
set_value('bind', bind, section, pjsip, nmapped, 'transport')
|
838
|
set_transport_common(section, sip, pjsip, protocol, nmapped)
|
839
|
|
840
|
|
841
|
def set_tls_cert_file(val, pjsip, section, nmapped):
|
842
|
"""Sets cert_file based on sip.conf tlscertfile"""
|
843
|
set_value('cert_file', val, section, pjsip, nmapped,
|
844
|
'transport')
|
845
|
|
846
|
|
847
|
def set_tls_private_key(val, pjsip, section, nmapped):
|
848
|
"""Sets privkey_file based on sip.conf tlsprivatekey or sslprivatekey"""
|
849
|
set_value('priv_key_file', val, section, pjsip, nmapped,
|
850
|
'transport')
|
851
|
|
852
|
|
853
|
def set_tls_cipher(val, pjsip, section, nmapped):
|
854
|
"""Sets cipher based on sip.conf tlscipher or sslcipher"""
|
855
|
if val == 'ALL':
|
856
|
return
|
857
|
print('chan_sip ciphers do not match 1:1 with PJSIP ciphers.' \
|
858
|
' You should manually review and adjust this.', file=sys.stderr)
|
859
|
|
860
|
set_value('cipher', val, section, pjsip, nmapped, 'transport')
|
861
|
|
862
|
|
863
|
def set_tls_cafile(val, pjsip, section, nmapped):
|
864
|
"""Sets ca_list_file based on sip.conf tlscafile"""
|
865
|
set_value('ca_list_file', val, section, pjsip, nmapped,
|
866
|
'transport')
|
867
|
|
868
|
|
869
|
def set_tls_capath(val, pjsip, section, nmapped):
|
870
|
"""Sets ca_list_path based on sip.conf tlscapath"""
|
871
|
set_value('ca_list_path', val, section, pjsip, nmapped,
|
872
|
'transport')
|
873
|
|
874
|
|
875
|
def set_tls_verifyclient(val, pjsip, section, nmapped):
|
876
|
"""Sets verify_client based on sip.conf tlsverifyclient"""
|
877
|
set_value('verify_client', val, section, pjsip, nmapped,
|
878
|
'transport')
|
879
|
|
880
|
|
881
|
def set_tls_verifyserver(val, pjsip, section, nmapped):
|
882
|
"""Sets verify_server based on sip.conf tlsdontverifyserver"""
|
883
|
|
884
|
if val == 'no':
|
885
|
set_value('verify_server', 'yes', section, pjsip, nmapped,
|
886
|
'transport')
|
887
|
else:
|
888
|
set_value('verify_server', 'no', section, pjsip, nmapped,
|
889
|
'transport')
|
890
|
|
891
|
|
892
|
def create_tls(sip, pjsip, nmapped):
|
893
|
"""
|
894
|
Creates a 'transport-tls' section in pjsip.conf based on the following
|
895
|
settings from sip.conf:
|
896
|
|
897
|
tlsenable (or sslenable)
|
898
|
tlsbindaddr (or sslbindaddr or bindaddr)
|
899
|
tlsprivatekey (or sslprivatekey)
|
900
|
tlscipher (or sslcipher)
|
901
|
tlscafile
|
902
|
tlscapath (or tlscadir)
|
903
|
tlscertfile (or sslcert or tlscert)
|
904
|
tlsverifyclient
|
905
|
tlsdontverifyserver
|
906
|
tlsclientmethod (or sslclientmethod)
|
907
|
"""
|
908
|
protocol = 'tls'
|
909
|
bind, section = get_bind(sip, pjsip, protocol)
|
910
|
if not bind:
|
911
|
return
|
912
|
|
913
|
set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
|
914
|
set_value('bind', bind, section, pjsip, nmapped, 'transport')
|
915
|
set_transport_common(section, sip, pjsip, protocol, nmapped)
|
916
|
|
917
|
tls_map = [
|
918
|
(['tlscertfile', 'sslcert', 'tlscert'], set_tls_cert_file),
|
919
|
(['tlsprivatekey', 'sslprivatekey'], set_tls_private_key),
|
920
|
(['tlscipher', 'sslcipher'], set_tls_cipher),
|
921
|
(['tlscafile'], set_tls_cafile),
|
922
|
(['tlscapath', 'tlscadir'], set_tls_capath),
|
923
|
(['tlsverifyclient'], set_tls_verifyclient),
|
924
|
(['tlsdontverifyserver'], set_tls_verifyserver)
|
925
|
]
|
926
|
|
927
|
for i in tls_map:
|
928
|
try:
|
929
|
i[1](sip.multi_get('general', i[0])[0], pjsip, section, nmapped)
|
930
|
except LookupError:
|
931
|
pass
|
932
|
|
933
|
try:
|
934
|
method = sip.multi_get('general', ['tlsclientmethod',
|
935
|
'sslclientmethod'])[0]
|
936
|
if section != 'transport-' + protocol + '6': # print only once
|
937
|
print('In chan_sip, you specified the TLS version. With chan_sip,' \
|
938
|
' this was just for outbound client connections. In' \
|
939
|
' chan_pjsip, this value is for client and server. Instead,' \
|
940
|
' consider not to specify \'tlsclientmethod\' for chan_sip' \
|
941
|
' and \'method = sslv23\' for chan_pjsip.', file=sys.stderr)
|
942
|
except LookupError:
|
943
|
"""
|
944
|
OpenSSL emerged during the 90s. SSLv2 and SSLv3 were the only
|
945
|
existing methods at that time. The OpenSSL project continued. And as
|
946
|
of today (OpenSSL 1.0.2) this does not start SSLv2 and SSLv3 anymore
|
947
|
but TLSv1.0 and v1.2. Or stated differently: This method should
|
948
|
have been called 'method = secure' or 'method = automatic' back in
|
949
|
the 90s. The PJProject did not realize this and uses 'tlsv1' as
|
950
|
default when unspecified, which disables TLSv1.2. chan_sip used
|
951
|
'sslv23' as default when unspecified, which gives TLSv1.0 and v1.2.
|
952
|
"""
|
953
|
method = 'sslv23'
|
954
|
set_value('method', method, section, pjsip, nmapped, 'transport')
|
955
|
|
956
|
|
957
|
def map_transports(sip, pjsip, nmapped):
|
958
|
"""
|
959
|
Finds options in sip.conf general section pertaining to
|
960
|
transport configuration and creates appropriate transport
|
961
|
configuration sections in pjsip.conf.
|
962
|
|
963
|
sip.conf only allows a single UDP transport, TCP transport,
|
964
|
and TLS transport for each IP version. As such, the mapping
|
965
|
into PJSIP can be made consistent by defining six sections:
|
966
|
|
967
|
transport-udp6
|
968
|
transport-udp
|
969
|
transport-tcp6
|
970
|
transport-tcp
|
971
|
transport-tls6
|
972
|
transport-tls
|
973
|
|
974
|
To accommodate the default behaviors in sip.conf, we'll need to
|
975
|
create the UDP transports first, followed by the TCP and TLS transports.
|
976
|
"""
|
977
|
|
978
|
# First create a UDP transport. Even if no bind parameters were provided
|
979
|
# in sip.conf, chan_sip would always bind to UDP 0.0.0.0:5060
|
980
|
create_udp(sip, pjsip, nmapped)
|
981
|
create_udp(sip, pjsip, nmapped)
|
982
|
|
983
|
create_ws(sip, pjsip, nmapped)
|
984
|
|
985
|
# TCP settings may be dependent on UDP settings, so do it second.
|
986
|
create_tcp(sip, pjsip, nmapped)
|
987
|
create_tcp(sip, pjsip, nmapped)
|
988
|
create_tls(sip, pjsip, nmapped)
|
989
|
create_tls(sip, pjsip, nmapped)
|
990
|
|
991
|
|
992
|
def map_auth(sip, pjsip, nmapped):
|
993
|
"""
|
994
|
Creates auth sections based on entries in the authentication section of
|
995
|
sip.conf. pjsip.conf section names consist of "auth_" followed by the name
|
996
|
of the realm.
|
997
|
"""
|
998
|
try:
|
999
|
auths = sip.get('authentication', 'auth')
|
1000
|
except LookupError:
|
1001
|
return
|
1002
|
|
1003
|
for i in auths:
|
1004
|
creds, at, realm = i.partition('@')
|
1005
|
if not at and not realm:
|
1006
|
# Invalid. Move on
|
1007
|
continue
|
1008
|
user, colon, secret = creds.partition(':')
|
1009
|
if not secret:
|
1010
|
user, sharp, md5 = creds.partition('#')
|
1011
|
if not md5:
|
1012
|
#Invalid. move on
|
1013
|
continue
|
1014
|
section = "auth_" + realm
|
1015
|
|
1016
|
set_value('realm', realm, section, pjsip, nmapped, 'auth')
|
1017
|
set_value('username', user, section, pjsip, nmapped, 'auth')
|
1018
|
if secret:
|
1019
|
set_value('password', secret, section, pjsip, nmapped, 'auth')
|
1020
|
else:
|
1021
|
set_value('md5_cred', md5, section, pjsip, nmapped, 'auth')
|
1022
|
set_value('auth_type', 'md5', section, pjsip, nmapped, 'auth')
|
1023
|
|
1024
|
|
1025
|
class Registration:
|
1026
|
"""
|
1027
|
Class for parsing and storing information in a register line in sip.conf.
|
1028
|
"""
|
1029
|
def __init__(self, line, retry_interval, max_retries, outbound_proxy):
|
1030
|
self.retry_interval = retry_interval
|
1031
|
self.max_retries = max_retries
|
1032
|
self.outbound_proxy = outbound_proxy
|
1033
|
self.parse(line)
|
1034
|
self.extract_trunk_name()
|
1035
|
|
1036
|
def parse(self, line):
|
1037
|
"""
|
1038
|
Initial parsing routine for register lines in sip.conf.
|
1039
|
|
1040
|
This splits the line into the part before the host, and the part
|
1041
|
after the '@' symbol. These two parts are then passed to their
|
1042
|
own parsing routines
|
1043
|
"""
|
1044
|
|
1045
|
# register =>
|
1046
|
# [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry]
|
1047
|
|
1048
|
prehost, at, host_part = line.rpartition('@')
|
1049
|
if not prehost:
|
1050
|
raise
|
1051
|
|
1052
|
self.parse_host_part(host_part)
|
1053
|
self.parse_user_part(prehost)
|
1054
|
|
1055
|
def parse_host_part(self, host_part):
|
1056
|
"""
|
1057
|
Parsing routine for the part after the final '@' in a register line.
|
1058
|
The strategy is to use partition calls to peel away the data starting
|
1059
|
from the right and working to the left.
|
1060
|
"""
|
1061
|
pre_expiry, sep, expiry = host_part.partition('~')
|
1062
|
pre_extension, sep, self.extension = pre_expiry.partition('/')
|
1063
|
self.host, sep, self.port = pre_extension.partition(':')
|
1064
|
|
1065
|
self.expiry = expiry if expiry else '120'
|
1066
|
|
1067
|
def parse_user_part(self, user_part):
|
1068
|
"""
|
1069
|
Parsing routine for the part before the final '@' in a register line.
|
1070
|
The only mandatory part of this line is the user portion. The strategy
|
1071
|
here is to start by using partition calls to remove everything to
|
1072
|
the right of the user, then finish by using rpartition calls to remove
|
1073
|
everything to the left of the user.
|
1074
|
"""
|
1075
|
self.peer = ''
|
1076
|
self.protocol = 'udp'
|
1077
|
protocols = ['udp', 'tcp', 'tls']
|
1078
|
for protocol in protocols:
|
1079
|
position = user_part.find(protocol + '://')
|
1080
|
if -1 < position:
|
1081
|
post_transport = user_part[position + 6:]
|
1082
|
self.peer, sep, self.protocol = user_part[:position + 3].rpartition('?')
|
1083
|
user_part = post_transport
|
1084
|
break
|
1085
|
|
1086
|
colons = user_part.count(':')
|
1087
|
if (colons == 3):
|
1088
|
# :domainport:secret:authuser
|
1089
|
pre_auth, sep, port_auth = user_part.partition(':')
|
1090
|
self.domainport, sep, auth = port_auth.partition(':')
|
1091
|
self.secret, sep, self.authuser = auth.partition(':')
|
1092
|
elif (colons == 2):
|
1093
|
# :secret:authuser
|
1094
|
pre_auth, sep, auth = user_part.partition(':')
|
1095
|
self.secret, sep, self.authuser = auth.partition(':')
|
1096
|
elif (colons == 1):
|
1097
|
# :secret
|
1098
|
pre_auth, sep, self.secret = user_part.partition(':')
|
1099
|
elif (colons == 0):
|
1100
|
# No port, secret, or authuser
|
1101
|
pre_auth = user_part
|
1102
|
else:
|
1103
|
# Invalid setting
|
1104
|
raise
|
1105
|
|
1106
|
self.user, sep, self.domain = pre_auth.partition('@')
|
1107
|
|
1108
|
def extract_trunk_name(self):
|
1109
|
"""
|
1110
|
Extract the name of the trunk to set different name for
|
1111
|
all the registration section in pjsip.conf.
|
1112
|
Function used in self.write().
|
1113
|
"""
|
1114
|
trunk = sip_dao.find_by(commented=0,
|
1115
|
category='trunk',
|
1116
|
host=self.host,
|
1117
|
username=self.authuser)
|
1118
|
|
1119
|
self.trunk_name = trunk.name
|
1120
|
|
1121
|
def write(self, pjsip, nmapped):
|
1122
|
"""
|
1123
|
Write parsed registration data into a section in pjsip.conf
|
1124
|
|
1125
|
Most of the data in self will get written to a registration section.
|
1126
|
However, there will also need to be an auth section created if a
|
1127
|
secret or authuser is present.
|
1128
|
|
1129
|
General mapping of values:
|
1130
|
A combination of self.host and self.port is server_uri
|
1131
|
A combination of self.user, self.domain, and self.domainport is
|
1132
|
client_uri
|
1133
|
self.expiry is expiration
|
1134
|
self.extension is contact_user
|
1135
|
self.protocol will map to one of the mapped transports
|
1136
|
self.secret and self.authuser will result in a new auth section, and
|
1137
|
outbound_auth will point to that section.
|
1138
|
XXX self.peer really doesn't map to anything :(
|
1139
|
"""
|
1140
|
|
1141
|
section = 'reg_' + self.trunk_name
|
1142
|
|
1143
|
set_value('retry_interval', self.retry_interval, section, pjsip,
|
1144
|
nmapped, 'registration')
|
1145
|
set_value('max_retries', self.max_retries, section, pjsip, nmapped,
|
1146
|
'registration')
|
1147
|
if self.extension:
|
1148
|
set_value('contact_user', self.extension, section, pjsip, nmapped,
|
1149
|
'registration')
|
1150
|
|
1151
|
set_value('expiration', self.expiry, section, pjsip, nmapped,
|
1152
|
'registration')
|
1153
|
|
1154
|
if self.protocol == 'udp':
|
1155
|
set_value('transport', 'transport-udp', section, pjsip, nmapped,
|
1156
|
'registration')
|
1157
|
elif self.protocol == 'tcp':
|
1158
|
set_value('transport', 'transport-tcp', section, pjsip, nmapped,
|
1159
|
'registration')
|
1160
|
elif self.protocol == 'tls':
|
1161
|
set_value('transport', 'transport-tls', section, pjsip, nmapped,
|
1162
|
'registration')
|
1163
|
|
1164
|
auth_section = 'auth_reg_' + self.trunk_name
|
1165
|
|
1166
|
if hasattr(self, 'secret') and self.secret:
|
1167
|
set_value('password', self.secret, auth_section, pjsip, nmapped,
|
1168
|
'auth')
|
1169
|
set_value('username', self.authuser if hasattr(self, 'authuser')
|
1170
|
else self.user, auth_section, pjsip, nmapped, 'auth')
|
1171
|
set_value('outbound_auth', auth_section, section, pjsip, nmapped,
|
1172
|
'registration')
|
1173
|
|
1174
|
client_uri = "sip:%s@" % self.user
|
1175
|
if self.domain:
|
1176
|
client_uri += self.domain
|
1177
|
else:
|
1178
|
client_uri += self.host
|
1179
|
|
1180
|
if hasattr(self, 'domainport') and self.domainport:
|
1181
|
client_uri += ":" + self.domainport
|
1182
|
elif self.port:
|
1183
|
client_uri += ":" + self.port
|
1184
|
|
1185
|
set_value('client_uri', client_uri, section, pjsip, nmapped,
|
1186
|
'registration')
|
1187
|
|
1188
|
server_uri = "sip:%s" % self.host
|
1189
|
if self.port:
|
1190
|
server_uri += ":" + self.port
|
1191
|
|
1192
|
set_value('server_uri', server_uri, section, pjsip, nmapped,
|
1193
|
'registration')
|
1194
|
|
1195
|
if self.outbound_proxy:
|
1196
|
set_value('outbound_proxy', self.outbound_proxy, section, pjsip,
|
1197
|
nmapped, 'registration')
|
1198
|
|
1199
|
|
1200
|
def map_registrations(sip, pjsip, nmapped):
|
1201
|
"""
|
1202
|
Gathers all necessary outbound registration data in sip.conf and creates
|
1203
|
corresponding registration sections in pjsip.conf
|
1204
|
"""
|
1205
|
try:
|
1206
|
regs = sip.get('general', 'register')
|
1207
|
except LookupError:
|
1208
|
return
|
1209
|
|
1210
|
try:
|
1211
|
retry_interval = sip.get('general', 'registertimeout')[0]
|
1212
|
except LookupError:
|
1213
|
retry_interval = '20'
|
1214
|
|
1215
|
try:
|
1216
|
max_retries = sip.get('general', 'registerattempts')[0]
|
1217
|
# Deal with sip.conf registerattempts=0 which meant 'unlimited'
|
1218
|
if int(max_retries) == 0:
|
1219
|
max_retries = '100'
|
1220
|
except LookupError:
|
1221
|
max_retries = '10'
|
1222
|
|
1223
|
try:
|
1224
|
outbound_proxy = sip.get('general', 'outboundproxy')[0]
|
1225
|
except LookupError:
|
1226
|
outbound_proxy = ''
|
1227
|
|
1228
|
for i in regs:
|
1229
|
reg = Registration(i, retry_interval, max_retries, outbound_proxy)
|
1230
|
reg.write(pjsip, nmapped)
|
1231
|
|
1232
|
|
1233
|
def map_setvars(sip, section, pjsip, nmapped):
|
1234
|
"""
|
1235
|
Map all setvar in peer section to the appropriate endpoint set_var
|
1236
|
"""
|
1237
|
try:
|
1238
|
setvars = sip.section(section)[0].get('setvar')
|
1239
|
except LookupError:
|
1240
|
return
|
1241
|
|
1242
|
for setvar in setvars:
|
1243
|
set_value('set_var', setvar, section, pjsip, nmapped)
|
1244
|
|
1245
|
|
1246
|
def map_peer(sip, section, pjsip, nmapped):
|
1247
|
"""
|
1248
|
Map the options from a peer section in sip.conf into the appropriate
|
1249
|
sections in pjsip.conf
|
1250
|
"""
|
1251
|
for i in peer_map:
|
1252
|
try:
|
1253
|
# coming from sip.conf the values should mostly be a list with a
|
1254
|
# single value. In the few cases that they are not a specialized
|
1255
|
# function (see merge_value) is used to retrieve the values.
|
1256
|
i[1](i[0], sip.get(section, i[0])[0], section, pjsip, nmapped)
|
1257
|
except LookupError:
|
1258
|
pass # key not found in sip.conf
|
1259
|
|
1260
|
setup_udptl(section, pjsip, nmapped)
|
1261
|
|
1262
|
def find_non_mapped(sections, nmapped):
|
1263
|
"""
|
1264
|
Determine sip.conf options that were not properly mapped to pjsip.conf
|
1265
|
options.
|
1266
|
"""
|
1267
|
for section, sect in sections.iteritems():
|
1268
|
try:
|
1269
|
# since we are pulling from sip.conf this should always
|
1270
|
# be a single value list
|
1271
|
sect = sect[0]
|
1272
|
# loop through the section and store any values that were not
|
1273
|
# mapped
|
1274
|
for key in sect.keys(True):
|
1275
|
for i in peer_map:
|
1276
|
if i[0] == key:
|
1277
|
break
|
1278
|
else:
|
1279
|
nmapped(section, key, sect[key])
|
1280
|
except LookupError:
|
1281
|
pass
|
1282
|
|
1283
|
|
1284
|
def map_system(sip, pjsip, nmapped):
|
1285
|
section = 'system' # Just a label; you as user can change that
|
1286
|
type = 'system' # Not a label, therefore not the same as section
|
1287
|
|
1288
|
try:
|
1289
|
user_agent = sip.get('general', 'useragent')[0]
|
1290
|
set_value('user_agent', user_agent, 'global', pjsip, nmapped, 'global')
|
1291
|
except LookupError:
|
1292
|
pass
|
1293
|
|
1294
|
|
1295
|
try:
|
1296
|
sipdebug = sip.get('general', 'sipdebug')[0]
|
1297
|
set_value('debug', sipdebug, 'global', pjsip, nmapped, 'global')
|
1298
|
except LookupError:
|
1299
|
pass
|
1300
|
|
1301
|
try:
|
1302
|
useroption_parsing = sip.get('general', 'legacy_useroption_parsing')[0]
|
1303
|
set_value('ignore_uri_user_options', useroption_parsing, 'global', pjsip, nmapped, 'global')
|
1304
|
except LookupError:
|
1305
|
pass
|
1306
|
|
1307
|
try:
|
1308
|
timer_t1 = sip.get('general', 'timert1')[0]
|
1309
|
set_value('timer_t1', timer_t1, section, pjsip, nmapped, type)
|
1310
|
except LookupError:
|
1311
|
pass
|
1312
|
|
1313
|
try:
|
1314
|
timer_b = sip.get('general', 'timerb')[0]
|
1315
|
set_value('timer_b', timer_b, section, pjsip, nmapped, type)
|
1316
|
except LookupError:
|
1317
|
pass
|
1318
|
|
1319
|
try:
|
1320
|
compact_headers = sip.get('general', 'compactheaders')[0]
|
1321
|
set_value('compact_headers', compact_headers, section, pjsip, nmapped, type)
|
1322
|
except LookupError:
|
1323
|
pass
|
1324
|
|
1325
|
|
1326
|
def convert(prmsip):
|
1327
|
"""
|
1328
|
Entry point for configuration file conversion. This
|
1329
|
function will create a pjsip.conf object and begin to
|
1330
|
map specific sections from sip.conf into it.
|
1331
|
Returns the new pjsip.conf object once completed
|
1332
|
"""
|
1333
|
global sip
|
1334
|
sip = prmsip
|
1335
|
|
1336
|
pjsip = sip.__class__()
|
1337
|
non_mappings = astdicts.MultiOrderedDict()
|
1338
|
nmapped = non_mapped(non_mappings)
|
1339
|
map_system(sip, pjsip, nmapped)
|
1340
|
map_transports(sip, pjsip, nmapped)
|
1341
|
map_registrations(sip, pjsip, nmapped)
|
1342
|
map_auth(sip, pjsip, nmapped)
|
1343
|
for section in sip.sections():
|
1344
|
if section == 'authentication':
|
1345
|
pass
|
1346
|
else:
|
1347
|
map_peer(sip, section, pjsip, nmapped)
|
1348
|
map_setvars(sip, section, pjsip, nmapped)
|
1349
|
|
1350
|
find_non_mapped(sip.defaults(), nmapped)
|
1351
|
find_non_mapped(sip.sections(), nmapped)
|
1352
|
|
1353
|
for key, val in sip.includes().iteritems():
|
1354
|
pjsip.add_include(PREFIX + key, convert(val, PREFIX + key,
|
1355
|
non_mappings, True)[0])
|
1356
|
return pjsip, non_mappings
|
1357
|
|
1358
|
|
1359
|
###############################################################################
|
1360
|
|
1361
|
|
1362
|
sip = None
|