Пример #1
0
 def placeAnswer(self, args):
     cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData() #TODO: move to a subroutine
     rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, passw, cli, \
       parameters = args
     self.huntstop_scodes = parameters.get('huntstop_scodes', ())
     if self.global_config.has_key('static_tr_out'):
         cld = re_replace(self.global_config['static_tr_out'], cld)
     if host == 'sip-ua':
         host = self.source[0]
         port = self.source[1]
     else:
         host = host.split(':', 1)
         if len(host) > 1:
             port = int(host[1])
         else:
             port = SipConf.default_port
         host = host[0]
     if not forward_on_fail and self.global_config['acct_enable']:
         self.acctA = RadiusAccounting(self.global_config, 'answer', send_start = self.global_config['start_acct_enable'])
         self.acctA.setParams(parameters.get('bill-to', self.username), parameters.get('bill-cli', cli), \
           parameters.get('bill-cld', cld), self.cGUID, self.cId, host, credit_time)
         print 'adding acct stop cb'
     else:
         self.acctA = FakeAccounting()
     ua = UA(self.global_config, self.recvEvent, user, passw, (host, port), credit_time, \
       (self.aConnA,), (self.aDisc,), (self.aDisc,), dead_cbs = (self.aDead,), \
       expire_time = expires, no_progress_time = no_progress_expires, \
       extra_headers = parameters.get('extra_headers', None))
     if self.rtp_proxy_session and parameters.get('rtpp', True):
         print 'placeAnswer() registering on sdp change callbacks'
         ua.on_local_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
         ua.on_remote_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
         body = body.getCopy()
         body.content += 'a=nortpproxy:yes\r\n'
         self.proxied = True
     ua.kaInterval = self.global_config['ka_ans'] #TODO: is this okay for uaA?
     if parameters.has_key('group_timeout'):
         timeout, skipto = parameters['group_timeout']
         Timeout(self.group_expires, timeout, 1, skipto)
     ua.recvEvent(CCEventTry((cId + '-b2b_%d' % rnum, cGUID, cli, cld, body, auth, \
       parameters.get('caller_name', self.caller_name))))
     return ua
