Projet

Général

Profil

[Résolu] Enregistrement de 2 trunks chez le même fourniss... » sip_to_pjsip.py.txt

Bastien HIRIARTE, 23/10/2023 09:40

 
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
    (1-1/1)