def post(self, request, format=None): needed_fields = ["imsi", "trans", "dest"] if not all(i in request.POST for i in needed_fields): return Response("Missing Args", status=status.HTTP_400_BAD_REQUEST) imsi = request.data['imsi'] ret = transaction = request.data['trans'] dest = request.data['dest'] # Kludge! try: endaga_sub.get_imsi_from_number(dest) local = True except BaseException: local = False if not local and is_dest_globe(dest): # replace first word with globe keyword transaction = 'globe_' + transaction.split('_')[1] # check first if subscriber has subscribed to U/B/D promos # follows priority order # we also have to check that the subscriber has allocation # for the transaction. For example: if a user wants to do # outside sms but only has promo allocation for unli local sms, then # we should apply regular rates for promo_type in ['U', 'B', 'D', 'G']: query = transaction + '__gt' listed_promos = PromoSubscription.objects.filter( contact__imsi__exact=imsi, promo__promo_type=promo_type, **{ query: 0 }).order_by('date_expiration') if listed_promos: if promo_type == 'G': # check destination number try: GroupMembers.objects.get(group__owner__exact=imsi, user__callerid=dest) break except BaseException: pass # destination not a group member, do nothing else: break # No promo subscriptions else: return Response(ret, status=status.HTTP_200_OK) # NOTE: get the first item from queryset since that would expire first key = 'listed_promos[0].%s' % transaction if eval(key) > 0: ret = '%s_%s' % (promo_type, transaction) return Response(ret, status=status.HTTP_200_OK)
def fsapi(session, stream, env, msisdn): """Handle FS API requests. Args: msisdn: a subscriber's number """ try: imsi = str(subscriber.get_imsi_from_number(msisdn, False)) except SubscriberNotFound: imsi = '' consoleLog('info', "Returned FSAPI: " + imsi + "\n") stream.write(imsi)
def chat(message, msisdn): """Handle chat requests. Args: msisdn: a subscriber's number """ try: imsi = str(subscriber.get_imsi_from_number(msisdn, False)) except SubscriberNotFound: imsi = '' consoleLog('info', "Returned Chat: " + imsi + "\n") message.chat_execute('set', '_openbts_ret=%s' % imsi)
def bill(self, to_number, from_number): try: if from_number == DASHBOARD_FROM_NUMBER: self.tariff_type = 'free_sms' tariff = billing.get_sms_cost(self.tariff_type, destination_number=to_number) username = subscriber.get_imsi_from_number(to_number) if username: reason = 'SMS from %s to %s (incoming_sms)' % ( from_number, username) old_balance = subscriber.get_account_balance(username) subscriber.subtract_credit(username, str(int(tariff))) events.create_sms_event(username, old_balance, tariff, reason, to_number, from_number=from_number) except Exception as e: logger.error("Endaga bill error:" + traceback.format_exc(e))
def fsapi(session, stream, env, msisdn): """Handle FS API requests. Args: msisdn: a subscriber's number """ try: imsi = str(subscriber.get_imsi_from_number(msisdn)) except SubscriberNotFound: # If the MSISDN isn't even in the sub registry, it's not camped consoleLog('info', "Returned FSAPI: FALSE\n") stream.write('FALSE') return camped = [str(_['imsi']) for _ in bts.active_subscribers()] res = "TRUE" if imsi in camped else "FALSE" consoleLog('info', "Returned FSAPI: %s\n" % res) stream.write(res)
def chat(message, msisdn): """Handle chat requests. Args: msisdn: a subscriber's number """ try: imsi = str(subscriber.get_imsi_from_number(msisdn)) except SubscriberNotFound: # If the MSISDN isn't even in the sub registry, it's not camped consoleLog('info', "Returned Chat: FALSE\n") message.chat_execute('set', '_openbts_ret=FALSE') return camped = [str(_['imsi']) for _ in bts.active_subscribers()] res = "TRUE" if imsi in camped else "FALSE" consoleLog('info', "Returned Chat: %s\n" % res) message.chat_execute('set', '_openbts_ret=%s' % res)
def POST(self, command): """Handles certain POST commands.""" # Always send back these headers. headers = {'Content-type': 'text/plain'} # Validate the exact endpoint. valid_post_commands = ('deactivate_number', 'deactivate_subscriber') if command not in valid_post_commands: return web.NotFound() # Get the posted data and validate. There should be a 'jwt' key with # signed data. That dict should contain a 'number' key -- the one we # want to deactivate. data = web.input() jwt = data.get('jwt', None) if not jwt: return web.BadRequest() serializer = itsdangerous.JSONWebSignatureSerializer( self.conf['bts_secret']) try: jwt = serializer.loads(jwt) except itsdangerous.BadSignature: return web.BadRequest() if command == 'deactivate_number': if 'number' not in jwt: return web.BadRequest() # The params validated, deactivate the number. ValueError is # raised if this is the subscriber's last number. # The number should correspond to an IMSI or give a 404 try: imsi = subscriber.get_imsi_from_number(jwt['number']) subscriber.delete_number(imsi, jwt['number']) except SubscriberNotFound: return web.NotFound() except ValueError: return web.BadRequest() return web.ok(None, headers) elif command == 'deactivate_subscriber': if 'imsi' not in jwt: return web.BadRequest() # The number should correspond to an IMSI. try: subscriber.delete_subscriber(jwt['imsi']) except SubscriberNotFound: return web.NotFound() return web.ok(None, headers)
def handle_incoming(from_imsi, request): """Called externally by an FS script. Args: from_imsi: sender's IMSI request: a credit transfer or credit transfer confirmation request """ request = request.strip() # This parses a to_number (length 1 or more) and an amount that can # be formatted using a comma for a thousands seperator and a period for # the decimal place transfer_command = re.compile( r'^(?P<to_number>[0-9]+)' r'\*' r'(?P<amount>[0-9]*(?:,[0-9]{3})*(?:\.[0-9]*)?)$') transfer = transfer_command.match(request) confirm_command = re.compile(r'^(?P<confirm_code>[0-9]{%d})$' % int(config_db['code_length'])) confirm = confirm_command.match(request) _init_pending_transfer_db() if transfer: to_number, amount = transfer.groups() amount = freeswitch_strings.parse_credits(amount).amount_raw # Translate everything into IMSIs. try: to_imsi = subscriber.get_imsi_from_number(to_number) _, resp = process_transfer(from_imsi, to_imsi, amount) except SubscriberNotFound: resp = gt("Invalid phone number: %(number)s" % {'number': to_number}) elif confirm: # The code is the whole request, so no need for groups. code = request.strip() _, resp = process_confirm(from_imsi, code) else: # NOTE: Sent when the user tries to transfer credit with the wrong # format message. resp = gt("To transfer credit, reply with a message in the" " format 'NUMBER*AMOUNT'.") from_number = subscriber.get_numbers_from_imsi(from_imsi)[0] sms.send(str(from_number), str(config_db['app_number']), str(resp))
def POST(self): """Handles POST requests.""" data = web.input() if ('from_name' not in data or 'service_type' not in data or 'destination' not in data): raise web.BadRequest() # Process the CDR data. cost_in_credits = billing.get_sms_cost( data.service_type, destination_number=data.destination) try: old_balance = subscriber.get_account_balance(data.from_name) subscriber.subtract_credit(data.from_name, str(cost_in_credits)) except SubscriberNotFound: # The subscriber does not exist yet but has sent an SMS if data.service_type == 'free_sms': # But that is OK for a free service like provisioning old_balance = 0 else: raise reason = "SMS sent to %s (%s)" % (data.destination, data.service_type) events.create_sms_event( data.from_name, old_balance, cost_in_credits, reason, data.destination, from_imsi=data.from_name, from_number=data.from_number) # If this was an in-network event, find the cost for the recipient. We # can lookup the recipient by data.destination (the "to number"). if 'local' in data.service_type: recipient_imsi = subscriber.get_imsi_from_number(data.destination) old_balance = subscriber.get_account_balance(recipient_imsi) cost_in_credits = billing.get_sms_cost( 'local_recv_sms', destination_number=data.destination) subscriber.subtract_credit(recipient_imsi, str(cost_in_credits)) reason = "SMS received from %s (local_recv_sms)" % data.from_name events.create_sms_event( recipient_imsi, old_balance, cost_in_credits, reason, data.destination, from_imsi=data.from_name, from_number=data.from_number) # Return 200 OK. headers = { 'Content-type': 'text/plain' } raise web.OK(None, headers)
def process_cdr(self, cdr_xml): """Processes the XML CDR for the caller.""" cdr_dom = xml.parseString(cdr_xml) # Handle only b-legs for billing. origin = cdr_dom.getElementsByTagName("origination") if origin: return # Handle only b-legs for billing. # For our purposes, billsec is how long the call lasted. call_duration # captures the amount of time spent ringing, which we don't charge for, # so don't include here. Caller and callee are just used for logging # and reason statements. # TODO(matt): what happens if the tag does not exist? call_duration = int( get_tag_text( cdr_dom.getElementsByTagName("duration")[0].childNodes)) billsec = int( get_tag_text( cdr_dom.getElementsByTagName("billsec")[0].childNodes)) # In b-leg cdrs, there are multiple destinations -- the sip one (IMSI) # and the dialed one (MSISDN). We want the latter. callees = cdr_dom.getElementsByTagName("destination_number") callee = '' for c in callees: c = get_tag_text(c.childNodes) # NOT THE IMSI if c[0:4] != IMSI_PREFIX: callee = c break if callee[0] == "+": callee = callee[1:] hangupcause = get_hangup_cause(cdr_dom) # This is where we get the info we need to do billing. if len(cdr_dom.getElementsByTagName("service_type")) > 0: service_type = get_tag_text( cdr_dom.getElementsByTagName("service_type")[0].childNodes) # Get caller / callee info. See the 'CDR notes' doc in drive for # more info. from_imsi, from_number, to_imsi, to_number = 4 * [None] # We always get 'from_imsi' from the <username> tag with the # <caller_profile> parent element. If it's a BTS-originated call, # this will be an IMSI; otherwise, it'll be an MSISDN. if service_type not in ['incoming_call']: elements = cdr_dom.getElementsByTagName('username') for element in elements: if element.parentNode.nodeName == 'caller_profile': username = get_tag_text(element.childNodes) from_imsi = subscriber.get_imsi_from_username(username) break # Get 'from_number' (only available for outside and local calls). if service_type in ['outside_call', 'local_call', 'incoming_call']: elements = cdr_dom.getElementsByTagName('caller_id_name') for element in elements: if element.parentNode.nodeName == 'caller_profile': from_number = get_tag_text(element.childNodes) break # Get 'to_imsi' (only available for local/incoming calls). if service_type in ['local_call', 'incoming_call']: elements = cdr_dom.getElementsByTagName('callee_id_number') for element in elements: if element.parentNode.nodeName == 'caller_profile': callee_id = get_tag_text(element.childNodes) if callee_id[0:4] == IMSI_PREFIX: to_imsi = callee_id else: # callee_id_number in the CDR is MSISDN. to_imsi = subscriber.get_imsi_from_number( callee_id) break # Get 'to_number' (slightly different for local/incoming calls). if service_type in ['outside_call', 'free_call', 'error_call']: elements = cdr_dom.getElementsByTagName('destination_number') for element in elements: if element.parentNode.nodeName == 'caller_profile': to_number = get_tag_text(element.childNodes) break elif service_type in ['local_call', 'incoming_call']: elements = cdr_dom.getElementsByTagName('destination_number') for element in elements: if (element.parentNode.nodeName == 'originator_caller_profile'): to_number = get_tag_text(element.childNodes) break # Generate billing information for the caller, if the caller is # local to the BTS. if service_type != 'incoming_call': tariff = billing.get_service_tariff( service_type, 'call', destination_number=to_number) cost = billing.get_call_cost(billsec, service_type, destination_number=to_number) reason = "%s sec call to %s (%s/%s)" % ( billsec, to_number, service_type, hangupcause) old_balance = subscriber.get_account_balance(from_imsi) subscriber.subtract_credit(from_imsi, str(cost)) owner_imsi = from_imsi kind = service_type events.create_call_event(owner_imsi, from_imsi, from_number, to_imsi, to_number, old_balance, cost, kind, reason, tariff, call_duration, billsec) # Create a call record for the callee, if applicable. if service_type in ['local_call', 'incoming_call']: if service_type == 'local_call': service_type = 'local_recv_call' tariff = billing.get_service_tariff(service_type, 'call') cost = billing.get_call_cost(billsec, service_type) reason = "%d sec call from %s (%s/%s)" % ( billsec, from_number, service_type, hangupcause) # Note! This is different than how we bill for a caller -- # we're deducting from the 'to_imsi' (the callee) instead. old_balance = subscriber.get_account_balance(to_imsi) subscriber.subtract_credit(to_imsi, str(cost)) owner_imsi = to_imsi kind = service_type events.create_call_event(owner_imsi, from_imsi, from_number, to_imsi, to_number, old_balance, cost, kind, reason, tariff, call_duration, billsec) else: username = get_tag_text( cdr_dom.getElementsByTagName("username")[0].childNodes) from_imsi = subscriber.get_imsi_from_username(username) message = "No rate info for this call. (from: %s, billsec: %s)" % ( from_imsi, billsec) logger.error(message)