Пример #2
0
class CallController(object):
    id = 1
    uaA = None
    uaO = None
    state = None
    cId = None
    cld = None
    eTry = None
    routes = None
    remote_ip = None
    source = None
    acctA = None
    acctO = None
    global_config = None
    rtp_proxy_session = None
    huntstop_scodes = None
    pass_headers = None
    auth_proc = None
    proxied = False
    challenge = None

    def __init__(self, remote_ip, source, global_config, pass_headers):
        self.id = CallController.id
        CallController.id += 1
        self.global_config = global_config
        self.uaA = UA(self.global_config, event_cb = self.recvEvent, conn_cbs = (self.aConn,), disc_cbs = (self.aDisc,), \
          fail_cbs = (self.aDisc,), dead_cbs = (self.aDead,))
        self.uaA.kaInterval = self.global_config['keepalive_ans']
        self.state = CCStateIdle
        self.uaO = None
        self.routes = []
        self.remote_ip = remote_ip
        self.source = source
        self.pass_headers = pass_headers

    def recvEvent(self, event, ua):
        if ua == self.uaA:
            if self.state == CCStateIdle:
                if not isinstance(event, CCEventTry):
                    # Some weird event received
                    self.uaA.recvEvent(CCEventDisconnect(rtime = event.rtime))
                    return
                self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name = event.getData()
                self.cGUID = cGUID.hexForm()
                if self.cld == None:
                    self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (1)'), rtime = event.rtime))
                    self.state = CCStateDead
                    return
                if body == None:
                    self.uaA.recvEvent(CCEventFail((500, 'Body-less INVITE is not supported'), rtime = event.rtime))
                    self.state = CCStateDead
                    return
                if self.global_config.has_key('_allowed_pts'):
                    try:
                        body.parse()
                    except:
                        self.uaA.recvEvent(CCEventFail((400, 'Malformed SDP Body'), rtime = event.rtime))
                        self.state = CCStateDead
                        return
                    allowed_pts = self.global_config['_allowed_pts']
                    mbody = body.content.sections[0].m_header
                    if mbody.transport.lower() == 'rtp/avp':
                        mbody.formats = [x for x in mbody.formats if x in allowed_pts]
                        if len(mbody.formats) == 0:
                            self.uaA.recvEvent(CCEventFail((488, 'Not Acceptable Here')))
                            self.state = CCStateDead
                            return
                if self.cld.startswith('nat-'):
                    self.cld = self.cld[4:]
                    body.content += 'a=nated:yes\r\n'
                    event.data = (self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name)
                if self.global_config.has_key('static_tr_in'):
                    self.cld = re_replace(self.global_config['static_tr_in'], self.cld)
                    event.data = (self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name)
                if self.global_config.has_key('_rtp_proxy_clients'):
                    self.rtp_proxy_session = Rtp_proxy_session(self.global_config, call_id = self.cId, \
                      notify_socket = global_config['b2bua_socket'], \
                      notify_tag = quote('r %s' % str(self.id)))
                    self.rtp_proxy_session.callee_raddress = (self.remote_ip, 5060)
                self.eTry = event
                self.state = CCStateWaitRoute
                if not self.global_config['auth_enable']:
                    self.username = self.remote_ip
                    self.rDone(((), 0))
                elif auth == None or auth.username == None or len(auth.username) == 0:
                    self.username = self.remote_ip
                    self.auth_proc = self.global_config['_radius_client'].do_auth(self.remote_ip, self.cli, self.cld, self.cGUID, \
                      self.cId, self.remote_ip, self.rDone)
                else:
                    self.username = auth.username
                    self.auth_proc = self.global_config['_radius_client'].do_auth(auth.username, self.cli, self.cld, self.cGUID, 
                      self.cId, self.remote_ip, self.rDone, auth.realm, auth.nonce, auth.uri, auth.response)
                return
            if self.state not in (CCStateARComplete, CCStateConnected, CCStateDisconnecting) or self.uaO == None:
                return
            self.uaO.recvEvent(event)
        else:
            if (isinstance(event, CCEventFail) or isinstance(event, CCEventDisconnect)) and self.state == CCStateARComplete and \
              (isinstance(self.uaA.state, UasStateTrying) or isinstance(self.uaA.state, UasStateRinging)) and len(self.routes) > 0:
                if isinstance(event, CCEventFail):
                    code = event.getData()[0]
                else:
                    code = None
                if code == None or code not in self.huntstop_scodes:
                    self.placeOriginate(self.routes.pop(0))
                    return
            self.uaA.recvEvent(event)

    def rDone(self, results):
        # Check that we got necessary result from Radius
        if len(results) != 2 or results[1] != 0:
            if isinstance(self.uaA.state, UasStateTrying):
                if self.challenge != None:
                    event = CCEventFail((401, 'Unauthorized'))
                    event.extra_header = self.challenge
                else:
                    event = CCEventFail((403, 'Auth Failed'))
                self.uaA.recvEvent(event)
                self.state = CCStateDead
            return
        if self.global_config['acct_enable']:
            self.acctA = RadiusAccounting(self.global_config, 'answer', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctA.ms_precision = self.global_config.getdefault('precise_acct', False)
            self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip)
        else:
            self.acctA = FakeAccounting()
        # Check that uaA is still in a valid state, send acct stop
        if not isinstance(self.uaA.state, UasStateTrying):
            self.acctA.disc(self.uaA, time(), 'caller')
            return
        cli = [x[1][4:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')]
        if len(cli) > 0:
            self.cli = cli[0]
            if len(self.cli) == 0:
                self.cli = None
        caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
        if len(caller_name) > 0:
            self.caller_name = caller_name[0]
            if len(self.caller_name) == 0:
                self.caller_name = None
        credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
        if len(credit_time) > 0:
            global_credit_time = int(credit_time[0][1])
        else:
            global_credit_time = None
        if not self.global_config.has_key('static_route'):
            routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
            if len(routing) == 0:
                self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
                self.state = CCStateDead
                return
            routing = [x[1][8:].split(';') for x in routing]
        else:
            routing = [self.global_config['static_route'].split(';')]
        rnum = 0
        for route in routing:
            rnum += 1
            if route[0].find('@') != -1:
                cld, host = route[0].split('@')
                if len(cld) == 0:
                    # Allow CLD to be forcefully removed by sending `Routing:@host' entry,
                    # as opposed to the Routing:host, which means that CLD should be obtained
                    # from the incoming call leg.
                    cld = None
            else:
                cld = self.cld
                host = route[0]
            credit_time = global_credit_time
            expires = None
            no_progress_expires = None
            forward_on_fail = False
            user = None
            passw = None
            cli = self.cli
            parameters = {}
            parameters['extra_headers'] = self.pass_headers[:]
            for a, v in [x.split('=') for x in route[1:]]:
                if a == 'credit-time':
                    credit_time = int(v)
                    if credit_time < 0:
                        credit_time = None
                elif a == 'expires':
                    expires = int(v)
                    if expires < 0:
                        expires = None
                elif a == 'hs_scodes':
                    parameters['huntstop_scodes'] = tuple([int(x) for x in v.split(',') if len(x.strip()) > 0])
                elif a == 'np_expires':
                    no_progress_expires = int(v)
                    if no_progress_expires < 0:
                        no_progress_expires = None
                elif a == 'forward_on_fail':
                    forward_on_fail = True
                elif a == 'auth':
                    user, passw = v.split(':', 1)
                elif a == 'cli':
                    cli = v
                    if len(cli) == 0:
                        cli = None
                elif a == 'cnam':
                    caller_name = unquote(v)
                    if len(caller_name) == 0:
                        caller_name = None
                    parameters['caller_name'] = caller_name
                elif a == 'ash':
                    ash = SipHeader(unquote(v))
                    parameters['extra_headers'].append(ash)
                elif a == 'rtpp':
                    parameters['rtpp'] = (int(v) != 0)
                elif a == 'gt':
                    timeout, skip = v.split(',', 1)
                    parameters['group_timeout'] = (int(timeout), rnum + int(skip))
                else:
                    parameters[a] = v
            if self.global_config.has_key('max_credit_time'):
                if credit_time == None or credit_time > self.global_config['max_credit_time']:
                    credit_time = self.global_config['max_credit_time']
            if credit_time == 0 or expires == 0:
                continue
            self.routes.append((rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, \
              passw, cli, parameters))
            #print 'Got route:', host, cld
        if len(self.routes) == 0:
            self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
            self.state = CCStateDead
            return
        self.state = CCStateARComplete
        self.placeOriginate(self.routes.pop(0))

    def placeOriginate(self, args):
        cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData()
        rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, passw, cli, \
          parameters = args
        self.huntstop_scodes = parameters.get('huntstop_scodes', ())
        if self.global_config.has_key('static_tr_out'):
            cld = re_replace(self.global_config['static_tr_out'], cld)
        if host == 'sip-ua':
            host = self.source[0]
            port = self.source[1]
        else:
            if host.startswith('['):
                # IPv6
                host = host.split(']', 1)
                port = host[1].split(':', 1)
                host = host[0] + ']'
                if len(port) > 1:
                    port = int(port[1])
                else:
                    port = SipConf.default_port
            else:
                # IPv4
                host = host.split(':', 1)
                if len(host) > 1:
                    port = int(host[1])
                else:
                    port = SipConf.default_port
                host = host[0]
        if not forward_on_fail and self.global_config['acct_enable']:
            self.acctO = RadiusAccounting(self.global_config, 'originate', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctO.ms_precision = self.global_config.getdefault('precise_acct', False)
            self.acctO.setParams(parameters.get('bill-to', self.username), parameters.get('bill-cli', cli), \
              parameters.get('bill-cld', cld), self.cGUID, self.cId, host)
        else:
            self.acctO = None
        self.acctA.credit_time = credit_time
        conn_handlers = [self.oConn]
        disc_handlers = []
        if not forward_on_fail and self.global_config['acct_enable']:
            disc_handlers.append(self.acctO.disc)
        self.uaO = UA(self.global_config, self.recvEvent, user, passw, (host, port), credit_time, tuple(conn_handlers), \
          tuple(disc_handlers), tuple(disc_handlers), dead_cbs = (self.oDead,), expire_time = expires, \
          no_progress_time = no_progress_expires, extra_headers = parameters.get('extra_headers', None))
        if self.rtp_proxy_session != None and parameters.get('rtpp', True):
            self.uaO.on_local_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
            self.uaO.on_remote_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
            self.rtp_proxy_session.caller_raddress = (host, port)
            body = body.getCopy()
            body.content += 'a=nortpproxy:yes\r\n'
            self.proxied = True
        self.uaO.kaInterval = self.global_config['keepalive_orig']
        if parameters.has_key('group_timeout'):
            timeout, skipto = parameters['group_timeout']
            Timeout(self.group_expires, timeout, 1, skipto)
        if self.global_config.getdefault('hide_call_id', False):
            cId = SipCallId(md5(str(cId)).hexdigest() + ('-b2b_%d' % rnum))
        else:
            cId += '-b2b_%d' % rnum
        event = CCEventTry((cId, cGUID, cli, cld, body, auth, \
          parameters.get('caller_name', self.caller_name)))
        event.reason = self.eTry.reason
        self.uaO.recvEvent(event)

    def disconnect(self, rtime = None):
        self.uaA.disconnect(rtime = rtime)

    def oConn(self, ua, rtime, origin):
        if self.acctO != None:
            self.acctO.conn(ua, rtime, origin)

    def aConn(self, ua, rtime, origin):
        self.state = CCStateConnected
        self.acctA.conn(ua, rtime, origin)

    def aDisc(self, ua, rtime, origin, result = 0):
        if self.state == CCStateWaitRoute and self.auth_proc != None:
            self.auth_proc.cancel()
            self.auth_proc = None
        if self.uaO != None and self.state != CCStateDead:
            self.state = CCStateDisconnecting
        else:
            self.state = CCStateDead
        if self.acctA != None:
            self.acctA.disc(ua, rtime, origin, result)
        self.rtp_proxy_session = None

    def aDead(self, ua):
        if (self.uaO == None or isinstance(self.uaO.state, UaStateDead)):
            if self.global_config['_cmap'].debug_mode:
                print 'garbadge collecting', self
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def oDead(self, ua):
        if ua == self.uaO and isinstance(self.uaA.state, UaStateDead):
            if self.global_config['_cmap'].debug_mode:
                print 'garbadge collecting', self
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def group_expires(self, skipto):
        if self.state != CCStateARComplete or len(self.routes) == 0 or self.routes[0][0] > skipto or \
          (not isinstance(self.uaA.state, UasStateTrying) and not isinstance(self.uaA.state, UasStateRinging)):
            return
        # When the last group in the list has timeouted don't disconnect
        # the current attempt forcefully. Instead, make sure that if the
        # current originate call leg fails no more routes will be
        # processed.
        if skipto == self.routes[-1][0] + 1:
            self.routes = []
            return
        while self.routes[0][0] != skipto:
            self.routes.pop(0)
        self.uaO.disconnect()
Пример #3
0
 def rDone(self, results):
     # Check that we got necessary result from Radius
     if len(results) != 2 or results[1] != 0:
         if isinstance(self.uaA.state, UasStateTrying):
             if self.challenge != None:
                 event = CCEventFail((401, 'Unauthorized'))
                 event.extra_header = self.challenge
             else:
                 event = CCEventFail((403, 'Auth Failed'))
             self.uaA.recvEvent(event)
             self.state = CCStateDead
         return
     if self.global_config['acct_enable']:
         self.acctA = RadiusAccounting(self.global_config, 'answer', \
           send_start = self.global_config['start_acct_enable'], lperiod = \
           self.global_config.getdefault('alive_acct_int', None))
         self.acctA.ms_precision = self.global_config.getdefault('precise_acct', False)
         self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip)
     else:
         self.acctA = FakeAccounting()
     # Check that uaA is still in a valid state, send acct stop
     if not isinstance(self.uaA.state, UasStateTrying):
         self.acctA.disc(self.uaA, time(), 'caller')
         return
     cli = [x[1][4:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')]
     if len(cli) > 0:
         self.cli = cli[0]
         if len(self.cli) == 0:
             self.cli = None
     caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
     if len(caller_name) > 0:
         self.caller_name = caller_name[0]
         if len(self.caller_name) == 0:
             self.caller_name = None
     credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
     if len(credit_time) > 0:
         global_credit_time = int(credit_time[0][1])
     else:
         global_credit_time = None
     if not self.global_config.has_key('static_route'):
         routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
         if len(routing) == 0:
             self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
             self.state = CCStateDead
             return
         routing = [x[1][8:].split(';') for x in routing]
     else:
         routing = [self.global_config['static_route'].split(';')]
     rnum = 0
     for route in routing:
         rnum += 1
         if route[0].find('@') != -1:
             cld, host = route[0].split('@')
             if len(cld) == 0:
                 # Allow CLD to be forcefully removed by sending `Routing:@host' entry,
                 # as opposed to the Routing:host, which means that CLD should be obtained
                 # from the incoming call leg.
                 cld = None
         else:
             cld = self.cld
             host = route[0]
         credit_time = global_credit_time
         expires = None
         no_progress_expires = None
         forward_on_fail = False
         user = None
         passw = None
         cli = self.cli
         parameters = {}
         parameters['extra_headers'] = self.pass_headers[:]
         for a, v in [x.split('=') for x in route[1:]]:
             if a == 'credit-time':
                 credit_time = int(v)
                 if credit_time < 0:
                     credit_time = None
             elif a == 'expires':
                 expires = int(v)
                 if expires < 0:
                     expires = None
             elif a == 'hs_scodes':
                 parameters['huntstop_scodes'] = tuple([int(x) for x in v.split(',') if len(x.strip()) > 0])
             elif a == 'np_expires':
                 no_progress_expires = int(v)
                 if no_progress_expires < 0:
                     no_progress_expires = None
             elif a == 'forward_on_fail':
                 forward_on_fail = True
             elif a == 'auth':
                 user, passw = v.split(':', 1)
             elif a == 'cli':
                 cli = v
                 if len(cli) == 0:
                     cli = None
             elif a == 'cnam':
                 caller_name = unquote(v)
                 if len(caller_name) == 0:
                     caller_name = None
                 parameters['caller_name'] = caller_name
             elif a == 'ash':
                 ash = SipHeader(unquote(v))
                 parameters['extra_headers'].append(ash)
             elif a == 'rtpp':
                 parameters['rtpp'] = (int(v) != 0)
             elif a == 'gt':
                 timeout, skip = v.split(',', 1)
                 parameters['group_timeout'] = (int(timeout), rnum + int(skip))
             else:
                 parameters[a] = v
         if self.global_config.has_key('max_credit_time'):
             if credit_time == None or credit_time > self.global_config['max_credit_time']:
                 credit_time = self.global_config['max_credit_time']
         if credit_time == 0 or expires == 0:
             continue
         self.routes.append((rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, \
           passw, cli, parameters))
         #print 'Got route:', host, cld
     if len(self.routes) == 0:
         self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
         self.state = CCStateDead
         return
     self.state = CCStateARComplete
     self.placeOriginate(self.routes.pop(0))
Пример #4
0
 def rDone(self, results):
     # Check that we got necessary result from Radius
     if len(results) != 2 or results[1] != 0:
         if isinstance(self.uaA.state, UasStateTrying):
             if self.challenge != None:
                 event = CCEventFail((401, 'Unauthorized'))
                 event.extra_header = self.challenge
             else:
                 event = CCEventFail((403, 'Auth Failed'))
             self.uaA.recvEvent(event)
             self.state = CCStateDead
         return
     if self.global_config['acct_enable']:
         self.acctA = RadiusAccounting(self.global_config, 'answer', \
           send_start = self.global_config['start_acct_enable'], lperiod = \
           self.global_config.getdefault('alive_acct_int', None))
         self.acctA.ms_precision = self.global_config.getdefault(
             'precise_acct', False)
         self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID,
                              self.cId, self.remote_ip)
     else:
         self.acctA = FakeAccounting()
     # Check that uaA is still in a valid state, send acct stop
     if not isinstance(self.uaA.state, UasStateTrying):
         self.acctA.disc(self.uaA, MonoTime(), 'caller')
         return
     cli = [
         x[1][4:] for x in results[0]
         if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')
     ]
     if len(cli) > 0:
         self.cli = cli[0]
         if len(self.cli) == 0:
             self.cli = None
     caller_name = [
         x[1][5:] for x in results[0]
         if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')
     ]
     if len(caller_name) > 0:
         self.caller_name = caller_name[0]
         if len(self.caller_name) == 0:
             self.caller_name = None
     credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
     if len(credit_time) > 0:
         credit_time = int(credit_time[0][1])
     else:
         credit_time = None
     if not '_static_route' in self.global_config:
         routing = [
             x for x in results[0]
             if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')
         ]
         if len(routing) == 0:
             self.uaA.recvEvent(
                 CCEventFail((500, 'Internal Server Error (2)')))
             self.state = CCStateDead
             return
         routing = [B2BRoute(x[1][8:]) for x in routing]
     else:
         routing = [
             self.global_config['_static_route'].getCopy(),
         ]
     rnum = 0
     for oroute in routing:
         rnum += 1
         max_credit_time = self.global_config.getdefault(
             'max_credit_time', None)
         oroute.customize(rnum, self.cld, self.cli, credit_time, self.pass_headers, \
           max_credit_time)
         if oroute.credit_time == 0 or oroute.expires == 0:
             continue
         self.routes.append(oroute)
         #print 'Got route:', oroute.hostport, oroute.cld
     if len(self.routes) == 0:
         self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
         self.state = CCStateDead
         return
     self.state = CCStateARComplete
     self.placeOriginate(self.routes.pop(0))
Пример #5
0
class CallController(object):
    id = 1
    uaA = None
    uaO = None
    state = None
    cId = None
    cld = None
    eTry = None
    routes = None
    remote_ip = None
    source = None
    acctA = None
    acctO = None
    global_config = None
    rtp_proxy_session = None
    huntstop_scodes = None
    pass_headers = None
    auth_proc = None
    proxied = False
    challenge = None

    def __init__(self, remote_ip, source, global_config, pass_headers):
        self.id = CallController.id
        CallController.id += 1
        self.global_config = global_config
        self.uaA = UA(self.global_config, event_cb = self.recvEvent, conn_cbs = (self.aConn,), disc_cbs = (self.aDisc,), \
          fail_cbs = (self.aDisc,), dead_cbs = (self.aDead,))
        self.uaA.kaInterval = self.global_config['keepalive_ans']
        self.uaA.local_ua = self.global_config['_uaname']
        self.state = CCStateIdle
        self.uaO = None
        self.routes = []
        self.remote_ip = remote_ip
        self.source = source
        self.pass_headers = pass_headers

    def recvEvent(self, event, ua):
        if ua == self.uaA:
            if self.state == CCStateIdle:
                if not isinstance(event, CCEventTry):
                    # Some weird event received
                    self.uaA.recvEvent(CCEventDisconnect(rtime=event.rtime))
                    return
                self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name = event.getData(
                )
                self.cGUID = cGUID.hexForm()
                if self.cld == None:
                    self.uaA.recvEvent(
                        CCEventFail((500, 'Internal Server Error (1)'),
                                    rtime=event.rtime))
                    self.state = CCStateDead
                    return
                if body != None and '_allowed_pts' in self.global_config:
                    try:
                        body.parse()
                    except:
                        self.uaA.recvEvent(
                            CCEventFail((400, 'Malformed SDP Body'),
                                        rtime=event.rtime))
                        self.state = CCStateDead
                        return
                    allowed_pts = self.global_config['_allowed_pts']
                    mbody = body.content.sections[0].m_header
                    if mbody.transport.lower() == 'rtp/avp':
                        old_len = len(mbody.formats)
                        mbody.formats = [
                            x for x in mbody.formats if x in allowed_pts
                        ]
                        if len(mbody.formats) == 0:
                            self.uaA.recvEvent(
                                CCEventFail((488, 'Not Acceptable Here')))
                            self.state = CCStateDead
                            return
                        if old_len > len(mbody.formats):
                            body.content.sections[0].optimize_a()
                if self.cld.startswith('nat-'):
                    self.cld = self.cld[4:]
                    if body != None:
                        body.content += 'a=nated:yes\r\n'
                    event.data = (self.cId, cGUID, self.cli, self.cld, body,
                                  auth, self.caller_name)
                if 'static_tr_in' in self.global_config:
                    self.cld = re_replace(self.global_config['static_tr_in'],
                                          self.cld)
                    event.data = (self.cId, cGUID, self.cli, self.cld, body,
                                  auth, self.caller_name)
                if '_rtp_proxy_clients' in self.global_config:
                    self.rtp_proxy_session = Rtp_proxy_session(self.global_config, call_id = self.cId, \
                      notify_socket = self.global_config['b2bua_socket'], \
                      notify_tag = quote('r %s' % str(self.id)))
                    self.rtp_proxy_session.callee.raddress = (self.remote_ip,
                                                              5060)
                    self.rtp_proxy_session.insert_nortpp = True
                self.eTry = event
                self.state = CCStateWaitRoute
                if not self.global_config['auth_enable']:
                    self.username = self.remote_ip
                    self.rDone(((), 0))
                elif auth == None or auth.username == None or len(
                        auth.username) == 0:
                    self.username = self.remote_ip
                    self.auth_proc = self.global_config['_radius_client'].do_auth(self.remote_ip, self.cli, self.cld, self.cGUID, \
                      self.cId, self.remote_ip, self.rDone)
                else:
                    self.username = auth.username
                    self.auth_proc = self.global_config[
                        '_radius_client'].do_auth(auth.username, self.cli,
                                                  self.cld, self.cGUID,
                                                  self.cId, self.remote_ip,
                                                  self.rDone, auth.realm,
                                                  auth.nonce, auth.uri,
                                                  auth.response)
                return
            if self.state not in (CCStateARComplete, CCStateConnected,
                                  CCStateDisconnecting) or self.uaO == None:
                return
            self.uaO.recvEvent(event)
        else:
            if (isinstance(event, CCEventFail) or isinstance(event, CCEventDisconnect)) and self.state == CCStateARComplete and \
              (isinstance(self.uaA.state, UasStateTrying) or isinstance(self.uaA.state, UasStateRinging)) and len(self.routes) > 0:
                if isinstance(event, CCEventFail):
                    code = event.getData()[0]
                else:
                    code = None
                if code == None or code not in self.huntstop_scodes:
                    self.placeOriginate(self.routes.pop(0))
                    return
            self.uaA.recvEvent(event)

    def rDone(self, results):
        # Check that we got necessary result from Radius
        if len(results) != 2 or results[1] != 0:
            if isinstance(self.uaA.state, UasStateTrying):
                if self.challenge != None:
                    event = CCEventFail((401, 'Unauthorized'))
                    event.extra_header = self.challenge
                else:
                    event = CCEventFail((403, 'Auth Failed'))
                self.uaA.recvEvent(event)
                self.state = CCStateDead
            return
        if self.global_config['acct_enable']:
            self.acctA = RadiusAccounting(self.global_config, 'answer', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctA.ms_precision = self.global_config.getdefault(
                'precise_acct', False)
            self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID,
                                 self.cId, self.remote_ip)
        else:
            self.acctA = FakeAccounting()
        # Check that uaA is still in a valid state, send acct stop
        if not isinstance(self.uaA.state, UasStateTrying):
            self.acctA.disc(self.uaA, MonoTime(), 'caller')
            return
        cli = [
            x[1][4:] for x in results[0]
            if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')
        ]
        if len(cli) > 0:
            self.cli = cli[0]
            if len(self.cli) == 0:
                self.cli = None
        caller_name = [
            x[1][5:] for x in results[0]
            if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')
        ]
        if len(caller_name) > 0:
            self.caller_name = caller_name[0]
            if len(self.caller_name) == 0:
                self.caller_name = None
        credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
        if len(credit_time) > 0:
            credit_time = int(credit_time[0][1])
        else:
            credit_time = None
        if not '_static_route' in self.global_config:
            routing = [
                x for x in results[0]
                if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')
            ]
            if len(routing) == 0:
                self.uaA.recvEvent(
                    CCEventFail((500, 'Internal Server Error (2)')))
                self.state = CCStateDead
                return
            routing = [B2BRoute(x[1][8:]) for x in routing]
        else:
            routing = [
                self.global_config['_static_route'].getCopy(),
            ]
        rnum = 0
        for oroute in routing:
            rnum += 1
            max_credit_time = self.global_config.getdefault(
                'max_credit_time', None)
            oroute.customize(rnum, self.cld, self.cli, credit_time, self.pass_headers, \
              max_credit_time)
            if oroute.credit_time == 0 or oroute.expires == 0:
                continue
            self.routes.append(oroute)
            #print 'Got route:', oroute.hostport, oroute.cld
        if len(self.routes) == 0:
            self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
            self.state = CCStateDead
            return
        self.state = CCStateARComplete
        self.placeOriginate(self.routes.pop(0))

    def placeOriginate(self, oroute):
        cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData()
        cld = oroute.cld
        self.huntstop_scodes = oroute.params.get('huntstop_scodes', ())
        if 'static_tr_out' in self.global_config:
            cld = re_replace(self.global_config['static_tr_out'], cld)
        if oroute.hostport == 'sip-ua':
            host = self.source[0]
            nh_address, same_af = self.source, True
        else:
            host = oroute.hostonly
            nh_address, same_af = oroute.getNHAddr(self.source)
        if not oroute.forward_on_fail and self.global_config['acct_enable']:
            self.acctO = RadiusAccounting(self.global_config, 'originate', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctO.ms_precision = self.global_config.getdefault(
                'precise_acct', False)
            self.acctO.setParams(oroute.params.get('bill-to', self.username), oroute.params.get('bill-cli', oroute.cli), \
              oroute.params.get('bill-cld', cld), self.cGUID, self.cId, host)
        else:
            self.acctO = None
        self.acctA.credit_time = oroute.credit_time
        conn_handlers = [self.oConn]
        disc_handlers = []
        if not oroute.forward_on_fail and self.global_config['acct_enable']:
            disc_handlers.append(self.acctO.disc)
        self.uaO = UA(self.global_config, self.recvEvent, oroute.user, oroute.passw, nh_address, oroute.credit_time, tuple(conn_handlers), \
          tuple(disc_handlers), tuple(disc_handlers), dead_cbs = (self.oDead,), expire_time = oroute.expires, \
          no_progress_time = oroute.no_progress_expires, extra_headers = oroute.extra_headers)
        self.uaO.local_ua = self.global_config['_uaname']
        if self.source != oroute.params.get('outbound_proxy', None):
            self.uaO.outbound_proxy = oroute.params.get('outbound_proxy', None)
        if self.rtp_proxy_session != None and oroute.params.get('rtpp', True):
            self.uaO.on_local_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
            self.uaO.on_remote_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
            self.rtp_proxy_session.caller.raddress = nh_address
            if body != None:
                body = body.getCopy()
            self.proxied = True
        self.uaO.kaInterval = self.global_config['keepalive_orig']
        if 'group_timeout' in oroute.params:
            timeout, skipto = oroute.params['group_timeout']
            Timeout(self.group_expires, timeout, 1, skipto)
        if self.global_config.getdefault('hide_call_id', False):
            cId = SipCallId(
                md5(str(cId).encode()).hexdigest() + ('-b2b_%d' % oroute.rnum))
        else:
            cId += '-b2b_%d' % oroute.rnum
        event = CCEventTry((cId, cGUID, oroute.cli, cld, body, auth, \
          oroute.params.get('caller_name', self.caller_name)))
        if self.eTry.max_forwards != None:
            event.max_forwards = self.eTry.max_forwards - 1
            if event.max_forwards <= 0:
                self.uaA.recvEvent(CCEventFail((483, 'Too Many Hops')))
                self.state = CCStateDead
                return
        event.reason = self.eTry.reason
        self.uaO.recvEvent(event)

    def disconnect(self, rtime=None):
        self.uaA.disconnect(rtime=rtime)

    def oConn(self, ua, rtime, origin):
        if self.acctO != None:
            self.acctO.conn(ua, rtime, origin)

    def aConn(self, ua, rtime, origin):
        self.state = CCStateConnected
        self.acctA.conn(ua, rtime, origin)

    def aDisc(self, ua, rtime, origin, result=0):
        if self.state == CCStateWaitRoute and self.auth_proc != None:
            self.auth_proc.cancel()
            self.auth_proc = None
        if self.uaO != None and self.state != CCStateDead:
            self.state = CCStateDisconnecting
        else:
            self.state = CCStateDead
        if self.acctA != None:
            self.acctA.disc(ua, rtime, origin, result)
        self.rtp_proxy_session = None

    def aDead(self, ua):
        if (self.uaO == None or isinstance(self.uaO.state, UaStateDead)):
            if self.global_config['_cmap'].debug_mode:
                print('garbadge collecting', self)
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def oDead(self, ua):
        if ua == self.uaO and isinstance(self.uaA.state, UaStateDead):
            if self.global_config['_cmap'].debug_mode:
                print('garbadge collecting', self)
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def group_expires(self, skipto):
        if self.state != CCStateARComplete or len(self.routes) == 0 or self.routes[0][0] > skipto or \
          (not isinstance(self.uaA.state, UasStateTrying) and not isinstance(self.uaA.state, UasStateRinging)):
            return
        # When the last group in the list has timeouted don't disconnect
        # the current attempt forcefully. Instead, make sure that if the
        # current originate call leg fails no more routes will be
        # processed.
        if skipto == self.routes[-1][0] + 1:
            self.routes = []
            return
        while self.routes[0][0] != skipto:
            self.routes.pop(0)
        self.uaO.disconnect()
Пример #6
0
 def rDone(self, results):
     # Check that we got necessary result from Radius
     if len(results) != 2 or results[1] != 0:
         if isinstance(self.uaA.state, UasStateTrying):
             if self.challenge != None:
                 event = CCEventFail((401, 'Unauthorized'))
                 event.extra_header = self.challenge
             else:
                 event = CCEventFail((403, 'Auth Failed'))
             self.uaA.recvEvent(event)
             self.state = CCStateDead
         return
     if self.global_config['acct_enable']:
         self.acctA = RadiusAccounting(self.global_config, 'answer', \
           send_start = self.global_config['start_acct_enable'], lperiod = \
           self.global_config.getdefault('alive_acct_int', None))
         self.acctA.ms_precision = self.global_config.getdefault('precise_acct', False)
         self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip)
     else:
         self.acctA = FakeAccounting()
     # Check that uaA is still in a valid state, send acct stop
     if not isinstance(self.uaA.state, UasStateTrying):
         self.acctA.disc(self.uaA, MonoTime(), 'caller')
         return
     cli = [x[1][4:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')]
     if len(cli) > 0:
         self.cli = cli[0]
         if len(self.cli) == 0:
             self.cli = None
     caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
     if len(caller_name) > 0:
         self.caller_name = caller_name[0]
         if len(self.caller_name) == 0:
             self.caller_name = None
     credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
     if len(credit_time) > 0:
         credit_time = int(credit_time[0][1])
     else:
         credit_time = None
     if not '_static_route' in self.global_config:
         routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
         if len(routing) == 0:
             self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
             self.state = CCStateDead
             return
         routing = [B2BRoute(x[1][8:]) for x in routing]
     else:
         routing = [self.global_config['_static_route'].getCopy(),]
     rnum = 0
     for oroute in routing:
         rnum += 1
         max_credit_time = self.global_config.getdefault('max_credit_time', None)
         oroute.customize(rnum, self.cld, self.cli, credit_time, self.pass_headers, \
           max_credit_time)
         if oroute.credit_time == 0 or oroute.expires == 0:
             continue
         self.routes.append(oroute)
         #print 'Got route:', oroute.hostport, oroute.cld
     if len(self.routes) == 0:
         self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
         self.state = CCStateDead
         return
     self.state = CCStateARComplete
     self.placeOriginate(self.routes.pop(0))
Пример #7
0
class CallController(object):
    id = 1
    uaA = None
    uaO = None
    state = None
    cId = None
    cld = None
    eTry = None
    routes = None
    remote_ip = None
    source = None
    acctA = None
    acctO = None
    global_config = None
    rtp_proxy_session = None
    huntstop_scodes = None
    pass_headers = None
    auth_proc = None
    proxied = False
    challenge = None

    def __init__(self, remote_ip, source, global_config, pass_headers):
        self.id = CallController.id
        CallController.id += 1
        self.global_config = global_config
        self.uaA = UA(self.global_config, event_cb = self.recvEvent, conn_cbs = (self.aConn,), disc_cbs = (self.aDisc,), \
          fail_cbs = (self.aDisc,), dead_cbs = (self.aDead,))
        self.uaA.kaInterval = self.global_config['keepalive_ans']
        self.uaA.local_ua = self.global_config['_uaname']
        self.state = CCStateIdle
        self.uaO = None
        self.routes = []
        self.remote_ip = remote_ip
        self.source = source
        self.pass_headers = pass_headers

    def recvEvent(self, event, ua):
        if ua == self.uaA:
            if self.state == CCStateIdle:
                if not isinstance(event, CCEventTry):
                    # Some weird event received
                    self.uaA.recvEvent(CCEventDisconnect(rtime = event.rtime))
                    return
                self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name = event.getData()
                self.cGUID = cGUID.hexForm()
                if self.cld == None:
                    self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (1)'), rtime = event.rtime))
                    self.state = CCStateDead
                    return
                if body != None and '_allowed_pts' in self.global_config:
                    try:
                        body.parse()
                    except:
                        self.uaA.recvEvent(CCEventFail((400, 'Malformed SDP Body'), rtime = event.rtime))
                        self.state = CCStateDead
                        return
                    allowed_pts = self.global_config['_allowed_pts']
                    mbody = body.content.sections[0].m_header
                    if mbody.transport.lower() == 'rtp/avp':
                        old_len = len(mbody.formats)
                        mbody.formats = [x for x in mbody.formats if x in allowed_pts]
                        if len(mbody.formats) == 0:
                            self.uaA.recvEvent(CCEventFail((488, 'Not Acceptable Here')))
                            self.state = CCStateDead
                            return
                        if old_len > len(mbody.formats):
                            body.content.sections[0].optimize_a()
                if self.cld.startswith('nat-'):
                    self.cld = self.cld[4:]
                    if body != None:
                        body.content += 'a=nated:yes\r\n'
                    event.data = (self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name)
                if 'static_tr_in' in self.global_config:
                    self.cld = re_replace(self.global_config['static_tr_in'], self.cld)
                    event.data = (self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name)
                if '_rtp_proxy_clients' in self.global_config:
                    self.rtp_proxy_session = Rtp_proxy_session(self.global_config, call_id = self.cId, \
                      notify_socket = self.global_config['b2bua_socket'], \
                      notify_tag = quote('r %s' % str(self.id)))
                    self.rtp_proxy_session.callee.raddress = (self.remote_ip, 5060)
                    self.rtp_proxy_session.insert_nortpp = True
                self.eTry = event
                self.state = CCStateWaitRoute
                if not self.global_config['auth_enable']:
                    self.username = self.remote_ip
                    self.rDone(((), 0))
                elif auth == None or auth.username == None or len(auth.username) == 0:
                    self.username = self.remote_ip
                    self.auth_proc = self.global_config['_radius_client'].do_auth(self.remote_ip, self.cli, self.cld, self.cGUID, \
                      self.cId, self.remote_ip, self.rDone)
                else:
                    self.username = auth.username
                    self.auth_proc = self.global_config['_radius_client'].do_auth(auth.username, self.cli, self.cld, self.cGUID, 
                      self.cId, self.remote_ip, self.rDone, auth.realm, auth.nonce, auth.uri, auth.response)
                return
            if self.state not in (CCStateARComplete, CCStateConnected, CCStateDisconnecting) or self.uaO == None:
                return
            self.uaO.recvEvent(event)
        else:
            if (isinstance(event, CCEventFail) or isinstance(event, CCEventDisconnect)) and self.state == CCStateARComplete and \
              (isinstance(self.uaA.state, UasStateTrying) or isinstance(self.uaA.state, UasStateRinging)) and len(self.routes) > 0:
                if isinstance(event, CCEventFail):
                    code = event.getData()[0]
                else:
                    code = None
                if code == None or code not in self.huntstop_scodes:
                    self.placeOriginate(self.routes.pop(0))
                    return
            self.uaA.recvEvent(event)

    def rDone(self, results):
        # Check that we got necessary result from Radius
        if len(results) != 2 or results[1] != 0:
            if isinstance(self.uaA.state, UasStateTrying):
                if self.challenge != None:
                    event = CCEventFail((401, 'Unauthorized'))
                    event.extra_header = self.challenge
                else:
                    event = CCEventFail((403, 'Auth Failed'))
                self.uaA.recvEvent(event)
                self.state = CCStateDead
            return
        if self.global_config['acct_enable']:
            self.acctA = RadiusAccounting(self.global_config, 'answer', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctA.ms_precision = self.global_config.getdefault('precise_acct', False)
            self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip)
        else:
            self.acctA = FakeAccounting()
        # Check that uaA is still in a valid state, send acct stop
        if not isinstance(self.uaA.state, UasStateTrying):
            self.acctA.disc(self.uaA, MonoTime(), 'caller')
            return
        cli = [x[1][4:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')]
        if len(cli) > 0:
            self.cli = cli[0]
            if len(self.cli) == 0:
                self.cli = None
        caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
        if len(caller_name) > 0:
            self.caller_name = caller_name[0]
            if len(self.caller_name) == 0:
                self.caller_name = None
        credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
        if len(credit_time) > 0:
            credit_time = int(credit_time[0][1])
        else:
            credit_time = None
        if not '_static_route' in self.global_config:
            routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
            if len(routing) == 0:
                self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
                self.state = CCStateDead
                return
            routing = [B2BRoute(x[1][8:]) for x in routing]
        else:
            routing = [self.global_config['_static_route'].getCopy(),]
        rnum = 0
        for oroute in routing:
            rnum += 1
            max_credit_time = self.global_config.getdefault('max_credit_time', None)
            oroute.customize(rnum, self.cld, self.cli, credit_time, self.pass_headers, \
              max_credit_time)
            if oroute.credit_time == 0 or oroute.expires == 0:
                continue
            self.routes.append(oroute)
            #print 'Got route:', oroute.hostport, oroute.cld
        if len(self.routes) == 0:
            self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
            self.state = CCStateDead
            return
        self.state = CCStateARComplete
        self.placeOriginate(self.routes.pop(0))

    def placeOriginate(self, oroute):
        cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData()
        cld = oroute.cld
        self.huntstop_scodes = oroute.params.get('huntstop_scodes', ())
        if 'static_tr_out' in self.global_config:
            cld = re_replace(self.global_config['static_tr_out'], cld)
        if oroute.hostport == 'sip-ua':
            host = self.source[0]
            nh_address, same_af = self.source, True
        else:
            host = oroute.hostonly
            nh_address, same_af = oroute.getNHAddr(self.source)
        if not oroute.forward_on_fail and self.global_config['acct_enable']:
            self.acctO = RadiusAccounting(self.global_config, 'originate', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctO.ms_precision = self.global_config.getdefault('precise_acct', False)
            self.acctO.setParams(oroute.params.get('bill-to', self.username), oroute.params.get('bill-cli', oroute.cli), \
              oroute.params.get('bill-cld', cld), self.cGUID, self.cId, host)
        else:
            self.acctO = None
        self.acctA.credit_time = oroute.credit_time
        conn_handlers = [self.oConn]
        disc_handlers = []
        if not oroute.forward_on_fail and self.global_config['acct_enable']:
            disc_handlers.append(self.acctO.disc)
        self.uaO = UA(self.global_config, self.recvEvent, oroute.user, oroute.passw, nh_address, oroute.credit_time, tuple(conn_handlers), \
          tuple(disc_handlers), tuple(disc_handlers), dead_cbs = (self.oDead,), expire_time = oroute.expires, \
          no_progress_time = oroute.no_progress_expires, extra_headers = oroute.extra_headers)
        self.uaO.local_ua = self.global_config['_uaname']
        if self.source != oroute.params.get('outbound_proxy', None):
            self.uaO.outbound_proxy = oroute.params.get('outbound_proxy', None)
        if self.rtp_proxy_session != None and oroute.params.get('rtpp', True):
            self.uaO.on_local_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
            self.uaO.on_remote_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
            self.rtp_proxy_session.caller.raddress = nh_address
            if body != None:
                body = body.getCopy()
            self.proxied = True
        self.uaO.kaInterval = self.global_config['keepalive_orig']
        if 'group_timeout' in oroute.params:
            timeout, skipto = oroute.params['group_timeout']
            Timeout(self.group_expires, timeout, 1, skipto)
        if self.global_config.getdefault('hide_call_id', False):
            cId = SipCallId(md5(str(cId).encode()).hexdigest() + ('-b2b_%d' % oroute.rnum))
        else:
            cId += '-b2b_%d' % oroute.rnum
        event = CCEventTry((cId, cGUID, oroute.cli, cld, body, auth, \
          oroute.params.get('caller_name', self.caller_name)))
        if self.eTry.max_forwards != None:
            event.max_forwards = self.eTry.max_forwards - 1
            if event.max_forwards <= 0:
                self.uaA.recvEvent(CCEventFail((483, 'Too Many Hops')))
                self.state = CCStateDead
                return
        event.reason = self.eTry.reason
        self.uaO.recvEvent(event)

    def disconnect(self, rtime = None):
        self.uaA.disconnect(rtime = rtime)

    def oConn(self, ua, rtime, origin):
        if self.acctO != None:
            self.acctO.conn(ua, rtime, origin)

    def aConn(self, ua, rtime, origin):
        self.state = CCStateConnected
        self.acctA.conn(ua, rtime, origin)

    def aDisc(self, ua, rtime, origin, result = 0):
        if self.state == CCStateWaitRoute and self.auth_proc != None:
            self.auth_proc.cancel()
            self.auth_proc = None
        if self.uaO != None and self.state != CCStateDead:
            self.state = CCStateDisconnecting
        else:
            self.state = CCStateDead
        if self.acctA != None:
            self.acctA.disc(ua, rtime, origin, result)
        self.rtp_proxy_session = None

    def aDead(self, ua):
        if (self.uaO == None or isinstance(self.uaO.state, UaStateDead)):
            if self.global_config['_cmap'].debug_mode:
                print('garbadge collecting', self)
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def oDead(self, ua):
        if ua == self.uaO and isinstance(self.uaA.state, UaStateDead):
            if self.global_config['_cmap'].debug_mode:
                print('garbadge collecting', self)
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def group_expires(self, skipto):
        if self.state != CCStateARComplete or len(self.routes) == 0 or self.routes[0][0] > skipto or \
          (not isinstance(self.uaA.state, UasStateTrying) and not isinstance(self.uaA.state, UasStateRinging)):
            return
        # When the last group in the list has timeouted don't disconnect
        # the current attempt forcefully. Instead, make sure that if the
        # current originate call leg fails no more routes will be
        # processed.
        if skipto == self.routes[-1][0] + 1:
            self.routes = []
            return
        while self.routes[0][0] != skipto:
            self.routes.pop(0)
        self.uaO.disconnect()
Пример #8
0
class CallController(object):
    id = 1
    uaA = None
    uaO = None
    state = None
    cId = None
    cld = None
    eTry = None
    routes = None
    remote_ip = None
    source = None
    acctA = None
    acctO = None
    global_config = None
    rtp_proxy_session = None
    huntstop_scodes = None
    pass_headers = None
    auth_proc = None
    proxied = False
    challenge = None

    def __init__(self, remote_ip, source, global_config, pass_headers):
        self.id = CallController.id
        CallController.id += 1
        self.global_config = global_config
        self.uaA = UA(self.global_config, event_cb = self.recvEvent, conn_cbs = (self.aConn,), disc_cbs = (self.aDisc,), \
          fail_cbs = (self.aDisc,), dead_cbs = (self.aDead,))
        self.uaA.kaInterval = self.global_config['keepalive_ans']
        self.state = CCStateIdle
        self.uaO = None
        self.routes = []
        self.remote_ip = remote_ip
        self.source = source
        self.pass_headers = pass_headers

    def recvEvent(self, event, ua):
        if ua == self.uaA:
            if self.state == CCStateIdle:
                if not isinstance(event, CCEventTry):
                    # Some weird event received
                    self.uaA.recvEvent(CCEventDisconnect(rtime = event.rtime))
                    return
                self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name = event.getData()
                self.cGUID = cGUID.hexForm()
                if self.cld == None:
                    self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (1)'), rtime = event.rtime))
                    self.state = CCStateDead
                    return
                if body != None and self.global_config.has_key('_allowed_pts'):
                    try:
                        body.parse()
                    except:
                        self.uaA.recvEvent(CCEventFail((400, 'Malformed SDP Body'), rtime = event.rtime))
                        self.state = CCStateDead
                        return
                    allowed_pts = self.global_config['_allowed_pts']
                    mbody = body.content.sections[0].m_header
                    if mbody.transport.lower() == 'rtp/avp':
                        old_len = len(mbody.formats)
                        mbody.formats = [x for x in mbody.formats if x in allowed_pts]
                        if len(mbody.formats) == 0:
                            self.uaA.recvEvent(CCEventFail((488, 'Not Acceptable Here')))
                            self.state = CCStateDead
                            return
                        if old_len > len(mbody.formats):
                            body.content.sections[0].optimize_a()
                if self.cld.startswith('nat-'):
                    self.cld = self.cld[4:]
                    if body != None:
                        body.content += 'a=nated:yes\r\n'
                    event.data = (self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name)
                if self.global_config.has_key('static_tr_in'):
                    self.cld = re_replace(self.global_config['static_tr_in'], self.cld)
                    event.data = (self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name)
                if self.global_config.has_key('_rtp_proxy_clients'):
                    self.rtp_proxy_session = Rtp_proxy_session(self.global_config, call_id = self.cId, \
                      notify_socket = global_config['b2bua_socket'], \
                      notify_tag = quote('r %s' % str(self.id)))
                    self.rtp_proxy_session.callee_raddress = (self.remote_ip, 5060)
                self.eTry = event
                self.state = CCStateWaitRoute
                if not self.global_config['auth_enable']:
                    self.username = self.remote_ip
                    self.rDone(((), 0))
                elif auth == None or auth.username == None or len(auth.username) == 0:
                    self.username = self.remote_ip
                    self.auth_proc = self.global_config['_radius_client'].do_auth(self.remote_ip, self.cli, self.cld, self.cGUID, \
                      self.cId, self.remote_ip, self.rDone)
                else:
                    self.username = auth.username
                    self.auth_proc = self.global_config['_radius_client'].do_auth(auth.username, self.cli, self.cld, self.cGUID, 
                      self.cId, self.remote_ip, self.rDone, auth.realm, auth.nonce, auth.uri, auth.response)
                return
            if self.state not in (CCStateARComplete, CCStateConnected, CCStateDisconnecting) or self.uaO == None:
                return
            self.uaO.recvEvent(event)
        else:
            if (isinstance(event, CCEventFail) or isinstance(event, CCEventDisconnect)) and self.state == CCStateARComplete and \
              (isinstance(self.uaA.state, UasStateTrying) or isinstance(self.uaA.state, UasStateRinging)) and len(self.routes) > 0:
                if isinstance(event, CCEventFail):
                    code = event.getData()[0]
                else:
                    code = None
                if code == None or code not in self.huntstop_scodes:
                    self.placeOriginate(self.routes.pop(0))
                    return
            self.uaA.recvEvent(event)

    def rDone(self, results):
        # Check that we got necessary result from Radius
        if len(results) != 2 or results[1] != 0:
            if isinstance(self.uaA.state, UasStateTrying):
                if self.challenge != None:
                    event = CCEventFail((401, 'Unauthorized'))
                    event.extra_header = self.challenge
                else:
                    event = CCEventFail((403, 'Auth Failed'))
                self.uaA.recvEvent(event)
                self.state = CCStateDead
            return
        if self.global_config['acct_enable']:
            self.acctA = RadiusAccounting(self.global_config, 'answer', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctA.ms_precision = self.global_config.getdefault('precise_acct', False)
            self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip)
        else:
            self.acctA = FakeAccounting()
        # Check that uaA is still in a valid state, send acct stop
        if not isinstance(self.uaA.state, UasStateTrying):
            self.acctA.disc(self.uaA, time(), 'caller')
            return
        cli = [x[1][4:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')]
        if len(cli) > 0:
            self.cli = cli[0]
            if len(self.cli) == 0:
                self.cli = None
        caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
        if len(caller_name) > 0:
            self.caller_name = caller_name[0]
            if len(self.caller_name) == 0:
                self.caller_name = None
        credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
        if len(credit_time) > 0:
            global_credit_time = int(credit_time[0][1])
        else:
            global_credit_time = None
        if not self.global_config.has_key('static_route'):
            routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
            if len(routing) == 0:
                self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
                self.state = CCStateDead
                return
            routing = [x[1][8:].split(';') for x in routing]
        else:
            routing = [self.global_config['static_route'].split(';')]
        rnum = 0
        for route in routing:
            rnum += 1
            if route[0].find('@') != -1:
                cld, host = route[0].split('@', 1)
                if len(cld) == 0:
                    # Allow CLD to be forcefully removed by sending `Routing:@host' entry,
                    # as opposed to the Routing:host, which means that CLD should be obtained
                    # from the incoming call leg.
                    cld = None
            else:
                cld = self.cld
                host = route[0]
            credit_time = global_credit_time
            expires = None
            no_progress_expires = None
            forward_on_fail = False
            user = None
            passw = None
            cli = self.cli
            parameters = {}
            parameters['extra_headers'] = self.pass_headers[:]
            for a, v in [x.split('=', 1) for x in route[1:]]:
                if a == 'credit-time':
                    credit_time = int(v)
                    if credit_time < 0:
                        credit_time = None
                elif a == 'expires':
                    expires = int(v)
                    if expires < 0:
                        expires = None
                elif a == 'hs_scodes':
                    parameters['huntstop_scodes'] = tuple([int(x) for x in v.split(',') if len(x.strip()) > 0])
                elif a == 'np_expires':
                    no_progress_expires = int(v)
                    if no_progress_expires < 0:
                        no_progress_expires = None
                elif a == 'forward_on_fail':
                    forward_on_fail = True
                elif a == 'auth':
                    user, passw = v.split(':', 1)
                elif a == 'cli':
                    cli = v
                    if len(cli) == 0:
                        cli = None
                elif a == 'cnam':
                    caller_name = unquote(v)
                    if len(caller_name) == 0:
                        caller_name = None
                    parameters['caller_name'] = caller_name
                elif a == 'ash':
                    ash = SipHeader(unquote(v))
                    parameters['extra_headers'].append(ash)
                elif a == 'rtpp':
                    parameters['rtpp'] = (int(v) != 0)
                elif a == 'gt':
                    timeout, skip = v.split(',', 1)
                    parameters['group_timeout'] = (int(timeout), rnum + int(skip))
                elif a == 'op':
                    host_port = v.split(':', 1)
                    if len(host_port) == 1:
                        parameters['outbound_proxy'] = (v, 5060)
                    else:
                        parameters['outbound_proxy'] = (host_port[0], int(host_port[1]))
                else:
                    parameters[a] = v
            if self.global_config.has_key('max_credit_time'):
                if credit_time == None or credit_time > self.global_config['max_credit_time']:
                    credit_time = self.global_config['max_credit_time']
            if credit_time == 0 or expires == 0:
                continue
            self.routes.append((rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, \
              passw, cli, parameters))
            #print 'Got route:', host, cld
        if len(self.routes) == 0:
            self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
            self.state = CCStateDead
            return
        self.state = CCStateARComplete
        self.placeOriginate(self.routes.pop(0))

    def placeOriginate(self, args):
        cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData()
        rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, passw, cli, \
          parameters = args
        self.huntstop_scodes = parameters.get('huntstop_scodes', ())
        if self.global_config.has_key('static_tr_out'):
            cld = re_replace(self.global_config['static_tr_out'], cld)
        if host == 'sip-ua':
            host = self.source[0]
            port = self.source[1]
        else:
            if host.startswith('['):
                # IPv6
                host = host.split(']', 1)
                port = host[1].split(':', 1)
                host = host[0] + ']'
                if len(port) > 1:
                    port = int(port[1])
                else:
                    port = SipConf.default_port
            else:
                # IPv4
                host = host.split(':', 1)
                if len(host) > 1:
                    port = int(host[1])
                else:
                    port = SipConf.default_port
                host = host[0]
        if not forward_on_fail and self.global_config['acct_enable']:
            self.acctO = RadiusAccounting(self.global_config, 'originate', \
              send_start = self.global_config['start_acct_enable'], lperiod = \
              self.global_config.getdefault('alive_acct_int', None))
            self.acctO.ms_precision = self.global_config.getdefault('precise_acct', False)
            self.acctO.setParams(parameters.get('bill-to', self.username), parameters.get('bill-cli', cli), \
              parameters.get('bill-cld', cld), self.cGUID, self.cId, host)
        else:
            self.acctO = None
        self.acctA.credit_time = credit_time
        conn_handlers = [self.oConn]
        disc_handlers = []
        if not forward_on_fail and self.global_config['acct_enable']:
            disc_handlers.append(self.acctO.disc)
        self.uaO = UA(self.global_config, self.recvEvent, user, passw, (host, port), credit_time, tuple(conn_handlers), \
          tuple(disc_handlers), tuple(disc_handlers), dead_cbs = (self.oDead,), expire_time = expires, \
          no_progress_time = no_progress_expires, extra_headers = parameters.get('extra_headers', None))
        if self.source != parameters.get('outbound_proxy', None):
            self.uaO.outbound_proxy = parameters.get('outbound_proxy', None)
        if self.rtp_proxy_session != None and parameters.get('rtpp', True):
            self.uaO.on_local_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
            self.uaO.on_remote_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
            self.rtp_proxy_session.caller_raddress = (host, port)
            if body != None:
                body = body.getCopy()
                body.content += 'a=nortpproxy:yes\r\n'
            self.proxied = True
        self.uaO.kaInterval = self.global_config['keepalive_orig']
        if parameters.has_key('group_timeout'):
            timeout, skipto = parameters['group_timeout']
            Timeout(self.group_expires, timeout, 1, skipto)
        if self.global_config.getdefault('hide_call_id', False):
            cId = SipCallId(md5(str(cId)).hexdigest() + ('-b2b_%d' % rnum))
        else:
            cId += '-b2b_%d' % rnum
        event = CCEventTry((cId, cGUID, cli, cld, body, auth, \
          parameters.get('caller_name', self.caller_name)))
        event.reason = self.eTry.reason
        self.uaO.recvEvent(event)

    def disconnect(self, rtime = None):
        self.uaA.disconnect(rtime = rtime)

    def oConn(self, ua, rtime, origin):
        if self.acctO != None:
            self.acctO.conn(ua, rtime, origin)

    def aConn(self, ua, rtime, origin):
        self.state = CCStateConnected
        self.acctA.conn(ua, rtime, origin)

    def aDisc(self, ua, rtime, origin, result = 0):
        if self.state == CCStateWaitRoute and self.auth_proc != None:
            self.auth_proc.cancel()
            self.auth_proc = None
        if self.uaO != None and self.state != CCStateDead:
            self.state = CCStateDisconnecting
        else:
            self.state = CCStateDead
        if self.acctA != None:
            self.acctA.disc(ua, rtime, origin, result)
        self.rtp_proxy_session = None

    def aDead(self, ua):
        if (self.uaO == None or isinstance(self.uaO.state, UaStateDead)):
            if self.global_config['_cmap'].debug_mode:
                print('garbadge collecting', self)
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def oDead(self, ua):
        if ua == self.uaO and isinstance(self.uaA.state, UaStateDead):
            if self.global_config['_cmap'].debug_mode:
                print('garbadge collecting', self)
            self.acctA = None
            self.acctO = None
            self.global_config['_cmap'].ccmap.remove(self)

    def group_expires(self, skipto):
        if self.state != CCStateARComplete or len(self.routes) == 0 or self.routes[0][0] > skipto or \
          (not isinstance(self.uaA.state, UasStateTrying) and not isinstance(self.uaA.state, UasStateRinging)):
            return
        # When the last group in the list has timeouted don't disconnect
        # the current attempt forcefully. Instead, make sure that if the
        # current originate call leg fails no more routes will be
        # processed.
        if skipto == self.routes[-1][0] + 1:
            self.routes = []
            return
        while self.routes[0][0] != skipto:
            self.routes.pop(0)
        self.uaO.disconnect()
Пример #9
0
 def rDone(self, results):
     # Check that we got necessary result from Radius
     if len(results) != 2 or results[1] != 0:
         if isinstance(self.uaA.state, UasStateTrying):
             if self.challenge != None:
                 event = CCEventFail((401, 'Unauthorized'))
                 event.extra_header = self.challenge
             else:
                 event = CCEventFail((403, 'Auth Failed'))
             self.uaA.recvEvent(event)
             self.state = CCStateDead
         return
     if self.global_config['acct_enable']:
         self.acctA = RadiusAccounting(self.global_config, 'answer', \
           send_start = self.global_config['start_acct_enable'], lperiod = \
           self.global_config.getdefault('alive_acct_int', None))
         self.acctA.ms_precision = self.global_config.getdefault('precise_acct', False)
         self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip)
     else:
         self.acctA = FakeAccounting()
     # Check that uaA is still in a valid state, send acct stop
     if not isinstance(self.uaA.state, UasStateTrying):
         self.acctA.disc(self.uaA, time(), 'caller')
         return
     cli = [x[1][4:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')]
     if len(cli) > 0:
         self.cli = cli[0]
         if len(self.cli) == 0:
             self.cli = None
     caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
     if len(caller_name) > 0:
         self.caller_name = caller_name[0]
         if len(self.caller_name) == 0:
             self.caller_name = None
     credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
     if len(credit_time) > 0:
         global_credit_time = int(credit_time[0][1])
     else:
         global_credit_time = None
     if not self.global_config.has_key('static_route'):
         routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
         if len(routing) == 0:
             self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
             self.state = CCStateDead
             return
         routing = [x[1][8:].split(';') for x in routing]
     else:
         routing = [self.global_config['static_route'].split(';')]
     rnum = 0
     for route in routing:
         rnum += 1
         if route[0].find('@') != -1:
             cld, host = route[0].split('@', 1)
             if len(cld) == 0:
                 # Allow CLD to be forcefully removed by sending `Routing:@host' entry,
                 # as opposed to the Routing:host, which means that CLD should be obtained
                 # from the incoming call leg.
                 cld = None
         else:
             cld = self.cld
             host = route[0]
         credit_time = global_credit_time
         expires = None
         no_progress_expires = None
         forward_on_fail = False
         user = None
         passw = None
         cli = self.cli
         parameters = {}
         parameters['extra_headers'] = self.pass_headers[:]
         for a, v in [x.split('=', 1) for x in route[1:]]:
             if a == 'credit-time':
                 credit_time = int(v)
                 if credit_time < 0:
                     credit_time = None
             elif a == 'expires':
                 expires = int(v)
                 if expires < 0:
                     expires = None
             elif a == 'hs_scodes':
                 parameters['huntstop_scodes'] = tuple([int(x) for x in v.split(',') if len(x.strip()) > 0])
             elif a == 'np_expires':
                 no_progress_expires = int(v)
                 if no_progress_expires < 0:
                     no_progress_expires = None
             elif a == 'forward_on_fail':
                 forward_on_fail = True
             elif a == 'auth':
                 user, passw = v.split(':', 1)
             elif a == 'cli':
                 cli = v
                 if len(cli) == 0:
                     cli = None
             elif a == 'cnam':
                 caller_name = unquote(v)
                 if len(caller_name) == 0:
                     caller_name = None
                 parameters['caller_name'] = caller_name
             elif a == 'ash':
                 ash = SipHeader(unquote(v))
                 parameters['extra_headers'].append(ash)
             elif a == 'rtpp':
                 parameters['rtpp'] = (int(v) != 0)
             elif a == 'gt':
                 timeout, skip = v.split(',', 1)
                 parameters['group_timeout'] = (int(timeout), rnum + int(skip))
             elif a == 'op':
                 host_port = v.split(':', 1)
                 if len(host_port) == 1:
                     parameters['outbound_proxy'] = (v, 5060)
                 else:
                     parameters['outbound_proxy'] = (host_port[0], int(host_port[1]))
             else:
                 parameters[a] = v
         if self.global_config.has_key('max_credit_time'):
             if credit_time == None or credit_time > self.global_config['max_credit_time']:
                 credit_time = self.global_config['max_credit_time']
         if credit_time == 0 or expires == 0:
             continue
         self.routes.append((rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, \
           passw, cli, parameters))
         #print 'Got route:', host, cld
     if len(self.routes) == 0:
         self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
         self.state = CCStateDead
         return
     self.state = CCStateARComplete
     self.placeOriginate(self.routes.pop(0))
Пример #10
0
class CallController(object):
    id = 1
    uaA = None
    uaO = None
    state = None
    cId = None
    cld = None
    eTry = None
    routes = None
    remote_ip = None
    source = None
    acctA = None
    acctO = None
    global_config = None
    rtp_proxy_session = None
    huntstop_scodes = None
    pass_headers = None
    auth_proc = None
    proxied = False
    challenge = None

    def __init__(self, remote_ip, source, global_config, pass_headers, username = None):
        self.id = CallController.id
        CallController.id += 1
        self.global_config = global_config
        if remote_ip:
            self.uaA = UA(self.global_config, event_cb = self.recvEvent, conn_cbs = (self.aConn,), disc_cbs = (self.aDisc,), \
              fail_cbs = (self.aDisc,), dead_cbs = (self.aDead,))
            self.uaA.kaInterval = self.global_config['ka_ans']
        self.state = CCStateIdle
        self.uaO = None
        self.routes = []
        self.remote_ip = remote_ip
        self.source = source
        self.pass_headers = pass_headers
        self.username = username

    def recvEvent(self, event, ua):
        who = 'uaA' if ua == self.uaA else 'uaO'
        print who, 'received event', event, 'in state', self.state.sname
        if self.uaA:
            print 'self.uaA.state:', self.uaA.state
        if self.uaO:
            print 'self.uaO.state:', self.uaO.state
        if ua == self.uaA:
            if self.state == CCStateIdle:
                if not isinstance(event, CCEventTry):
                    # Some weird event received
                    self.uaA.recvEvent(CCEventDisconnect(rtime = event.rtime))
                    return
                self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name = event.getData()
                print 'auth:', auth
                self.cGUID = cGUID.hexForm()
                if not self.cld:
                    self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (1)'), rtime = event.rtime))
                    self.state = CCStateDead
                    return
                if not body:
                    self.uaA.recvEvent(CCEventFail((500, 'Body-less INVITE is not supported'), rtime = event.rtime))
                    self.state = CCStateDead
                    return
                if self.global_config.has_key('allowed_pts'):
                    try:
                        body.parse()
                    except:
                        self.uaA.recvEvent(CCEventFail((400, 'Malformed SDP Body'), rtime = event.rtime))
                        self.state = CCStateDead
                        return
                    allowed_pts = self.global_config['allowed_pts']
                    mbody = body.content.sections[0].m_header
                    if mbody.transport.lower() == 'rtp/avp':
                        mbody.formats = [x for x in mbody.formats if x in allowed_pts]
                        if not mbody.formats:
                            self.uaA.recvEvent(CCEventFail((488, 'Not Acceptable Here')))
                            self.state = CCStateDead
                            return
                if self.cld.startswith('nat-'):
                    self.cld = self.cld[4:]
                    body.content += 'a=nated:yes\r\n'
                    event = CCEventTry((self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name), \
                      rtime = event.rtime, origin = event.origin)
                if self.global_config.has_key('static_tr_in'):
                    self.cld = re_replace(self.global_config['static_tr_in'], self.cld)
                    event = CCEventTry((self.cId, cGUID, self.cli, self.cld, body, auth, self.caller_name), \
                      rtime = event.rtime, origin = event.origin)
                if self.global_config.has_key('rtp_proxy_clients'):
                    self.rtp_proxy_session = Rtp_proxy_session(self.global_config, call_id = self.cId, \
                      notify_socket = global_config['b2bua_socket'], \
                      notify_tag = quote('r %s' % str(self.id)))
                self.eTry = event
                self.state = CCStateWaitRoute
                if not self.global_config['auth_enable']:
                    self.username = self.remote_ip
                    self.rDone(((), 0))
                elif not auth or not auth.username:
                    print 'setting username to remote ip ', self.remote_ip
                    self.username = self.remote_ip
                    self.auth_proc = self.global_config['radius_client'].do_auth(self.remote_ip, self.cli, self.cld, self.cGUID, \
                      self.cId, self.remote_ip, self.rDone)
                else:
                    print 'setting username auth username ', auth.username
                    self.username = auth.username
                    self.auth_proc = self.global_config['radius_client'].do_auth(auth.username, self.cli, self.cld, self.cGUID, 
                      self.cId, self.remote_ip, self.rDone, auth.realm, auth.nonce, auth.uri, auth.response)
                return
            if self.state == CCStateUpdatingA and isinstance(event, CCEventConnect):
                self.state = CCStateConnected
                #TODO: rad acct start
            if self.state == CCStateWaitRouteA and isinstance(event, CCEventConnect):
                # Command 'make call'.
                # The left phone answered.
                # An INVITE should be sent to the right phone.
                #TODO: move to a subroutine
                self.cli, self.cld = self.cld, self.cli
                body = self.uaA.rSDP #TODO: a get method()?
                print 'body:\n', body

                self.cId = SipCallId()
                self.caller_name = self.cli
                auth = None
                ev = CCEventTry((self.cId, self.cGUID, self.cli, self.cld, body, auth, self.cli), origin = self.cld)
                self.eTry = ev
                self.state = CCStateWaitRouteO
                if self.global_config['auth_enable']:
                    self.auth_proc = self.global_config['radius_client'].do_auth(self.cli, self.cli, self.cld, self.cGUID, \
                        self.cId, self.remote_ip, self.rDone)
                else:
                    self.rDone(((), 0))
            if self.state == CCStateDead and (isinstance(event, CCEventFail) or (isinstance(event, CCEventDisconnect))) and self.routes:
                if isinstance(event, CCEventFail):
                    code = event.getData()[0]
                else:
                    code = None
                if not code or code not in self.huntstop_scodes:
                    self.state = CCStateWaitRouteA
                    self.uaA = self.placeAnswer(self.routes.pop(0))
            if self.state not in (CCStateARComplete, CCStateConnected, CCStateDisconnecting) or not self.uaO:
                return
            self.uaO.recvEvent(event)
        else:
            if self.state == CCStateARComplete and isinstance(event, CCEventConnect) and not isinstance(self.uaO.state, UaStateConnected):
                if not self.global_config.has_key('rtp_proxy_clients'):
                    self.state = CCStateUpdatingA
                    body = self.uaO.rSDP #TODO: a get method()?
                    event = CCEventUpdate(body)
                    self.uaO.delayed_remote_sdp_update(event, body)
                else:
                    self.state = CCStateConnected
                return
            elif (isinstance(event, CCEventFail) or isinstance(event, CCEventDisconnect)) and self.state == CCStateARComplete and \
              (isinstance(self.uaA.state, UasStateTrying) or isinstance(self.uaA.state, UasStateRinging) \
              or isinstance(self.uaA.state, UaStateConnected)) and self.routes:
                if isinstance(event, CCEventFail):
                    code = event.getData()[0]
                else:
                    code = None
                if not code or code not in self.huntstop_scodes:
                    cb = self.oConnA if isinstance(self.uaA.state, UaStateConnected) else self.oConn
                    self.uaO = self.placeOriginate(self.routes.pop(0), cb)
                    return
            self.uaA.recvEvent(event)

    def rDone(self, results):
        # Check that we got necessary result from Radius
        if len(results) != 2 or results[1] != 0:
            if self.uaA and isinstance(self.uaA.state, UasStateTrying):
                if self.challenge:
                    event = CCEventFail((401, 'Unauthorized'))
                    event.extra_header = self.challenge
                else:
                    event = CCEventFail((403, 'Auth Failed'))
                self.uaA.recvEvent(event)
                self.state = CCStateDead
            return
        if not self.state == CCStateWaitRouteA and not self.state == CCStateWaitRouteO:
            if self.global_config['acct_enable']:
                print 'creating self.acctA'
                self.acctA = RadiusAccounting(self.global_config, 'answer', \
                  send_start = self.global_config['start_acct_enable'], itime = self.eTry.rtime)
                self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip)
            else:
                self.acctA = FakeAccounting()
        # Check that uaA is still in a valid state, send acct stop
        if not self.state == CCStateWaitRouteA and not self.state == CCStateWaitRouteO and not isinstance(self.uaA.state, UasStateTrying):
            print 'sending acct stop A 2'
            self.acctA.disc(self.uaA, time(), 'caller')
            return
        cli = [x[1][4:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CLI:')]
        if cli:
            self.cli = cli[0]
            if not self.cli:
                self.cli = None
        caller_name = [x[1][5:] for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('CNAM:')]
        if caller_name:
            self.caller_name = caller_name[0]
            if not self.caller_name:
                self.caller_name = None
        credit_time = [x for x in results[0] if x[0] == 'h323-credit-time']
        if credit_time:
            global_credit_time = int(credit_time[0][1])
        else:
            global_credit_time = None
        if not self.global_config.has_key('static_route'):
            routing = [x for x in results[0] if x[0] == 'h323-ivr-in' and x[1].startswith('Routing:')]
            if not routing:
                if self.uaA:
                    self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (2)')))
                self.state = CCStateDead
                return
            routing = [x[1][8:].split(';') for x in routing]
        else:
            routing = [self.global_config['static_route'].split(';')]
        rnum = 0
        self.routes = []
        for route in routing:
            rnum += 1
            if '@' in route[0]:
                cld, host = route[0].split('@')
                if not cld:
                    # Allow CLD to be forcefully removed by sending `Routing:@host' entry,
                    # as opposed to the Routing:host, which means that CLD should be obtained
                    # from the incoming call leg.
                    cld = None
            else:
                cld = self.cld
                host = route[0]
            credit_time = global_credit_time
            expires = None
            no_progress_expires = None
            forward_on_fail = False
            user = None
            passw = None
            cli = self.cli
            parameters = {}
            parameters['extra_headers'] = self.pass_headers[:]
            for a, v in [x.split('=') for x in route[1:]]:
                if a == 'credit-time':
                    credit_time = int(v)
                    if credit_time < 0:
                        credit_time = None
                elif a == 'expires':
                    expires = int(v)
                    if expires < 0:
                        expires = None
                elif a == 'hs_scodes':
                    parameters['huntstop_scodes'] = tuple([int(x) for x in v.split(',') if len(x.strip()) > 0])
                elif a == 'np_expires':
                    no_progress_expires = int(v)
                    if no_progress_expires < 0:
                        no_progress_expires = None
                elif a == 'forward_on_fail':
                    forward_on_fail = True
                elif a == 'auth':
                    user, passw = v.split(':', 1)
                elif a == 'cli':
                    cli = v
                    if not cli:
                        cli = None
                elif a == 'cnam':
                    caller_name = unquote(v)
                    if not caller_name:
                        caller_name = None
                    parameters['caller_name'] = caller_name
                elif a == 'ash':
                    ash = SipHeader(unquote(v))
                    parameters['extra_headers'].append(ash)
                elif a == 'rtpp':
                    parameters['rtpp'] = (int(v) != 0)
                elif a == 'gt':
                    timeout, skip = v.split(',', 1)
                    parameters['group_timeout'] = (int(timeout), rnum + int(skip))
                else:
                    parameters[a] = v
            if self.global_config.has_key('max_credit_time'):
                if credit_time == None or credit_time > self.global_config['max_credit_time']:
                    credit_time = self.global_config['max_credit_time']
            if credit_time == 0 or expires == 0:
                continue
            print 'append route, cli=' + cli + ', cld=' + cld
            self.routes.append((rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, \
              passw, cli, parameters))
            #print 'Got route:', host, cld
        if not self.routes:
            if self.uaA:
                self.uaA.recvEvent(CCEventFail((500, 'Internal Server Error (3)')))
            self.state = CCStateDead
            return
        print 'self.state:', self.state
        if self.state == CCStateWaitRouteA:
            # Make call. Got radius auth for the A phone.
            self.uaA = self.placeAnswer(self.routes.pop(0))
        elif self.state == CCStateWaitRouteO:
            # Make call. Got radius auth for the O phone.
            self.state = CCStateARComplete
            print 'self.eTry.getData():', self.eTry.getData()
            self.uaO = self.placeOriginate(self.routes.pop(0), self.oConnA)
        else:
            # Regular call. Got radius auth for the O phone.
            self.state = CCStateARComplete
            self.uaO = self.placeOriginate(self.routes.pop(0), self.oConn)

    def placeAnswer(self, args):
        cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData() #TODO: move to a subroutine
        rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, passw, cli, \
          parameters = args
        self.huntstop_scodes = parameters.get('huntstop_scodes', ())
        if self.global_config.has_key('static_tr_out'):
            cld = re_replace(self.global_config['static_tr_out'], cld)
        if host == 'sip-ua':
            host = self.source[0]
            port = self.source[1]
        else:
            host = host.split(':', 1)
            if len(host) > 1:
                port = int(host[1])
            else:
                port = SipConf.default_port
            host = host[0]
        if not forward_on_fail and self.global_config['acct_enable']:
            self.acctA = RadiusAccounting(self.global_config, 'answer', send_start = self.global_config['start_acct_enable'])
            self.acctA.setParams(parameters.get('bill-to', self.username), parameters.get('bill-cli', cli), \
              parameters.get('bill-cld', cld), self.cGUID, self.cId, host, credit_time)
            print 'adding acct stop cb'
        else:
            self.acctA = FakeAccounting()
        ua = UA(self.global_config, self.recvEvent, user, passw, (host, port), credit_time, \
          (self.aConnA,), (self.aDisc,), (self.aDisc,), dead_cbs = (self.aDead,), \
          expire_time = expires, no_progress_time = no_progress_expires, \
          extra_headers = parameters.get('extra_headers', None))
        if self.rtp_proxy_session and parameters.get('rtpp', True):
            print 'placeAnswer() registering on sdp change callbacks'
            ua.on_local_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
            ua.on_remote_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
            body = body.getCopy()
            body.content += 'a=nortpproxy:yes\r\n'
            self.proxied = True
        ua.kaInterval = self.global_config['ka_ans'] #TODO: is this okay for uaA?
        if parameters.has_key('group_timeout'):
            timeout, skipto = parameters['group_timeout']
            Timeout(self.group_expires, timeout, 1, skipto)
        ua.recvEvent(CCEventTry((cId + '-b2b_%d' % rnum, cGUID, cli, cld, body, auth, \
          parameters.get('caller_name', self.caller_name))))
        return ua

    def placeOriginate(self, args, conn_cbs):
        cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData()
        rnum, host, cld, credit_time, expires, no_progress_expires, forward_on_fail, user, passw, cli, \
          parameters = args
        self.huntstop_scodes = parameters.get('huntstop_scodes', ())
        if self.global_config.has_key('static_tr_out'):
            cld = re_replace(self.global_config['static_tr_out'], cld)
        if host == 'sip-ua':
            host = self.source[0]
            port = self.source[1]
        else:
            host = host.split(':', 1)
            if len(host) > 1:
                port = int(host[1])
            else:
                port = SipConf.default_port
            host = host[0]
        disc_cbs = []
        if not forward_on_fail and self.global_config['acct_enable']:
            self.acctO = RadiusAccounting(self.global_config, 'originate', send_start = self.global_config['start_acct_enable'])
            self.acctO.setParams(parameters.get('bill-to', self.username), parameters.get('bill-cli', cli), \
              parameters.get('bill-cld', cld), self.cGUID, self.cId, host, credit_time)
            print 'adding acct stop cb'
            disc_cbs.append(self.acctO.disc)
        else:
            self.acctO = None
        self.acctA.credit_time = credit_time
        ua = UA(self.global_config, self.recvEvent, user, passw, (host, port), credit_time, \
          (conn_cbs,), tuple(disc_cbs), tuple(disc_cbs), dead_cbs = (self.oDead,), \
          expire_time = expires, no_progress_time = no_progress_expires, \
          extra_headers = parameters.get('extra_headers', None))
        if self.rtp_proxy_session and parameters.get('rtpp', True):
            ua.on_local_sdp_change = self.rtp_proxy_session.on_caller_sdp_change
            ua.on_remote_sdp_change = self.rtp_proxy_session.on_callee_sdp_change
            body = body.getCopy()
            body.content += 'a=nortpproxy:yes\r\n'
            self.proxied = True
        ua.kaInterval = self.global_config['ka_orig']
        if parameters.has_key('group_timeout'):
            timeout, skipto = parameters['group_timeout']
            Timeout(self.group_expires, timeout, 1, skipto)
        ua.recvEvent(CCEventTry((cId + '-b2b_%d' % rnum, cGUID, cli, cld, body, auth, \
          parameters.get('caller_name', self.caller_name))))
        return ua

    def disconnect(self, rtime = None):
        self.uaA.disconnect(rtime = rtime)

    def oConn(self, ua, rtime, origin):
        print 'oConn(): self.state:', self.state, ', self.uaA.state:', self.uaA.state
#        traceback.print_stack(file = sys.stdout)
        if self.acctO:
            self.acctO.conn(ua, rtime, origin)

    def oConnA(self, ua, rtime, origin):
        print 'oConnA(): self.state:', self.state, ', self.uaA.state:', self.uaA.state
#        traceback.print_stack(file = sys.stdout)
        # make call.
        # The right phone answered.
        # A re-INVITE should be sent to the left phone.
        if self.acctO:
            self.acctO.conn(ua, rtime, origin)
        if not self.global_config.has_key('rtp_proxy_clients'):
            self.state = CCStateUpdatingA
            body = self.uaO.rSDP #TODO: a get method()?
            event = CCEventUpdate(body)
            self.uaO.delayed_remote_sdp_update(event, body)
        else:
            self.state = CCStateConnected

    def aConn(self, ua, rtime, origin):
        print 'aConn(): self.state:', self.state
#        traceback.print_stack(file = sys.stdout)
        self.state = CCStateConnected
        self.acctA.conn(ua, rtime, origin)

    def aConnA(self, ua, rtime, origin):
        print 'aConnA(): self.state:', self.state, ', self.uaA.state:', self.uaA.state
#        traceback.print_stack(file = sys.stdout)
        # Command 'make call'.
        # The left phone answered.
        # An INVITE should be sent to the right phone.
        #TODO: move to a subroutine
        self.acctA.conn(ua, rtime, origin)

    def aDisc(self, ua, rtime, origin, result = 0):
        print 'aDisc()'
        if self.state == CCStateWaitRoute and self.auth_proc:
            self.auth_proc.cancel()
            self.auth_proc = None
        if self.uaO and self.state != CCStateDead:
            self.state = CCStateDisconnecting
        else:
            self.state = CCStateDead
        if self.acctA:
            print 'sending acct stop A'
            self.acctA.disc(ua, rtime, origin, result)
            print 'sent acct stop A'
        if self.uaO:
            self.uaO.recvEvent(CCEventDisconnect(rtime = rtime, origin = origin))
#        self.rtp_proxy_session = None #TODO: what for?

    def aDead(self, ua):
        if (not self.uaO or isinstance(self.uaO.state, UaStateDead)):
            if self.global_config['cmap'].debug_mode:
                print 'garbadge collecting', self
            self.acctA = None
            self.acctO = None
            if self in self.global_config['cmap'].ccmap:
                self.global_config['cmap'].ccmap.remove(self)

    def oDead(self, ua):
        if ua == self.uaO and isinstance(self.uaA.state, UaStateDead):
            if self.global_config['cmap'].debug_mode:
                print 'garbadge collecting', self
            self.acctA = None
            self.acctO = None
            self.global_config['cmap'].ccmap.remove(self)

    def group_expires(self, skipto):
        if self.state != CCStateARComplete or len(self.routes) == 0 or self.routes[0][0] > skipto or \
          (not isinstance(self.uaA.state, UasStateTrying) and not isinstance(self.uaA.state, UasStateRinging)):
            return
        # When the last group in the list has timeouted don't disconnect
        # the current attempt forcefully. Instead, make sure that if the
        # current originate call leg fails no more routes will be
        # processed.
        if skipto == self.routes[-1][0] + 1:
            self.routes = []
            return
        while self.routes[0][0] != skipto:
            self.routes.pop(0)
        self.uaO.disconnect()

    def makeCall(self, fr, to):
        self.cGUID = SipCiscoGUID()
        self.cli = to 
        self.cld = fr
        self.cId = SipCallId()
        self.caller_name = self.cli
        auth = None
        if self.global_config.has_key('rtp_proxy_clients'):
            self.rtp_proxy_session = Rtp_proxy_session(self.global_config, call_id = self.cId, \
              notify_socket = global_config['b2bua_socket'], \
              notify_tag = quote('r %s' % str(self.id)))
        ev = CCEventTry((self.cId, self.cGUID, self.cli, self.cld, MsgBody(), auth, self.cli), origin = self.cld)
        self.eTry = ev
        self.state = CCStateWaitRouteA
        if self.global_config['auth_enable']:
            self.auth_proc = self.global_config['radius_client'].do_auth(fr, self.cli, self.cld, self.cGUID, \
                self.cId, self.remote_ip, self.rDone)
        else:
            self.rDone(((), 0))
        return self.cGUID