def setUp(self): self.script1 = MTInterceptorScript('abc') self.script2 = MTInterceptorScript('def') self.script3 = MTInterceptorScript('ghi') self.script4 = MTInterceptorScript('jkl') self.group100 = Group(100) self.user1 = User(1, self.group100, 'username', 'password') self.user2 = User(2, self.group100, 'username', 'password') self.mt_filter1 = [UserFilter(self.user1)] self.mt_filter2 = [DestinationAddrFilter('^10\d+')] self.transparent_filter = [TransparentFilter()] self.interceptor1 = StaticMTInterceptor(self.mt_filter1, self.script1) self.interceptor2 = StaticMTInterceptor(self.mt_filter2, self.script2) self.interceptor3 = StaticMTInterceptor(self.transparent_filter, self.script3) self.interceptor4 = DefaultInterceptor(self.script4) self.PDU_dst_1 = SubmitSM( source_addr=b'x', destination_addr=b'200', short_message=b'hello world', ) self.PDU_dst_2 = SubmitSM( source_addr=b'x', destination_addr=b'100', short_message=b'hello world', ) self.routable_matching_interceptor1 = RoutableSubmitSm( self.PDU_dst_1, self.user1) self.routable_matching_interceptor2 = RoutableSubmitSm( self.PDU_dst_2, self.user2) self.routable_notmatching_any = RoutableSubmitSm( self.PDU_dst_1, self.user2)
def setUp(self): self.connector1 = SmppClientConnector('abc') self.connector2 = SmppClientConnector('def') self.connector3 = SmppClientConnector('ghi') self.connector4 = SmppClientConnector('jkl') self.group100 = Group(100) self.user1 = User(1, self.group100, 'username', 'password') self.user2 = User(2, self.group100, 'username', 'password') self.mt_filter1 = [UserFilter(self.user1)] self.mt_filter2 = [DestinationAddrFilter('^10\d+')] self.transparent_filter = [TransparentFilter()] self.mt_route1 = StaticMTRoute(self.mt_filter1, self.connector1, 0.0) self.mt_route2 = StaticMTRoute(self.mt_filter2, self.connector2, 0.0) self.mt_route3 = StaticMTRoute(self.transparent_filter, self.connector3, 0.0) self.mt_route4 = DefaultRoute(self.connector4) self.PDU_dst_1 = SubmitSM( source_addr='x', destination_addr='200', short_message='hello world', ) self.PDU_dst_2 = SubmitSM( source_addr='x', destination_addr='100', short_message='hello world', ) self.routable_matching_route1 = RoutableSubmitSm( self.PDU_dst_1, self.user1) self.routable_matching_route2 = RoutableSubmitSm( self.PDU_dst_2, self.user2) self.routable_notmatching_any = RoutableSubmitSm( self.PDU_dst_1, self.user2)
def test_locking(self): o = RoutableSubmitSm(self.PDU, self.user, datetime.now()) self.assertRaises(InvalidLockError, o.lockPduParam, 'anything') self.assertRaises(InvalidLockError, o.pduParamIsLocked, 'anything') o.lockPduParam('service_type') self.assertTrue(o.pduParamIsLocked('service_type')) self.assertFalse(o.pduParamIsLocked('source_addr_ton'))
def setUp(self): RouteTestCase.setUp(self) self.PDU_dst_1 = SubmitSM( source_addr='20203060', destination_addr='1', short_message='hello world', ) self.routable_user1 = RoutableSubmitSm(self.PDU_dst_1, self.user1) self.routable_user2 = RoutableSubmitSm(self.PDU_dst_1, self.user2)
def test_tagging(self): o = RoutableSubmitSm(self.PDU, self.user, datetime.now()) self.assertRaises(InvalidTagError, o.addTag, 'anything') self.assertRaises(InvalidTagError, o.hasTag, 'anything') self.assertRaises(InvalidTagError, o.removeTag, 'anything') o.addTag(23) self.assertTrue(o.hasTag(23)) self.assertFalse(o.hasTag(30)) self.assertRaises(TagNotFoundError, o.removeTag, 30) self.assertEqual([23], o.getTags()) o.flushTags() self.assertEqual([], o.getTags())
def test_standard(self): o = RoutableSubmitSm(self.PDU, self.user, datetime.now()) self.assertEqual(o.pdu, self.PDU) self.assertEqual(o.user.uid, self.user.uid) self.assertEqual(o.user.group.gid, self.user.group.gid) self.assertNotEqual(o.datetime, None)
def test_tagging(self): o = RoutableSubmitSm(self.PDU, self.user, datetime.now()) self.assertRaises(InvalidTagError, o.addTag, "anything") self.assertRaises(InvalidTagError, o.hasTag, "anything") self.assertRaises(InvalidTagError, o.removeTag, "anything") o.addTag(23) self.assertTrue(o.hasTag(23)) self.assertFalse(o.hasTag(30)) self.assertRaises(TagNotFoundError, o.removeTag, 30) self.assertEqual([23], o.getTags()) o.flushTags() self.assertEqual([], o.getTags())
def test_without_datetime(self): o = RoutableSubmitSm(self.PDU, self.user) self.assertNotEqual(o.datetime, None)
def test_tagging(self): o = RoutableSubmitSm(self.PDU, self.user, datetime.now()) _any_object = object() self.assertRaises(InvalidTagError, o.addTag, _any_object) self.assertRaises(InvalidTagError, o.hasTag, _any_object) self.assertRaises(InvalidTagError, o.removeTag, _any_object) # Integer tags o.addTag(23) self.assertTrue(o.hasTag(23)) self.assertFalse(o.hasTag(30)) self.assertRaises(TagNotFoundError, o.removeTag, 30) self.assertEqual(['23'], o.getTags()) o.flushTags() self.assertEqual([], o.getTags()) # String tags o.addTag('23') self.assertTrue(o.hasTag('23')) self.assertFalse(o.hasTag('30')) self.assertRaises(TagNotFoundError, o.removeTag, '30') self.assertEqual(['23'], o.getTags()) o.flushTags() self.assertEqual([], o.getTags()) # Mixed tags o.addTag('23') o.addTag(24) self.assertEqual(['23', '24'], o.getTags()) o.flushTags()
def route_routable(self, request): try: # Do we have a hex-content ? if 'hex-content' not in request.args: # Convert utf8 to GSM 03.38 if request.args['coding'][0] == '0': short_message = gsm_encode( request.args['content'][0].decode('utf-8')) else: # Otherwise forward it as is short_message = request.args['content'][0] else: # Otherwise convert hex to bin short_message = hex2bin(request.args['hex-content'][0]) # Authentication user = self.RouterPB.authenticateUser( username=request.args['username'][0], password=request.args['password'][0]) if user is None: self.stats.inc('auth_error_count') self.log.debug( "Authentication failure for username:%s and password:%s", request.args['username'][0], request.args['password'][0]) self.log.error("Authentication failure for username:%s", request.args['username'][0]) raise AuthenticationError( 'Authentication failure for username:%s' % request.args['username'][0]) # Update CnxStatus user.getCnxStatus().httpapi['connects_count'] += 1 user.getCnxStatus().httpapi['rate_request_count'] += 1 user.getCnxStatus().httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_add=None if 'from' not in request.args else request.args['from'][0], destination_addr=request.args['to'][0], short_message=short_message, data_coding=int(request.args['coding'][0]), ) self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Rate', user, request, submit_sm=SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Prepare for interception than routing routable = RoutableSubmitSm(SubmitSmPDU, user) self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable, SubmitSmPDU) # Should we tag the routable ? tags = [] if 'tags' in request.args: tags = request.args['tags'][0].split(',') for tag in tags: routable.addTag(tag) self.log.debug('Tagged routable %s: +%s', routable, tag) # Intercept interceptor = self.RouterPB.getMTInterceptionTable( ).getInterceptorFor(routable) if interceptor is not None: self.log.debug( "RouterPB selected %s interceptor for this SubmitSmPDU", interceptor) if self.interceptorpb_client is None: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not set !") raise InterceptorNotSetError('InterceptorPB not set !') if not self.interceptorpb_client.isConnected: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not connected !") raise InterceptorNotConnectedError( 'InterceptorPB not connected !') script = interceptor.getScript() self.log.debug("Interceptor script loaded: %s", script) # Run ! r = yield self.interceptorpb_client.run_script( script, routable) if isinstance(r, dict) and r['http_status'] != 200: self.stats.inc('interceptor_error_count') self.log.error( 'Interceptor script returned %s http_status error.', r['http_status']) raise InterceptorRunError( code=r['http_status'], message='Interception specific error code %s' % r['http_status']) elif isinstance(r, str): self.stats.inc('interceptor_count') routable = pickle.loads(r) else: self.stats.inc('interceptor_error_count') self.log.error( 'Failed running interception script, got the following return: %s', r) raise InterceptorRunError( message= 'Failed running interception script, check log for details' ) # Routing route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.log.error( "No route matched from user %s for SubmitSmPDU: %s", user, SubmitSmPDU) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s for this SubmitSmPDU", route) # Get number of PDUs to be sent (for billing purpose) _pdu = SubmitSmPDU submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Get the bill bill = route.getBillFor(user) response = { 'return': { 'unit_rate': bill.getTotalAmounts(), 'submit_sm_count': submit_sm_count }, 'status': 200 } except Exception as e: self.log.error("Error: %s", e) if hasattr(e, 'code'): response = {'return': e.message, 'status': e.code} else: response = {'return': "Unknown error: %s" % e, 'status': 500} finally: self.log.debug("Returning %s to %s.", response, request.getClientIP()) # Return message if response['return'] is None: response['return'] = 'System error' request.setResponseCode(500) else: request.setResponseCode(response['status']) request.write(json.dumps(response['return'])) request.finish()
def render(self, request): """ /send request processing Note: This method MUST behave exactly like jasmin.protocols.smpp.factory.SMPPServerFactory.submit_sm_event """ self.log.debug("Rendering /send response with args: %s from %s" % (request.args, request.getClientIP())) response = {'return': None, 'status': 200} # updated_request will be filled with default values where request will never get modified # updated_request is used for sending the SMS, request is just kept as an original request object updated_request = request try: # Validation fields = { 'to': { 'optional': False, 'pattern': re.compile(r'^\+{0,1}\d+$') }, 'from': { 'optional': True }, 'coding': { 'optional': True, 'pattern': re.compile(r'^(0|1|2|3|4|5|6|7|8|9|10|13|14){1}$') }, 'username': { 'optional': False, 'pattern': re.compile(r'^.{1,30}$') }, 'password': { 'optional': False, 'pattern': re.compile(r'^.{1,30}$') }, # Priority validation pattern can be validated/filtered further more through HttpAPICredentialValidator 'priority': { 'optional': True, 'pattern': re.compile(r'^[0-3]$') }, 'dlr': { 'optional': False, 'pattern': re.compile(r'^(yes|no)$') }, 'dlr-url': { 'optional': True, 'pattern': re.compile(r'^(http|https)\://.*$') }, # DLR Level validation pattern can be validated/filtered further more through HttpAPICredentialValidator 'dlr-level': { 'optional': True, 'pattern': re.compile(r'^[1-3]$') }, 'dlr-method': { 'optional': True, 'pattern': re.compile(r'^(get|post)$', re.IGNORECASE) }, 'content': { 'optional': False }, } # Default coding is 0 when not provided if 'coding' not in updated_request.args: updated_request.args['coding'] = ['0'] # Set default for undefined updated_request.arguments if 'dlr-url' in updated_request.args or 'dlr-level' in updated_request.args: updated_request.args['dlr'] = ['yes'] if 'dlr' not in updated_request.args: # Setting DLR updated_request to 'no' updated_request.args['dlr'] = ['no'] # Set default values if updated_request.args['dlr'][0] == 'yes': if 'dlr-level' not in updated_request.args: # If DLR is requested and no dlr-level were provided, assume minimum level (1) updated_request.args['dlr-level'] = [1] if 'dlr-method' not in updated_request.args: # If DLR is requested and no dlr-method were provided, assume default (POST) updated_request.args['dlr-method'] = ['POST'] # DLR method must be uppercase if 'dlr-method' in updated_request.args: updated_request.args['dlr-method'][0] = updated_request.args[ 'dlr-method'][0].upper() # Make validation v = UrlArgsValidator(updated_request, fields) v.validate() # Authentication user = self.RouterPB.authenticateUser( username=updated_request.args['username'][0], password=updated_request.args['password'][0]) if user is None: self.log.debug( "Authentication failure for username:%s and password:%s" % (updated_request.args['username'][0], updated_request.args['password'][0])) self.log.error("Authentication failure for username:%s" % updated_request.args['username'][0]) raise AuthenticationError( 'Authentication failure for username:%s' % updated_request.args['username'][0]) # Update CnxStatus user.CnxStatus.httpapi['connects_count'] += 1 user.CnxStatus.httpapi['submit_sm_request_count'] += 1 user.CnxStatus.httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_addr=None if 'from' not in updated_request.args else updated_request.args['from'][0], destination_addr=updated_request.args['to'][0], short_message=updated_request.args['content'][0], data_coding=int(updated_request.args['coding'][0]), ) self.log.debug("Built base SubmitSmPDU: %s" % SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Send', user, SubmitSmPDU, request) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Routing routedConnector = None # init routable = RoutableSubmitSm(SubmitSmPDU, user) route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.log.error( "No route matched from user %s for SubmitSmPDU: %s" % (user, SubmitSmPDU)) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s for this SubmitSmPDU" % route) routedConnector = route.getConnector() # Set priority priority = 0 if 'priority' in updated_request.args: priority = int(updated_request.args['priority'][0]) SubmitSmPDU.params['priority_flag'] = priority_flag_value_map[ priority] self.log.debug("SubmitSmPDU priority is set to %s" % priority) # Set DLR bit mask # c.f. 5.2.17 registered_delivery #################################################################### # dlr-level # Signification # registered_delivery # #################################################################### # 1 # SMS-C level # x x x x x x 1 0 # # 2 # Terminal level (only) # x x x x x x 0 1 # # 3 # SMS-C level and Terminal level # x x x x x x 0 1 # #################################################################### if updated_request.args['dlr'][0] == 'yes': if updated_request.args['dlr-level'][0] == '1': SubmitSmPDU.params[ 'registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt. NO_SMSC_DELIVERY_RECEIPT_REQUESTED) elif updated_request.args['dlr-level'][ 0] == '2' or updated_request.args['dlr-level'][ 0] == '3': SubmitSmPDU.params[ 'registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt. SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE) self.log.debug("SubmitSmPDU registered_delivery is set to %s" % str(SubmitSmPDU.params['registered_delivery'])) dlr_level = int(updated_request.args['dlr-level'][0]) if 'dlr-url' in updated_request.args: dlr_url = updated_request.args['dlr-url'][0] else: dlr_url = None if updated_request.args['dlr-level'][0] == '1': dlr_level_text = 'SMS-C' elif updated_request.args['dlr-level'][0] == '2': dlr_level_text = 'Terminal' else: dlr_level_text = 'All' dlr_method = updated_request.args['dlr-method'][0] else: dlr_url = None dlr_level = 1 dlr_level_text = 'No' dlr_method = None # Get number of PDUs to be sent (for billing purpose) _pdu = SubmitSmPDU submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Pre-sending submit_sm: Billing processing bill = route.getBillFor(user) self.log.debug( "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)" % (bill.bid, bill.getTotalAmounts(), submit_sm_count)) charging_requirements = [] u_balance = user.mt_credential.getQuota('balance') u_subsm_count = user.mt_credential.getQuota('submit_sm_count') if u_balance is not None and bill.getTotalAmounts() > 0: # Ensure user have enough balance to pay submit_sm and submit_sm_resp charging_requirements.append({ 'condition': bill.getTotalAmounts() * submit_sm_count <= u_balance, 'error_message': 'Not enough balance (%s) for charging: %s' % (u_balance, bill.getTotalAmounts()) }) if u_subsm_count is not None: # Ensure user have enough submit_sm_count to to cover the bill action (decrement_submit_sm_count) charging_requirements.append({ 'condition': bill.getAction('decrement_submit_sm_count') * submit_sm_count <= u_subsm_count, 'error_message': 'Not enough submit_sm_count (%s) for charging: %s' % (u_subsm_count, bill.getAction('decrement_submit_sm_count')) }) if self.RouterPB.chargeUserForSubmitSms( user, bill, submit_sm_count, charging_requirements) is None: self.log.error( 'Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)' % (user, bill.bid, bill.getTotalAmounts(), submit_sm_count)) raise ChargingError( 'Cannot charge submit_sm, check RouterPB log file for details' ) ######################################################## # Send SubmitSmPDU through smpp client manager PB server self.log.debug( "Connector '%s' is set to be a route for this SubmitSmPDU" % routedConnector.cid) c = self.SMPPClientManagerPB.perspective_submit_sm( routedConnector.cid, SubmitSmPDU, priority, pickled=False, dlr_url=dlr_url, dlr_level=dlr_level, dlr_method=dlr_method, submit_sm_resp_bill=bill.getSubmitSmRespBill()) # Build final response if not c.result: self.log.error('Failed to send SubmitSmPDU to [cid:%s]' % routedConnector.cid) raise ServerError( 'Cannot send submit_sm, check SMPPClientManagerPB log file for details' ) else: self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s' % (routedConnector.cid, c.result)) response = {'return': c.result, 'status': 200} except Exception, e: self.log.error("Error: %s" % e) if hasattr(e, 'code'): response = {'return': e.message, 'status': e.code} else: response = {'return': "Unknown error: %s" % e, 'status': 500}
def render(self, request): """ /rate request processing Note: This method will indicate the rate of the message once sent """ self.log.debug("Rendering /rate response with args: %s from %s" % (request.args, request.getClientIP())) request.responseHeaders.addRawHeader(b"content-type", b"application/json") response = {'return': None, 'status': 200} self.stats.inc('request_count') self.stats.set('last_request_at', datetime.now()) try: # Validation (must be almost the same params as /send service) fields = { 'to': { 'optional': False, 'pattern': re.compile(r'^\+{0,1}\d+$') }, 'from': { 'optional': True }, 'coding': { 'optional': True, 'pattern': re.compile(r'^(0|1|2|3|4|5|6|7|8|9|10|13|14){1}$') }, 'username': { 'optional': False, 'pattern': re.compile(r'^.{1,9}$') }, 'password': { 'optional': False, 'pattern': re.compile(r'^.{1,9}$') }, # Priority validation pattern can be validated/filtered further more through HttpAPICredentialValidator 'priority': { 'optional': True, 'pattern': re.compile(r'^[0-3]$') }, # Validity period validation pattern can be validated/filtered further more through HttpAPICredentialValidator 'validity-period': { 'optional': True, 'pattern': re.compile(r'^\d+$') }, 'content': { 'optional': True }, } # Default coding is 0 when not provided if 'coding' not in request.args: request.args['coding'] = ['0'] # Content is optional, defaults to empty string if 'content' not in request.args: request.args['content'] = [''] # Make validation v = UrlArgsValidator(request, fields) v.validate() # Authentication user = self.RouterPB.authenticateUser( username=request.args['username'][0], password=request.args['password'][0]) if user is None: self.stats.inc('auth_error_count') self.log.debug( "Authentication failure for username:%s and password:%s" % (request.args['username'][0], request.args['password'][0])) self.log.error("Authentication failure for username:%s" % request.args['username'][0]) raise AuthenticationError( 'Authentication failure for username:%s' % request.args['username'][0]) # Update CnxStatus user.getCnxStatus().httpapi['connects_count'] += 1 user.getCnxStatus().httpapi['rate_request_count'] += 1 user.getCnxStatus().httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_addr=None if 'from' not in request.args else request.args['from'][0], destination_addr=request.args['to'][0], short_message=request.args['content'][0], data_coding=int(request.args['coding'][0]), ) self.log.debug("Built base SubmitSmPDU: %s" % SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Rate', user, request, submit_sm=SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Routing routedConnector = None # init routable = RoutableSubmitSm(SubmitSmPDU, user) route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.log.error( "No route matched from user %s for SubmitSmPDU: %s" % (user, SubmitSmPDU)) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s for this SubmitSmPDU" % route) routedConnector = route.getConnector() # Get number of PDUs to be sent (for billing purpose) _pdu = SubmitSmPDU submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Get the bill bill = route.getBillFor(user) response = { 'return': { 'unit_rate': bill.getTotalAmounts(), 'submit_sm_count': submit_sm_count }, 'status': 200 } except Exception, e: self.log.error("Error: %s" % e) if hasattr(e, 'code'): response = {'return': e.message, 'status': e.code} else: response = {'return': "Unknown error: %s" % e, 'status': 500}
def route_routable(self, updated_request): try: # Authentication user = self.RouterPB.authenticateUser( username=updated_request.args['username'][0], password=updated_request.args['password'][0]) if user is None: self.stats.inc('auth_error_count') self.log.debug( "Authentication failure for username:%s and password:%s", updated_request.args['username'][0], updated_request.args['password'][0]) self.log.error("Authentication failure for username:%s", updated_request.args['username'][0]) raise AuthenticationError( 'Authentication failure for username:%s' % updated_request.args['username'][0]) # Update CnxStatus user.getCnxStatus().httpapi['connects_count'] += 1 user.getCnxStatus().httpapi['submit_sm_request_count'] += 1 user.getCnxStatus().httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_addr=None if 'from' not in updated_request.args else updated_request.args['from'][0], destination_addr=updated_request.args['to'][0], short_message=updated_request.args['content'][0], data_coding=int(updated_request.args['coding'][0])) self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Send', user, updated_request, submit_sm=SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Prepare for interception then routing routedConnector = None # init routable = RoutableSubmitSm(SubmitSmPDU, user) # Intercept interceptor = self.RouterPB.getMTInterceptionTable( ).getInterceptorFor(routable) if interceptor is not None: self.log.debug( "RouterPB selected %s interceptor for this SubmitSmPDU", interceptor) if self.interceptorpb_client is None: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not set !") raise InterceptorNotSetError('InterceptorPB not set !') if not self.interceptorpb_client.isConnected: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not connected !") raise InterceptorNotConnectedError( 'InterceptorPB not connected !') script = interceptor.getScript() self.log.debug("Interceptor script loaded: %s", script) # Run ! r = yield self.interceptorpb_client.run_script( script, routable) if isinstance(r, dict) and r['http_status'] != 200: self.stats.inc('interceptor_error_count') self.log.error( 'Interceptor script returned %s http_status error.', r['http_status']) raise InterceptorRunError( code=r['http_status'], message='Interception specific error code %s' % r['http_status']) elif isinstance(r, str): self.stats.inc('interceptor_count') routable = pickle.loads(r) else: self.stats.inc('interceptor_error_count') self.log.error( 'Failed running interception script, got the following return: %s', r) raise InterceptorRunError( message= 'Failed running interception script, check log for details' ) # Get the route route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.stats.inc('route_error_count') self.log.error( "No route matched from user %s for SubmitSmPDU: %s", user, routable.pdu) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s route for this SubmitSmPDU", route) routedConnector = route.getConnector() # Set priority priority = 0 if 'priority' in updated_request.args: priority = int(updated_request.args['priority'][0]) routable.pdu.params['priority_flag'] = priority_flag_value_map[ priority] self.log.debug("SubmitSmPDU priority is set to %s", priority) # Set validity_period if 'validity-period' in updated_request.args: delta = timedelta( minutes=int(updated_request.args['validity-period'][0])) routable.pdu.params['validity_period'] = datetime.today( ) + delta self.log.debug( "SubmitSmPDU validity_period is set to %s (+%s minutes)", routable.pdu.params['validity_period'], updated_request.args['validity-period'][0]) # Set DLR bit mask # DLR setting is clearly described in #107 routable.pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED) if updated_request.args['dlr'][0] == 'yes': routable.pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED) self.log.debug("SubmitSmPDU registered_delivery is set to %s", str(routable.pdu.params['registered_delivery'])) dlr_level = int(updated_request.args['dlr-level'][0]) if 'dlr-url' in updated_request.args: dlr_url = updated_request.args['dlr-url'][0] else: dlr_url = None if updated_request.args['dlr-level'][0] == '1': dlr_level_text = 'SMS-C' elif updated_request.args['dlr-level'][0] == '2': dlr_level_text = 'Terminal' else: dlr_level_text = 'All' dlr_method = updated_request.args['dlr-method'][0] else: dlr_url = None dlr_level = 0 dlr_level_text = 'No' dlr_method = None # QoS throttling if user.mt_credential.getQuota( 'http_throughput') >= 0 and user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] != 0: qos_throughput_second = 1 / float( user.mt_credential.getQuota('http_throughput')) qos_throughput_ysecond_td = timedelta( microseconds=qos_throughput_second * 1000000) qos_delay = datetime.now() - user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] if qos_delay < qos_throughput_ysecond_td: self.stats.inc('throughput_error_count') self.log.error( "QoS: submit_sm_event is faster (%s) than fixed throughput (%s) for user (%s), rejecting message.", qos_delay, qos_throughput_ysecond_td, user) raise ThroughputExceededError("User throughput exceeded") user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] = datetime.now() # Get number of PDUs to be sent (for billing purpose) _pdu = routable.pdu submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Pre-sending submit_sm: Billing processing bill = route.getBillFor(user) self.log.debug( "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)", bill.bid, bill.getTotalAmounts(), submit_sm_count) charging_requirements = [] u_balance = user.mt_credential.getQuota('balance') u_subsm_count = user.mt_credential.getQuota('submit_sm_count') if u_balance is not None and bill.getTotalAmounts() > 0: # Ensure user have enough balance to pay submit_sm and submit_sm_resp charging_requirements.append({ 'condition': bill.getTotalAmounts() * submit_sm_count <= u_balance, 'error_message': 'Not enough balance (%s) for charging: %s' % (u_balance, bill.getTotalAmounts()) }) if u_subsm_count is not None: # Ensure user have enough submit_sm_count to to cover # the bill action (decrement_submit_sm_count) charging_requirements.append({ 'condition': bill.getAction('decrement_submit_sm_count') * submit_sm_count <= u_subsm_count, 'error_message': 'Not enough submit_sm_count (%s) for charging: %s' % (u_subsm_count, bill.getAction('decrement_submit_sm_count')) }) if self.RouterPB.chargeUserForSubmitSms( user, bill, submit_sm_count, charging_requirements) is None: self.stats.inc('charging_error_count') self.log.error( 'Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)', user, bill.bid, bill.getTotalAmounts(), submit_sm_count) raise ChargingError( 'Cannot charge submit_sm, check RouterPB log file for details' ) ######################################################## # Send SubmitSmPDU through smpp client manager PB server self.log.debug( "Connector '%s' is set to be a route for this SubmitSmPDU", routedConnector.cid) c = self.SMPPClientManagerPB.perspective_submit_sm( cid=routedConnector.cid, SubmitSmPDU=routable.pdu, submit_sm_bill=bill, priority=priority, pickled=False, dlr_url=dlr_url, dlr_level=dlr_level, dlr_method=dlr_method) # Build final response if not c.result: self.stats.inc('server_error_count') self.log.error('Failed to send SubmitSmPDU to [cid:%s]', routedConnector.cid) raise ServerError( 'Cannot send submit_sm, check SMPPClientManagerPB log file for details' ) else: self.stats.inc('success_count') self.stats.set('last_success_at', datetime.now()) self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s', routedConnector.cid, c.result) response = {'return': c.result, 'status': 200} except Exception, e: self.log.error("Error: %s", e) if hasattr(e, 'code'): response = {'return': e.message, 'status': e.code} else: response = {'return': "Unknown error: %s" % e, 'status': 500}
def submit_sm_event(self, system_id, *args): """This event handler will deliver the submit_sm to the right smppc connector. Note that Jasmin deliver submit_sm messages like this: - from httpapi to smppc (handled in jasmin.protocols.http.server) - from smpps to smppc (this event handler) Note: This event handler MUST behave exactly like jasmin.protocols.http.server.Send.render """ self.log.debug('Handling submit_sm event for system_id: %s' % system_id) # Args validation if len(args) != 2: self.log.error('(submit_sm_event/%s) Invalid args: %s' % (system_id, args)) raise SubmitSmInvalidArgsError() if not isinstance(args[1], pdu_types.PDURequest): self.log.error( '(submit_sm_event/%s) Received an unknown object when waiting for a PDURequest: %s' % (system_id, args[1])) raise SubmitSmInvalidArgsError() if args[1].id != pdu_types.CommandId.submit_sm: self.log.error( '(submit_sm_event/%s) Received a non submit_sm command id: %s' % (system_id, args[1].id)) raise SubmitSmInvalidArgsError() if not isinstance(args[0], SMPPServerProtocol): self.log.error( '(submit_sm_event/%s) Received an unknown object when waiting for a SMPPServerProtocol: %s' % (system_id, args[0])) raise SubmitSmInvalidArgsError() proto = args[0] user = proto.user SubmitSmPDU = args[1] # Update CnxStatus user.getCnxStatus().smpps['submit_sm_request_count'] += 1 # Basic validation if len(SubmitSmPDU.params['destination_addr'] ) < 1 or SubmitSmPDU.params['destination_addr'] is None: self.log.error( '(submit_sm_event/%s) SubmitSmPDU have no defined destination_addr' % system_id) raise SubmitSmWithoutDestinationAddrError() # Make Credential validation v = SmppsCredentialValidator('Send', user, SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) if self.RouterPB is None: self.log.error( '(submit_sm_event/%s) RouterPB not set: submit_sm will not be routed' % system_id) return # Routing routedConnector = None # init routable = RoutableSubmitSm(SubmitSmPDU, user) route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.log.error( "No route matched from user %s for SubmitSmPDU: %s" % (user, SubmitSmPDU)) raise SubmitSmRouteNotFoundError() # Get connector from selected route self.log.debug("RouterPB selected %s for this SubmitSmPDU" % route) routedConnector = route.getConnector() # QoS throttling if user.mt_credential.getQuota( 'smpps_throughput') >= 0 and user.getCnxStatus( ).smpps['qos_last_submit_sm_at'] != 0: qos_throughput_second = 1 / float( user.mt_credential.getQuota('smpps_throughput')) qos_throughput_ysecond_td = timedelta( microseconds=qos_throughput_second * 1000000) qos_delay = datetime.now() - user.getCnxStatus( ).smpps['qos_last_submit_sm_at'] if qos_delay < qos_throughput_ysecond_td: self.log.error( "QoS: submit_sm_event is faster (%s) than fixed throughput (%s) for user (%s), rejecting message." % (qos_delay, qos_throughput_ysecond_td, user)) raise SubmitSmThroughputExceededError() user.getCnxStatus().smpps['qos_last_submit_sm_at'] = datetime.now() # Pre-sending submit_sm: Billing processing bill = route.getBillFor(user) self.log.debug( "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU" % (bill.bid, bill.getTotalAmounts())) charging_requirements = [] u_balance = user.mt_credential.getQuota('balance') u_subsm_count = user.mt_credential.getQuota('submit_sm_count') if u_balance is not None and bill.getTotalAmounts() > 0: # Ensure user have enough balance to pay submit_sm and submit_sm_resp charging_requirements.append({ 'condition': bill.getTotalAmounts() <= u_balance, 'error_message': 'Not enough balance (%s) for charging: %s' % (u_balance, bill.getTotalAmounts()) }) if u_subsm_count is not None: # Ensure user have enough submit_sm_count to to cover the bill action (decrement_submit_sm_count) charging_requirements.append({ 'condition': bill.getAction('decrement_submit_sm_count') <= u_subsm_count, 'error_message': 'Not enough submit_sm_count (%s) for charging: %s' % (u_subsm_count, bill.getAction('decrement_submit_sm_count')) }) if self.RouterPB.chargeUserForSubmitSms( user, bill, requirements=charging_requirements) is None: self.log.error( 'Charging user %s failed, [bid:%s] [ttlamounts:%s] (check router log)' % (user, bill.bid, bill.getTotalAmounts())) raise SubmitSmChargingError() # Get priority value from SubmitSmPDU to pass to SMPPClientManagerPB.perspective_submit_sm() priority = 0 if SubmitSmPDU.params['priority_flag'] is not None: priority = SubmitSmPDU.params['priority_flag'].index if self.SMPPClientManagerPB is None: self.log.error( '(submit_sm_event/%s) SMPPClientManagerPB not set: submit_sm will not be submitted' % system_id) return ######################################################## # Send SubmitSmPDU through smpp client manager PB server self.log.debug( "Connector '%s' is set to be a route for this SubmitSmPDU" % routedConnector.cid) c = self.SMPPClientManagerPB.perspective_submit_sm( routedConnector.cid, SubmitSmPDU, priority, pickled=False, submit_sm_resp_bill=bill.getSubmitSmRespBill(), source_connector=proto) # Build final response if not c.result: self.log.error('Failed to send SubmitSmPDU to [cid:%s]' % routedConnector.cid) raise SubmitSmRoutingError() else: self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s' % (routedConnector.cid, c.result)) return DataHandlerResponse(status=pdu_types.CommandStatus.ESME_ROK, message_id=c.result)
def submit_sm_event_interceptor(self, system_id, *args): "Intercept submit_sm befor handing it to self.submit_sm_event" self.log.debug('Intercepting submit_sm event for system_id: %s', system_id) # Args validation if len(args) != 2: self.log.error('(submit_sm_event/%s) Invalid args: %s', system_id, args) raise SubmitSmInvalidArgsError() if not isinstance(args[1], pdu_types.PDURequest): self.log.error( '(submit_sm_event/%s) Received an unknown object when waiting for a PDURequest: %s', system_id, args[1]) raise SubmitSmInvalidArgsError() if args[1].id != pdu_types.CommandId.submit_sm: self.log.error( '(submit_sm_event/%s) Received a non submit_sm command id: %s', system_id, args[1].id) raise SubmitSmInvalidArgsError() if not isinstance(args[0], SMPPServerProtocol): self.log.error( '(submit_sm_event/%s) Received an unknown object when waiting for a SMPPServerProtocol: %s', system_id, args[0]) raise SubmitSmInvalidArgsError() proto = args[0] user = proto.user SubmitSmPDU = args[1] # Update CnxStatus user.getCnxStatus().smpps['submit_sm_request_count'] += 1 # Basic validation if len(SubmitSmPDU.params['destination_addr'] ) < 1 or SubmitSmPDU.params['destination_addr'] is None: self.log.error( '(submit_sm_event/%s) SubmitSmPDU have no defined destination_addr', system_id) raise SubmitSmWithoutDestinationAddrError() # Make Credential validation v = SmppsCredentialValidator('Send', user, SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) if self.RouterPB is None: self.log.error( '(submit_sm_event_interceptor/%s) RouterPB not set: submit_sm will not be routed', system_id) return # Prepare for interception then routing routable = RoutableSubmitSm(SubmitSmPDU, user) # Interception inline # @TODO: make Interception in a thread, just like httpapi interception interceptor = self.RouterPB.getMTInterceptionTable().getInterceptorFor( routable) if interceptor is not None: self.log.debug( "RouterPB selected %s interceptor for this SubmitSmPDU", interceptor) if self.interceptorpb_client is None: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not set !") raise InterceptorNotSetError('InterceptorPB not set !') if not self.interceptorpb_client.isConnected: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not connected !") raise InterceptorNotConnectedError( 'InterceptorPB not connected !') script = interceptor.getScript() self.log.debug("Interceptor script loaded: %s", script) # Run ! d = self.interceptorpb_client.run_script(script, routable) d.addCallback(self.submit_sm_post_interception, system_id=system_id, proto=proto) d.addErrback(self.submit_sm_post_interception) return d else: return self.submit_sm_post_interception(routable=routable, system_id=system_id, proto=proto)
def route_routable(self, request): try: # Do we have a hex-content ? if 'hex-content' not in request.args: # Convert utf8 to GSM 03.38 if request.args['coding'][0] == '0': short_message = gsm_encode(request.args['content'][0].decode('utf-8')) else: # Otherwise forward it as is short_message = request.args['content'][0] else: # Otherwise convert hex to bin short_message = hex2bin(request.args['hex-content'][0]) # Authentication user = self.RouterPB.authenticateUser( username=request.args['username'][0], password=request.args['password'][0] ) if user is None: self.stats.inc('auth_error_count') self.log.debug( "Authentication failure for username:%s and password:%s", request.args['username'][0], request.args['password'][0]) self.log.error( "Authentication failure for username:%s", request.args['username'][0]) raise AuthenticationError( 'Authentication failure for username:%s' % request.args['username'][0]) # Update CnxStatus user.getCnxStatus().httpapi['connects_count'] += 1 user.getCnxStatus().httpapi['rate_request_count'] += 1 user.getCnxStatus().httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_add=None if 'from' not in request.args else request.args['from'][0], destination_addr=request.args['to'][0], short_message=short_message, data_coding=int(request.args['coding'][0]), ) self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Rate', user, request, submit_sm=SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Prepare for interception than routing routable = RoutableSubmitSm(SubmitSmPDU, user) self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable, SubmitSmPDU) # Should we tag the routable ? tags = [] if 'tags' in request.args: tags = request.args['tags'][0].split(',') for tag in tags: routable.addTag(tag) self.log.debug('Tagged routable %s: +%s', routable, tag) # Intercept interceptor = self.RouterPB.getMTInterceptionTable().getInterceptorFor(routable) if interceptor is not None: self.log.debug("RouterPB selected %s interceptor for this SubmitSmPDU", interceptor) if self.interceptorpb_client is None: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not set !") raise InterceptorNotSetError('InterceptorPB not set !') if not self.interceptorpb_client.isConnected: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not connected !") raise InterceptorNotConnectedError('InterceptorPB not connected !') script = interceptor.getScript() self.log.debug("Interceptor script loaded: %s", script) # Run ! r = yield self.interceptorpb_client.run_script(script, routable) if isinstance(r, dict) and r['http_status'] != 200: self.stats.inc('interceptor_error_count') self.log.error('Interceptor script returned %s http_status error.', r['http_status']) raise InterceptorRunError( code=r['http_status'], message='Interception specific error code %s' % r['http_status'] ) elif isinstance(r, str): self.stats.inc('interceptor_count') routable = pickle.loads(r) else: self.stats.inc('interceptor_error_count') self.log.error('Failed running interception script, got the following return: %s', r) raise InterceptorRunError(message='Failed running interception script, check log for details') # Routing route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.log.error("No route matched from user %s for SubmitSmPDU: %s", user, SubmitSmPDU) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s for this SubmitSmPDU", route) # Get number of PDUs to be sent (for billing purpose) _pdu = SubmitSmPDU submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Get the bill bill = route.getBillFor(user) response = { 'return': { 'unit_rate': bill.getTotalAmounts(), 'submit_sm_count': submit_sm_count}, 'status': 200} except Exception as e: self.log.error("Error: %s", e) if hasattr(e, 'code'): response = {'return': e.message, 'status': e.code} else: response = {'return': "Unknown error: %s" % e, 'status': 500} finally: self.log.debug("Returning %s to %s.", response, request.getClientIP()) # Return message if response['return'] is None: response['return'] = 'System error' request.setResponseCode(500) else: request.setResponseCode(response['status']) request.write(json.dumps(response['return'])) request.finish()
def route_routable(self, updated_request): try: # Do we have a hex-content ? if 'hex-content' not in updated_request.args: # Convert utf8 to GSM 03.38 if updated_request.args['coding'][0] == '0': short_message = gsm_encode( updated_request.args['content'][0].decode('utf-8')) else: # Otherwise forward it as is short_message = updated_request.args['content'][0] else: # Otherwise convert hex to bin short_message = hex2bin(updated_request.args['hex-content'][0]) # Authentication user = self.RouterPB.authenticateUser( username=updated_request.args['username'][0], password=updated_request.args['password'][0]) if user is None: self.stats.inc('auth_error_count') self.log.debug( "Authentication failure for username:%s and password:%s", updated_request.args['username'][0], updated_request.args['password'][0]) self.log.error("Authentication failure for username:%s", updated_request.args['username'][0]) raise AuthenticationError( 'Authentication failure for username:%s' % updated_request.args['username'][0]) # Update CnxStatus user.getCnxStatus().httpapi['connects_count'] += 1 user.getCnxStatus().httpapi['submit_sm_request_count'] += 1 user.getCnxStatus().httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_addr=None if 'from' not in updated_request.args else updated_request.args['from'][0], destination_addr=updated_request.args['to'][0], short_message=short_message, data_coding=int(updated_request.args['coding'][0]), custom_tlvs=updated_request.args['custom_tlvs'][0]) self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Send', user, updated_request, submit_sm=SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Prepare for interception then routing routedConnector = None # init routable = RoutableSubmitSm(SubmitSmPDU, user) self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable, SubmitSmPDU) # Should we tag the routable ? tags = [] if 'tags' in updated_request.args: tags = updated_request.args['tags'][0].split(',') for tag in tags: routable.addTag(tag) self.log.debug('Tagged routable %s: +%s', routable, tag) # Intercept interceptor = self.RouterPB.getMTInterceptionTable( ).getInterceptorFor(routable) if interceptor is not None: self.log.debug( "RouterPB selected %s interceptor for this SubmitSmPDU", interceptor) if self.interceptorpb_client is None: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not set !") raise InterceptorNotSetError('InterceptorPB not set !') if not self.interceptorpb_client.isConnected: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not connected !") raise InterceptorNotConnectedError( 'InterceptorPB not connected !') script = interceptor.getScript() self.log.debug("Interceptor script loaded: %s", script) # Run ! r = yield self.interceptorpb_client.run_script( script, routable) if isinstance(r, dict) and r['http_status'] != 200: self.stats.inc('interceptor_error_count') self.log.error( 'Interceptor script returned %s http_status error.', r['http_status']) raise InterceptorRunError( code=r['http_status'], message='Interception specific error code %s' % r['http_status']) elif isinstance(r, str): self.stats.inc('interceptor_count') routable = pickle.loads(r) else: self.stats.inc('interceptor_error_count') self.log.error( 'Failed running interception script, got the following return: %s', r) raise InterceptorRunError( message= 'Failed running interception script, check log for details' ) # Get the route route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.stats.inc('route_error_count') self.log.error( "No route matched from user %s for SubmitSmPDU: %s", user, routable.pdu) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s route for this SubmitSmPDU", route) routedConnector = route.getConnector() # Is it a failover route ? then check for a bound connector, otherwise don't route # The failover route requires at least one connector to be up, no message enqueuing will # occur otherwise. if repr(route) == 'FailoverMTRoute': self.log.debug( 'Selected route is a failover, will ensure connector is bound:' ) while True: c = self.SMPPClientManagerPB.perspective_connector_details( routedConnector.cid) if c: self.log.debug('Connector [%s] is: %s', routedConnector.cid, c['session_state']) else: self.log.debug('Connector [%s] is not found', routedConnector.cid) if c and c['session_state'][:6] == 'BOUND_': # Choose this connector break else: # Check next connector, None if no more connectors are available routedConnector = route.getConnector() if routedConnector is None: break if routedConnector is None: self.stats.inc('route_error_count') self.log.error( "Failover route has no bound connector to handle SubmitSmPDU: %s", routable.pdu) raise ConnectorNotFoundError( "Failover route has no bound connectors") # Re-update SubmitSmPDU with parameters from the route's connector connector_config = self.SMPPClientManagerPB.perspective_connector_config( routedConnector.cid) if connector_config: connector_config = pickle.loads(connector_config) routable = update_submit_sm_pdu(routable=routable, config=connector_config) # Set priority priority = 0 if 'priority' in updated_request.args: priority = int(updated_request.args['priority'][0]) routable.pdu.params['priority_flag'] = priority_flag_value_map[ priority] self.log.debug("SubmitSmPDU priority is set to %s", priority) # Set schedule_delivery_time if 'sdt' in updated_request.args: routable.pdu.params['schedule_delivery_time'] = parse( updated_request.args['sdt'][0]) self.log.debug( "SubmitSmPDU schedule_delivery_time is set to %s (%s)", routable.pdu.params['schedule_delivery_time'], updated_request.args['sdt'][0]) # Set validity_period if 'validity-period' in updated_request.args: delta = timedelta( minutes=int(updated_request.args['validity-period'][0])) routable.pdu.params['validity_period'] = datetime.today( ) + delta self.log.debug( "SubmitSmPDU validity_period is set to %s (+%s minutes)", routable.pdu.params['validity_period'], updated_request.args['validity-period'][0]) # Set DLR bit mask on the last pdu _last_pdu = routable.pdu while True: if hasattr(_last_pdu, 'nextPdu'): _last_pdu = _last_pdu.nextPdu else: break # DLR setting is clearly described in #107 _last_pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED) if updated_request.args['dlr'][0] == 'yes': _last_pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED) self.log.debug("SubmitSmPDU registered_delivery is set to %s", str(_last_pdu.params['registered_delivery'])) dlr_level = int(updated_request.args['dlr-level'][0]) if 'dlr-url' in updated_request.args: dlr_url = updated_request.args['dlr-url'][0] else: dlr_url = None if updated_request.args['dlr-level'][0] == '1': dlr_level_text = 'SMS-C' elif updated_request.args['dlr-level'][0] == '2': dlr_level_text = 'Terminal' else: dlr_level_text = 'All' dlr_method = updated_request.args['dlr-method'][0] else: dlr_url = None dlr_level = 0 dlr_level_text = 'No' dlr_method = None # QoS throttling if user.mt_credential.getQuota( 'http_throughput') >= 0 and user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] != 0: qos_throughput_second = 1 / float( user.mt_credential.getQuota('http_throughput')) qos_throughput_ysecond_td = timedelta( microseconds=qos_throughput_second * 1000000) qos_delay = datetime.now() - user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] if qos_delay < qos_throughput_ysecond_td: self.stats.inc('throughput_error_count') self.log.error( "QoS: submit_sm_event is faster (%s) than fixed throughput (%s), user:%s, rejecting message.", qos_delay, qos_throughput_ysecond_td, user) raise ThroughputExceededError("User throughput exceeded") user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] = datetime.now() # Get number of PDUs to be sent (for billing purpose) _pdu = routable.pdu submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Pre-sending submit_sm: Billing processing bill = route.getBillFor(user) self.log.debug( "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)", bill.bid, bill.getTotalAmounts(), submit_sm_count) charging_requirements = [] u_balance = user.mt_credential.getQuota('balance') u_subsm_count = user.mt_credential.getQuota('submit_sm_count') if u_balance is not None and bill.getTotalAmounts() > 0: # Ensure user have enough balance to pay submit_sm and submit_sm_resp charging_requirements.append({ 'condition': bill.getTotalAmounts() * submit_sm_count <= u_balance, 'error_message': 'Not enough balance (%s) for charging: %s' % (u_balance, bill.getTotalAmounts()) }) if u_subsm_count is not None: # Ensure user have enough submit_sm_count to to cover # the bill action (decrement_submit_sm_count) charging_requirements.append({ 'condition': bill.getAction('decrement_submit_sm_count') * submit_sm_count <= u_subsm_count, 'error_message': 'Not enough submit_sm_count (%s) for charging: %s' % (u_subsm_count, bill.getAction('decrement_submit_sm_count')) }) if self.RouterPB.chargeUserForSubmitSms( user, bill, submit_sm_count, charging_requirements) is None: self.stats.inc('charging_error_count') self.log.error( 'Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)', user, bill.bid, bill.getTotalAmounts(), submit_sm_count) raise ChargingError( 'Cannot charge submit_sm, check RouterPB log file for details' ) ######################################################## # Send SubmitSmPDU through smpp client manager PB server self.log.debug( "Connector '%s' is set to be a route for this SubmitSmPDU", routedConnector.cid) c = self.SMPPClientManagerPB.perspective_submit_sm( uid=user.uid, cid=routedConnector.cid, SubmitSmPDU=routable.pdu, submit_sm_bill=bill, priority=priority, pickled=False, dlr_url=dlr_url, dlr_level=dlr_level, dlr_method=dlr_method) # Build final response if not c.result: self.stats.inc('server_error_count') self.log.error('Failed to send SubmitSmPDU to [cid:%s]', routedConnector.cid) raise ServerError( 'Cannot send submit_sm, check SMPPClientManagerPB log file for details' ) else: self.stats.inc('success_count') self.stats.set('last_success_at', datetime.now()) self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s', routedConnector.cid, c.result) response = {'return': c.result, 'status': 200} except Exception as e: self.log.error("Error: %s", e) if hasattr(e, 'code'): response = {'return': e.message, 'status': e.code} else: response = {'return': "Unknown error: %s" % e, 'status': 500} finally: self.log.debug("Returning %s to %s.", response, updated_request.getClientIP()) updated_request.setResponseCode(response['status']) # Default return _return = 'Error "%s"' % response['return'] # Success return if response['status'] == 200 and routedConnector is not None: self.log.info( 'SMS-MT [uid:%s] [cid:%s] [msgid:%s] [prio:%s] [dlr:%s] [from:%s] [to:%s] [content:%s]', user.uid, routedConnector.cid, response['return'], priority, dlr_level_text, routable.pdu.params['source_addr'], updated_request.args['to'][0], re.sub(r'[^\x20-\x7E]+', '.', short_message)) _return = 'Success "%s"' % response['return'] updated_request.write(_return) updated_request.finish()
def route_routable(self, updated_request): try: # Do we have a hex-content ? if 'hex-content' not in updated_request.args: # Convert utf8 to GSM 03.38 if updated_request.args['coding'][0] == '0': short_message = gsm_encode(updated_request.args['content'][0].decode('utf-8')) else: # Otherwise forward it as is short_message = updated_request.args['content'][0] else: # Otherwise convert hex to bin short_message = hex2bin(updated_request.args['hex-content'][0]) # Authentication user = self.RouterPB.authenticateUser( username=updated_request.args['username'][0], password=updated_request.args['password'][0]) if user is None: self.stats.inc('auth_error_count') self.log.debug( "Authentication failure for username:%s and password:%s", updated_request.args['username'][0], updated_request.args['password'][0]) self.log.error( "Authentication failure for username:%s", updated_request.args['username'][0]) raise AuthenticationError( 'Authentication failure for username:%s' % updated_request.args['username'][0]) # Update CnxStatus user.getCnxStatus().httpapi['connects_count'] += 1 user.getCnxStatus().httpapi['submit_sm_request_count'] += 1 user.getCnxStatus().httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_addr=None if 'from' not in updated_request.args else updated_request.args['from'][0], destination_addr=updated_request.args['to'][0], short_message=short_message, data_coding=int(updated_request.args['coding'][0]), custom_tlvs=updated_request.args['custom_tlvs'][0]) self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Send', user, updated_request, submit_sm=SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Prepare for interception then routing routedConnector = None # init routable = RoutableSubmitSm(SubmitSmPDU, user) self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable, SubmitSmPDU) # Should we tag the routable ? tags = [] if 'tags' in updated_request.args: tags = updated_request.args['tags'][0].split(',') for tag in tags: routable.addTag(tag) self.log.debug('Tagged routable %s: +%s', routable, tag) # Intercept interceptor = self.RouterPB.getMTInterceptionTable().getInterceptorFor(routable) if interceptor is not None: self.log.debug("RouterPB selected %s interceptor for this SubmitSmPDU", interceptor) if self.interceptorpb_client is None: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not set !") raise InterceptorNotSetError('InterceptorPB not set !') if not self.interceptorpb_client.isConnected: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not connected !") raise InterceptorNotConnectedError('InterceptorPB not connected !') script = interceptor.getScript() self.log.debug("Interceptor script loaded: %s", script) # Run ! r = yield self.interceptorpb_client.run_script(script, routable) if isinstance(r, dict) and r['http_status'] != 200: self.stats.inc('interceptor_error_count') self.log.error('Interceptor script returned %s http_status error.', r['http_status']) raise InterceptorRunError( code=r['http_status'], message='Interception specific error code %s' % r['http_status'] ) elif isinstance(r, str): self.stats.inc('interceptor_count') routable = pickle.loads(r) else: self.stats.inc('interceptor_error_count') self.log.error('Failed running interception script, got the following return: %s', r) raise InterceptorRunError(message='Failed running interception script, check log for details') # Get the route route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.stats.inc('route_error_count') self.log.error("No route matched from user %s for SubmitSmPDU: %s", user, routable.pdu) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s route for this SubmitSmPDU", route) routedConnector = route.getConnector() # Is it a failover route ? then check for a bound connector, otherwise don't route # The failover route requires at least one connector to be up, no message enqueuing will # occur otherwise. if repr(route) == 'FailoverMTRoute': self.log.debug('Selected route is a failover, will ensure connector is bound:') while True: c = self.SMPPClientManagerPB.perspective_connector_details(routedConnector.cid) if c: self.log.debug('Connector [%s] is: %s', routedConnector.cid, c['session_state']) else: self.log.debug('Connector [%s] is not found', routedConnector.cid) if c and c['session_state'][:6] == 'BOUND_': # Choose this connector break else: # Check next connector, None if no more connectors are available routedConnector = route.getConnector() if routedConnector is None: break if routedConnector is None: self.stats.inc('route_error_count') self.log.error("Failover route has no bound connector to handle SubmitSmPDU: %s", routable.pdu) raise ConnectorNotFoundError("Failover route has no bound connectors") # Re-update SubmitSmPDU with parameters from the route's connector connector_config = self.SMPPClientManagerPB.perspective_connector_config(routedConnector.cid) if connector_config: connector_config = pickle.loads(connector_config) routable = update_submit_sm_pdu(routable=routable, config=connector_config) # Set priority priority = 0 if 'priority' in updated_request.args: priority = int(updated_request.args['priority'][0]) routable.pdu.params['priority_flag'] = priority_flag_value_map[priority] self.log.debug("SubmitSmPDU priority is set to %s", priority) # Set schedule_delivery_time if 'sdt' in updated_request.args: routable.pdu.params['schedule_delivery_time'] = parse(updated_request.args['sdt'][0]) self.log.debug( "SubmitSmPDU schedule_delivery_time is set to %s (%s)", routable.pdu.params['schedule_delivery_time'], updated_request.args['sdt'][0]) # Set validity_period if 'validity-period' in updated_request.args: delta = timedelta(minutes=int(updated_request.args['validity-period'][0])) routable.pdu.params['validity_period'] = datetime.today() + delta self.log.debug( "SubmitSmPDU validity_period is set to %s (+%s minutes)", routable.pdu.params['validity_period'], updated_request.args['validity-period'][0]) # Set DLR bit mask on the last pdu _last_pdu = routable.pdu while True: if hasattr(_last_pdu, 'nextPdu'): _last_pdu = _last_pdu.nextPdu else: break # DLR setting is clearly described in #107 _last_pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED) if updated_request.args['dlr'][0] == 'yes': _last_pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED) self.log.debug( "SubmitSmPDU registered_delivery is set to %s", str(_last_pdu.params['registered_delivery'])) dlr_level = int(updated_request.args['dlr-level'][0]) if 'dlr-url' in updated_request.args: dlr_url = updated_request.args['dlr-url'][0] else: dlr_url = None if updated_request.args['dlr-level'][0] == '1': dlr_level_text = 'SMS-C' elif updated_request.args['dlr-level'][0] == '2': dlr_level_text = 'Terminal' else: dlr_level_text = 'All' dlr_method = updated_request.args['dlr-method'][0] else: dlr_url = None dlr_level = 0 dlr_level_text = 'No' dlr_method = None # QoS throttling if user.mt_credential.getQuota('http_throughput') >= 0 and user.getCnxStatus().httpapi[ 'qos_last_submit_sm_at'] != 0: qos_throughput_second = 1 / float(user.mt_credential.getQuota('http_throughput')) qos_throughput_ysecond_td = timedelta(microseconds=qos_throughput_second * 1000000) qos_delay = datetime.now() - user.getCnxStatus().httpapi['qos_last_submit_sm_at'] if qos_delay < qos_throughput_ysecond_td: self.stats.inc('throughput_error_count') self.log.error( "QoS: submit_sm_event is faster (%s) than fixed throughput (%s), user:%s, rejecting message.", qos_delay, qos_throughput_ysecond_td, user) raise ThroughputExceededError("User throughput exceeded") user.getCnxStatus().httpapi['qos_last_submit_sm_at'] = datetime.now() # Get number of PDUs to be sent (for billing purpose) _pdu = routable.pdu submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Pre-sending submit_sm: Billing processing bill = route.getBillFor(user) self.log.debug("SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)", bill.bid, bill.getTotalAmounts(), submit_sm_count) charging_requirements = [] u_balance = user.mt_credential.getQuota('balance') u_subsm_count = user.mt_credential.getQuota('submit_sm_count') if u_balance is not None and bill.getTotalAmounts() > 0: # Ensure user have enough balance to pay submit_sm and submit_sm_resp charging_requirements.append({ 'condition': bill.getTotalAmounts() * submit_sm_count <= u_balance, 'error_message': 'Not enough balance (%s) for charging: %s' % ( u_balance, bill.getTotalAmounts())}) if u_subsm_count is not None: # Ensure user have enough submit_sm_count to to cover # the bill action (decrement_submit_sm_count) charging_requirements.append({ 'condition': bill.getAction('decrement_submit_sm_count') * submit_sm_count <= u_subsm_count, 'error_message': 'Not enough submit_sm_count (%s) for charging: %s' % ( u_subsm_count, bill.getAction('decrement_submit_sm_count'))}) if self.RouterPB.chargeUserForSubmitSms(user, bill, submit_sm_count, charging_requirements) is None: self.stats.inc('charging_error_count') self.log.error('Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)', user, bill.bid, bill.getTotalAmounts(), submit_sm_count) raise ChargingError('Cannot charge submit_sm, check RouterPB log file for details') ######################################################## # Send SubmitSmPDU through smpp client manager PB server self.log.debug("Connector '%s' is set to be a route for this SubmitSmPDU", routedConnector.cid) c = self.SMPPClientManagerPB.perspective_submit_sm( uid=user.uid, cid=routedConnector.cid, SubmitSmPDU=routable.pdu, submit_sm_bill=bill, priority=priority, pickled=False, dlr_url=dlr_url, dlr_level=dlr_level, dlr_method=dlr_method) # Build final response if not c.result: self.stats.inc('server_error_count') self.log.error('Failed to send SubmitSmPDU to [cid:%s]', routedConnector.cid) raise ServerError('Cannot send submit_sm, check SMPPClientManagerPB log file for details') else: self.stats.inc('success_count') self.stats.set('last_success_at', datetime.now()) self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s', routedConnector.cid, c.result) response = {'return': c.result, 'status': 200} except Exception as e: self.log.error("Error: %s", e) if hasattr(e, 'code'): response = {'return': e.message, 'status': e.code} else: response = {'return': "Unknown error: %s" % e, 'status': 500} finally: self.log.debug("Returning %s to %s.", response, updated_request.getClientIP()) updated_request.setResponseCode(response['status']) # Default return _return = 'Error "%s"' % response['return'] # Success return if response['status'] == 200 and routedConnector is not None: self.log.info( 'SMS-MT [uid:%s] [cid:%s] [msgid:%s] [prio:%s] [dlr:%s] [from:%s] [to:%s] [content:%s]', user.uid, routedConnector.cid, response['return'], priority, dlr_level_text, routable.pdu.params['source_addr'], updated_request.args['to'][0], re.sub(r'[^\x20-\x7E]+', '.', short_message)) _return = 'Success "%s"' % response['return'] updated_request.write(_return) updated_request.finish()