class SMS: def __init__(self): self.server = kannel_server self.port = kannel_port self.username = kannel_username self.password = kannel_password self.charset = 'UTF-8' self.coding = 2 self.context = 'SMS_LOCAL' self.source = '' self.destination = '' self.internal_destination = '' self.text = '' self.save_sms = 1 self.numbering = Numbering() def filter(self): if self.destination in extensions_list: return False if len(self.destination) < 5: sms_log.info('Dropping SMS on floor because destinaton: %s' % self.destination) return True if self.charset == '8-BIT' and len(self.destination) < 7: sms_log.info('Dropping 8-BIT SMS with destinaton: %s' % self.destination) return True drop_regexp = ['simchautosynchro.+', 'DSAX[0-9]+ND', 'Activate:dt=', 'REG-REQ?v=3;', '^GWDR'] for regexp in drop_regexp: if re.search(regexp, self.text): sms_log.info('Dropping SMS on floor because text matched %s' % regexp) return True return False def sip_sms(self): if use_sip != 'yes': return False if self.destination == '': return False try: sip_endpoints = self.numbering.is_number_sip_connected_no_session(self.destination) sip_endpoint = sip_endpoints.split(',')[0] except Exception as e: sms_log.info('Exception: %s' % e) return False sms_log.info('SIP SMS? %s' % sip_endpoint) if not sip_endpoint: return False m = re.compile('sofia/([a-z]*)/sip:(.*)').search(sip_endpoint) if m: sip_profile = m.group(1) sip_contact = (m.group(2)) params = sip_contact.split(';') # Get fs_path param. res = re.compile('^fs_path=') search = filter(res.match, params) if len(search) > 0: # Have fs_path bracket = re.compile('fs_path=%3C(.*)%3E').search(search[0]) if bracket: params = urllib.unquote(bracket.group(1)).split(';') path = params[0].replace('sip:', '') r = re.compile('received=*') rec = filter(r.match, params) received = rec[0].replace('received=sip:', '') else: import code code.interact(local=locals()) path = search[0] received = urllib.unquote(path).split('=')[1].split('@')[1] else: received = 'None' if sip_profile == 'internalvpn': simple_dest = self.destination+'@'+vpn_ip_address if path == sip_central_ip_address: self.source = self.source+'@sip.rhizomatica.org' simple_dest = self.destination+'@'+ sip_central_ip_address +';received='+received else: simple_dest = sip_profile+'/'+sip_contact try: con = ESL.ESLconnection("127.0.0.1", "8021", "ClueCon") event = ESL.ESLevent("CUSTOM", "SMS::SEND_MESSAGE") sms_log.info('SMS to SIP: Source is %s' % self.source) sms_log.info('SMS to SIP: Dest: %s' % simple_dest) sms_log.info('SMS to SIP: Received: %s' % received) sms_log.info('Text: %s' % self.text.decode(self.charset, 'replace')) sms_log.info('Text: %s' % type(self.text)) sms_log.info('Coding: %s' % self.coding) event.addHeader("from", self.source) event.addHeader("to", simple_dest) event.addHeader("sip_profile", sip_profile) event.addHeader("dest_proto", "sip") event.addHeader("type", "text/plain") if self.coding == '0': msg = self.text.decode('utf8', 'replace') else: msg = self.text.decode(self.charset, 'replace') sms_log.info('Type: %s' % type(msg)) sms_log.info('Text: %s' % msg) event.addBody(msg.encode(self.charset, 'replace')) con.sendEvent(event) return True except Exception as e: api_log.info('Caught Error in sms sip routine: %s' % e) def receive(self, source, destination, text, charset, coding): self.charset = charset self.coding = coding self.source = source self.text = text self.internal_destination = destination if destination.find('+') > 1: destination = destination.split('+')[0] self.destination = destination sms_log.info('Received SMS: %s %s %s %s %s' % (source, destination, text, charset, coding)) #sms_log.info(binascii.hexlify(text)) # SMS_LOCAL | SMS_INTERNAL | SMS_INBOUND | SMS_OUTBOUND | SMS_ROAMING # some seemingly autogenerated SMS we just want to drop on the floor: try: if self.filter(): return except Exception as e: api_log.info('Caught an Error in sms:filter %s' % e) pass #if sip_sms(): # return try: sub = Subscriber() # check if source or destination is roaming try: if not (source == '10000' or self.numbering.is_number_known(source)): sms_log.info('Sender unauthorized send notification') self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return if self.numbering.is_number_roaming(source): sms_log.info('Source number is roaming') self.roaming('caller') return except NumberingException as e: sms_log.info('Sender unauthorized send notification message (exception)') self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return try: if self.numbering.is_number_roaming(destination): sms_log.info('Destination number is roaming') self.roaming('called') return except NumberingException as e: sms_log.info('Destination unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_destination_unauthorized']) return try: source_authorized = sub.is_authorized(source, 0) except SubscriberException as e: source_authorized = False try: destination_authorized = sub.is_authorized(destination, 0) except SubscriberException as e: destination_authorized = False sms_log.info('Source_authorized: %s Destination_authorized: %s' % (str(source_authorized), str(destination_authorized))) if not source_authorized and not self.numbering.is_number_internal(source): sms_log.info('Sender unauthorized send notification message (EXT)') self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return if self.numbering.is_number_local(destination): sms_log.info('SMS_LOCAL check if subscriber is authorized') # get auth info sub = Subscriber() source_authorized = sub.is_authorized(source, 0) destination_authorized = sub.is_authorized(destination, 0) try: if source_authorized and destination_authorized: sms_log.info('Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_LOCAL' # Decision was not to send coding on here..... self.send(source, destination, text, charset) else: if not self.numbering.is_number_local(source) and destination_authorized: sms_log.info('SMS_INTERNAL Forward SMS back to BSC') self.context = 'SMS_INTERNAL' self.send(source, destination, text, charset) else: if destination_authorized and not self.numbering.is_number_local(source): sms_log.info('SMS_INBOUND Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_INBOUND' self.send(source, destination, text, charset) else: self.charset = 'UTF-8' self.coding = 2 self.save_sms = 0 self.context = 'SMS_UNAUTH' if not source_authorized and len(destination) != 3: sms_log.info('Sender unauthorized send notification message') self.send(config['smsc'], source, config['sms_source_unauthorized']) else: sms_log.info('Destination unauthorized inform sender with a notification message') self.send(config['smsc'], source, config['sms_destination_unauthorized']) except SubscriberException as e: raise SMSException('Receive SMS error: %s' % e) else: # dest number is not local, check if dest number is a shortcode if destination in extensions_list: sms_log.info('Destination number is a shortcode, execute shortcode handler') extension = importlib.import_module('extensions.ext_'+destination, 'extensions') try: sms_log.debug('Exec shortcode handler') extension.handler('', source, destination, text) except ExtensionException as e: raise SMSException('Receive SMS error: %s' % e) else: # check if sms is for another location if self.numbering.is_number_internal(destination) and len(destination) == 11: sms_log.info('SMS is for another site') try: site_ip = self.numbering.get_site_ip(destination) sms_log.info('Send SMS to site IP: %s' % site_ip) self.context = 'SMS_INTERNAL' self.send(source, destination, text, self.charset, site_ip) except NumberingException as e: raise SMSException('Receive SMS error: %s' % e) elif len(destination) != 3: # dest number is for an external number send sms to sms provider self.context = 'SMS_OUTBOUND' sms_log.info('SMS is for an external number send SMS to SMS provider') self.send(config['smsc'], source, 'Lo sentimos, destino '+str(destination)+ ' no disponible', 'utf-8') else: sms_log.info('SMS for %s was dropped' % destination) except NumberingException as e: raise SMSException('Receive SMS Error: %s' % e) def prepare_txt_for_kannel(self, text, charset): # Kannel wants a coding param in the POST # GSM 03.38=0 UTF-8=1, UCS2=2 # and we need a str. charset_to_kannel_coding = {'0':'UTF-8', '2':'UTF-16BE'} if type(text) == unicode: sms_log.debug('Have unicode') self.coding = self.determine_coding(text) self.charset = charset_to_kannel_coding[self.coding] str_text = text.encode(self.charset) return (str_text, text) try: sms_log.debug('Have string, trying %s', charset) unicode_text = unicode(text, charset) self.coding = self.determine_coding(unicode_text) self.charset = charset_to_kannel_coding[self.coding] str_text = unicode_text.encode(self.charset) return (str_text, unicode_text) except Exception as ex: sms_log.info('Encoding Error: %s', str(ex)) self.charset = self.determine_coding(text, charset) unicode_text = text.decode(self.charset) str_text = text.decode('UTF-8', 'replace').encode(self.charset) return (str_text, unicode_text) def send(self, source, destination, text, charset='utf-8', server=config['local_ip']): ''' Send an SMS either: 1) To the local system via: a) HTTP POST to kannel or b) SUMBIT_SM using libsmpp to local SMSC listener 2) http POST to a remote RAPI:receive() ''' sms_log.info('SMS Send: Text: <%s> Charset: %s' % (text, charset)) # We don't trust the caller to send us unicode, or to send a correct charset, if any. sms_log.info('Type of text: %s', type(text)) if 'use_kannel' in globals() and use_kannel == 'yes': str_text, unicode_text = self.prepare_txt_for_kannel(text, charset) else: str_text = '' if type(text) != unicode: # this could crash if we are fed bullshit. unicode_text = text.decode(charset) else: unicode_text = text if server == config['local_ip']: try: sms_log.info('Send SMS to Local: %s %s %s' % (source, destination, text)) self.local_smpp_submit_sm(source, destination, unicode_text, str_text) if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) return True except SMSException as ex: sms_log.error("Local submit failed: %s" % str(ex)) return False try: sms_log.info('Send SMS to %s: %s %s %s' % (server, source, destination, unicode_text)) if "+" not in self.internal_destination: destination = destination + '+1' else: s = self.internal_destination.split('+') destination = s[0] + '+' + str(int(s[1]) + 1) if int(s[1]) > 4: sms_log.error("!! SMS is Looping(%s)", s[1]) values = {'source': source, 'destination': destination, 'charset': self.charset, 'coding': self.coding, 'text': str_text, 'btext': '', 'dr': '', 'dcs': ''} data = urllib.urlencode(values) t = Thread(target=self._t_urlopen, args=(server, data)) t.start() sms_log.info('Started Remote RAPI Thread') if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: # Never happen.... raise SMSException('Error sending SMS to site %s' % server) def local_smpp_submit_sm(self, source, destination, unicode_text, str_text=''): if 'use_kannel' in globals() and use_kannel == 'yes': try: enc_text = urllib.urlencode({'text': str_text}) kannel_post = "http://%s:%d/cgi-bin/sendsms?username=%s&password=%s&charset=%s&coding=%s&to=%s&from=%s&%s"\ % (self.server, self.port, self.username, self.password, self.charset, self.coding, destination, source, enc_text) sms_log.info('Kannel URL: %s' % (kannel_post)) res = urllib.urlopen(kannel_post).read() sms_log.info('Kannel Result: %s' % (res)) return except IOError: raise SMSException('Error connecting to Kannel to send SMS') global _sent def _smpp_rx_submit_resp(pdu): global _sent sms_log.info("Sent (%s)", pdu.message_id) if pdu.command == "submit_sm_resp": _sent = pdu.status try: source = network_name if source == '10000' else source if not source.isdigit(): ston = smpplib.consts.SMPP_TON_ALNUM snpi = smpplib.consts.SMPP_NPI_UNK else: ston = smpplib.consts.SMPP_TON_SBSCR snpi = smpplib.consts.SMPP_NPI_ISDN parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts(unicode_text) smpp_client = smpplib.client.Client("127.0.0.1", 2775, 90) smpplib.client.logger.setLevel('INFO') smpp_client.set_message_received_handler(lambda pdu: sms_log.info("Rcvd while sending (%s)", pdu.command)) smpp_client.set_message_sent_handler(_smpp_rx_submit_resp) smpp_client.connect() smpp_client.bind_transmitter(system_id="ISMPP", password="******") _sent = -1 for part in parts: pdu = smpp_client.send_message( source_addr_ton=ston, source_addr_npi=snpi, source_addr=str(source), dest_addr_ton=smpplib.consts.SMPP_TON_SBSCR, dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN, destination_addr=str(destination), data_coding=encoding_flag, esm_class=msg_type_flag, short_message=part, registered_delivery=False, ) while _sent < 0: smpp_client.read_once() smpp_client.unbind() smpp_client.disconnect() del pdu del smpp_client except (IOError, smpplib.exceptions.ConnectionError) as ex: raise SMSException('Unable to Submit Message via SMPP %s' % str(ex)) except smpplib.exceptions.PDUError as ex: smpp_client.unbind() smpp_client.disconnect() raise SMSException('SMPP Error Submitting Message %s' % str(ex)) def check_decode0338(self, text): try: return text.decode('gsm03.38') except Exception as ex: sms_log.error(str(ex)) try: gsm_shift_codec = gsm0338.Codec(single_shift_decode_map=gsm0338.SINGLE_SHIFT_CHARACTER_SET_SPANISH) return gsm_shift_codec.decode(text)[0] except Exception as ex: sms_log.error(str(ex)) def determine_coding(self, unicode_str): if type(unicode_str) != unicode: raise SMSException('Input is not unicode') try: try: _test0338 = unicode_str.encode('gsm03.38') sms_log.debug('GSM03.38 OK "%s" -> "%s"' % (unicode_str, _test0338.decode('gsm03.38'))) return '0' except ValueError as ex: sms_log.debug('Encoding to GSM03.38 default alphabet not possible. %s' % sys.exc_info()[1]) _test0338s = gsm0338.Codec(single_shift_decode_map=gsm0338.SINGLE_SHIFT_CHARACTER_SET_SPANISH) _test = _test0338s.encode(unicode_str)[0] sms_log.debug('GSM03.38 Spanish Shift OK "%s" -> "%s"' % (unicode_str, _test0338s.decode(_test)[0])) return '2' except Exception as ex: template = "exception of type {0}. Arguments:\n{1!r}" print template.format(type(ex).__name__, ex.args) sms_log.debug('Using GSM03.38 Spanish Shift not possible. %s' % sys.exc_info()[1]) return '2' def roaming(self, subject): self.numbering = Numbering() self.subscriber = Subscriber() if subject == 'caller': # calling number is roaming # check if destination number is roaming as well if self.numbering.is_number_roaming(self.destination): # well destination number is roaming as well send SMS to current_bts where the subscriber is roaming try: current_bts = self.numbering.get_current_bts(self.destination) sms_log.info('Destination number is roaming send SMS to current_bts: %s' % current_bts) if current_bts == config['local_ip']: log.info('Current bts same as local site send call to local Kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text, self.charset) else: # send sms to destination site self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) except NumberingException as e: sms_log.error(e) else: # destination is not roaming check if destination if local site if self.numbering.is_number_local(self.destination) and len(self.destination) == 11: sms_log.info('Destination is a local number') if self.subscriber.is_authorized(self.destination, 0): sms_log.info('Send sms to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # destination cannot receive SMS inform source self.context = 'SMS_ROAMING_UNAUTH' # Why receive here? Why not send? self.receive(config['smsc'], source, config['sms_destination_unauthorized'], self.charset, self.coding) else: # number is not local check if number is internal if self.numbering.is_number_internal(self.destination) and len(self.destination) == 11: # number is internal send SMS to destination site current_bts = self.numbering.get_site_ip(self.destination) self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) else: # check if number is for outbound. # not implemented yet. just return sms_log.info('Invalid destination for SMS') return else: # the destination is roaming send call to current_bts try: current_bts = self.numbering.get_current_bts(self.destination) if current_bts == config['local_ip']: sms_log.info('Destination is roaming on our site send SMS to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text, self.charset) else: sms_log.info('Destination is roaming send sms to other site') self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) except NumberingException as e: sms_log.error(e) def save(self, source, destination, context): # insert SMS in the history try: cur = db_conn.cursor() cur.execute('INSERT INTO sms(source_addr,destination_addr,context) VALUES(%s,%s,%s)', (source, destination, context)) except psycopg2.DatabaseError as e: db_conn.rollback() raise SMSException('PG_HLR error saving SMS in the history: %s' % e) finally: cur.close() db_conn.commit() def send_immediate(self, num, text): appstring = 'OpenBSC' appport = 4242 vty = obscvty.VTYInteract(appstring, '127.0.0.1', appport) cmd = 'subscriber extension %s sms sender extension %s send %s' % (num, config['smsc'], text) vty.command(cmd) def broadcast_to_all_subscribers(self, text, btype): sub = Subscriber() if btype == 'all': subscribers_list = sub.get_all() elif btype == 'notpaid': subscribers_list = sub.get_all_notpaid() elif btype == 'unauthorized': subscribers_list = sub.get_all_unauthorized() elif btype == 'extension': subscribers_list = sub.get_all_5digits() for mysub in subscribers_list: self.send(config['smsc'], mysub[1], text) sms_log.debug('Broadcast message sent to %s' % mysub[1]) time.sleep(1) def send_broadcast(self, text, btype): sms_log.info('Send broadcast SMS to all subscribers. text: %s' % text) t = Thread(target=self.broadcast_to_all_subscribers, args=(text, btype, )) t.start() def _t_urlopen(self, url, data): try: res = urllib.urlopen('http://%s:8085/sms' % url, data) res.read() res.close() return res except IOError as ex: sms_log.error("FIMXE: SMS is lost (%s)", ex) return False
class SMS: def __init__(self): self.server = kannel_server self.port = kannel_port self.username = kannel_username self.password = kannel_password self.charset = 'UTF-8' self.coding = 2 self.context = 'SMS_LOCAL' self.source = '' self.destination = '' self.text = '' self.save_sms = 1 self.numbering = Numbering() def receive(self, source, destination, text, charset, coding): self.charset = charset self.coding = coding self.source = source self.destination = destination self.text = text sms_log.info('Received SMS: %s %s' % (source, destination)) # SMS_LOCAL | SMS_INTERNAL | SMS_INBOUND | SMS_OUTBOUND | SMS_ROAMING try: # auth checks # get auth info sub = Subscriber() # check if source or destination is roaming try: if self.numbering.is_number_roaming(source): sms_log.info('Source number is roaming') self.roaming('caller') return except NumberingException as e: sms_log.info('Sender unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_source_unauthorized']) return try: if self.numbering.is_number_roaming(destination): sms_log.info('Destination number is roaming') self.roaming('called') return except NumberingException as e: sms_log.info('Destination unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_destination_unauthorized']) return try: source_authorized = sub.is_authorized(source, 0) except SubscriberException as e: source_authorized = False try: destination_authorized = sub.is_authorized(destination, 0) except SubscriberException as e: destination_authorized = False sms_log.info('Source_authorized: %s Destination_authorized: %s' % (str(source_authorized), str(destination_authorized))) if not source_authorized and not self.numbering.is_number_internal(source): sms_log.info('Sender unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_source_unauthorized']) return if self.numbering.is_number_local(destination): sms_log.info('SMS_LOCAL check if subscriber is authorized') # get auth info sub = Subscriber() source_authorized = sub.is_authorized(source, 0) destination_authorized = sub.is_authorized(destination, 0) try: if source_authorized and destination_authorized: sms_log.info('Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_LOCAL' self.send(source, destination, text) else: if not self.numbering.is_number_local(source) and destination_authorized: sms_log.info('SMS_INTERNAL Forward SMS back to BSC') self.context = 'SMS_INTERNAL' self.send(source, destination, text) else: if destination_authorized and not self.numbering.is_number_local(source): sms_log.info('SMS_INBOUND Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_INBOUND' self.send(source, destination, text) else: self.charset = 'UTF-8' self.coding = 2 self.save_sms = 0 self.context = 'SMS_UNAUTH' if not source_authorized: sms_log.info('Sender unauthorized send notification message') self.send(config['smsc'], source, config['sms_source_unauthorized']) else: sms_log.info('Destination unauthorized inform sender with a notification message') self.send(config['smsc'], source, config['sms_destination_unauthorized']) except SubscriberException as e: raise SMSException('Receive SMS error: %s' % e) else: # dest number is not local, check if dest number is a shortcode if destination in extensions_list: sms_log.info('Destination number is a shortcode, execute shortcode handler') extension = importlib.import_module('extensions.ext_'+destination, 'extensions') try: sms_log.debug('Exec shortcode handler') extension.handler('', source, destination, text) except ExtensionException as e: raise SMSException('Receive SMS error: %s' % e) else: # check if sms is for another location if self.numbering.is_number_internal(destination) and len(destination) == 11: sms_log.info('SMS is for another site') try: site_ip = self.numbering.get_site_ip(destination) sms_log.info('Send SMS to site IP: %s' % site_ip) self.context = 'SMS_INTERNAL' self.send(source, destination, text, site_ip) except NumberingException as e: raise SMSException('Receive SMS error: %s' % e) else: # dest number is for an external number send sms to sms provider self.context = 'SMS_OUTBOUND' sms_log.info('SMS is for an external number send SMS to SMS provider') except NumberingException as e: raise SMSException('Receive SMS Error: %s' % e) def send(self, source, destination, text, server=config['local_ip']): enc_text = urllib.urlencode({'text': text }) if server == config['local_ip']: try: sms_log.info('Send SMS: %s %s' % (source, destination)) res = urllib.urlopen( "http://%s:%d/cgi-bin/sendsms?username=%s&password=%s&charset=%s&coding=%s&to=%s&from=%s&%s"\ % (self.server, self.port, self.username, self.password, self.charset, self.coding, destination, source, enc_text) ).read() if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: raise SMSException('Error connecting to Kannel to send SMS') else: try: sms_log.info('Send SMS to %s: %s %s' % (server, source, destination)) values = {'source': source, 'destination': destination, 'charset': self.charset, 'coding': self.coding, 'text': text } data = urllib.urlencode(values) res = urllib.urlopen('http://%s:8085/sms' % server, data).read() if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: raise SMSException('Error sending SMS to site %s' % server) def roaming(self, subject): self.numbering = Numbering() self.subscriber = Subscriber() if subject == 'caller': # calling number is roaming # check if destination number is roaming as well if self.numbering.is_number_roaming(self.destination): # well destination number is roaming as well send SMS to current_bts where the subscriber is roaming try: current_bts = self.numbering.get_current_bts(self.destination) sms_log.info('Destination number is roaming send SMS to current_bts: %s' % current_bts) if current_bts == config['local_ip']: log.info('Current bts same as local site send call to local Kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # send sms to destination site self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, current_bts) except NumberingException as e: sms_log.error(e) else: # destination is not roaming check if destination if local site if self.numbering.is_number_local(self.destination) and len(self.destination) == 11: sms_log.info('Destination is a local number') if self.subscriber.is_authorized(self.destination, 0): sms_log.info('Send sms to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # destination cannot receive SMS inform source self.context = 'SMS_ROAMING_UNAUTH' self.receive(config['smsc'], source, config['sms_destination_unauthorized'], self.charset, self.coding) else: # number is not local check if number is internal if self.numbering.is_number_internal(self.destination) and len(self.destination) == 11: # number is internal send SMS to destination site current_bts = self.numbering.get_site_ip(self.destination) self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, current_bts) else: # check if number is for outbound. # not implemented yet. just return sms_log.info('Invalid destination for SMS') return else: # the destination is roaming send call to current_bts try: current_bts = self.numbering.get_current_bts(self.destination) if current_bts == config['local_ip']: sms_log.info('Destination is roaming on our site send SMS to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: sms_log.info('Destination is roaming send sms to other site') self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, current_bts) except NumberingException as e: sms_log.error(e) def save(self, source, destination, context): # insert SMS in the history try: cur = db_conn.cursor() cur.execute('INSERT INTO sms(source_addr,destination_addr,context) VALUES(%s,%s,%s)', (source, destination, context)) except psycopg2.DatabaseError as e: db_conn.rollback() raise SMSException('PG_HLR error saving SMS in the history: %s' % e) finally: cur.close() db_conn.commit() def send_immediate(self, num, text): appstring = 'OpenBSC' appport = 4242 vty = obscvty.VTYInteract(appstring, '127.0.0.1', appport) cmd = 'subscriber extension %s sms sender extension %s send %s' % (num, config['smsc'], text) vty.command(cmd) def broadcast_to_all_subscribers(self, text, btype): sub = Subscriber() if btype == 'all': subscribers_list = sub.get_all() elif btype == 'notpaid': subscribers_list = sub.get_all_notpaid() elif btype == 'unauthorized': subscribers_list = sub.get_all_unauthorized() elif btype == 'extension': subscribers_list = sub.get_all_5digits() for mysub in subscribers_list: self.send(config['smsc'], mysub[1], text) sms_log.debug('Broadcast message sent to %s' % mysub[1]) time.sleep(1) def send_broadcast(self, text, btype): sms_log.info('Send broadcast SMS to all subscribers. text: %s' % text) t = Thread(target=self.broadcast_to_all_subscribers, args=(text, btype, )) t.start()
class SMS: def __init__(self): self.server = kannel_server self.port = kannel_port self.username = kannel_username self.password = kannel_password self.charset = 'UTF-8' self.coding = 2 self.context = 'SMS_LOCAL' self.source = '' self.destination = '' self.text = '' self.save_sms = 1 self.numbering = Numbering() def receive(self, source, destination, text, charset, coding): self.charset = charset self.coding = coding self.source = source self.destination = destination self.text = text sms_log.info('Received SMS: %s %s' % (source, destination)) # SMS_LOCAL | SMS_INTERNAL | SMS_INBOUND | SMS_OUTBOUND | SMS_ROAMING try: # auth checks # get auth info sub = Subscriber() # check if source or destination is roaming try: if self.numbering.is_number_roaming(source): sms_log.info('Source number is roaming') self.roaming('caller') return except NumberingException as e: sms_log.info('Sender unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_source_unauthorized']) return try: if self.numbering.is_number_roaming(destination): sms_log.info('Destination number is roaming') self.roaming('called') return except NumberingException as e: sms_log.info( 'Destination unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_destination_unauthorized']) return try: source_authorized = sub.is_authorized(source, 0) except SubscriberException as e: source_authorized = False try: destination_authorized = sub.is_authorized(destination, 0) except SubscriberException as e: destination_authorized = False sms_log.info('Source_authorized: %s Destination_authorized: %s' % (str(source_authorized), str(destination_authorized))) if not source_authorized and not self.numbering.is_number_internal( source): sms_log.info('Sender unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_source_unauthorized']) return if self.numbering.is_number_local(destination): sms_log.info('SMS_LOCAL check if subscriber is authorized') # get auth info sub = Subscriber() source_authorized = sub.is_authorized(source, 0) destination_authorized = sub.is_authorized(destination, 0) try: if source_authorized and destination_authorized: sms_log.info('Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_LOCAL' self.send(source, destination, text) else: if not self.numbering.is_number_local( source) and destination_authorized: sms_log.info( 'SMS_INTERNAL Forward SMS back to BSC') self.context = 'SMS_INTERNAL' self.send(source, destination, text) else: if destination_authorized and not self.numbering.is_number_local( source): sms_log.info( 'SMS_INBOUND Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_INBOUND' self.send(source, destination, text) else: self.charset = 'UTF-8' self.coding = 2 self.save_sms = 0 self.context = 'SMS_UNAUTH' if not source_authorized: sms_log.info( 'Sender unauthorized send notification message' ) self.send( config['smsc'], source, config['sms_source_unauthorized']) else: sms_log.info( 'Destination unauthorized inform sender with a notification message' ) self.send( config['smsc'], source, config['sms_destination_unauthorized']) except SubscriberException as e: raise SMSException('Receive SMS error: %s' % e) else: # dest number is not local, check if dest number is a shortcode if destination in extensions_list: sms_log.info( 'Destination number is a shortcode, execute shortcode handler' ) extension = importlib.import_module( 'extensions.ext_' + destination, 'extensions') try: sms_log.debug('Exec shortcode handler') extension.handler('', source, destination, text) except ExtensionException as e: raise SMSException('Receive SMS error: %s' % e) else: # check if sms is for another location if self.numbering.is_number_internal(destination) and len( destination) == 11: sms_log.info('SMS is for another site') try: site_ip = self.numbering.get_site_ip(destination) sms_log.info('Send SMS to site IP: %s' % site_ip) self.context = 'SMS_INTERNAL' self.send(source, destination, text, site_ip) except NumberingException as e: raise SMSException('Receive SMS error: %s' % e) else: # dest number is for an external number send sms to sms provider self.context = 'SMS_OUTBOUND' sms_log.info( 'SMS is for an external number send SMS to SMS provider' ) except NumberingException as e: raise SMSException('Receive SMS Error: %s' % e) def send(self, source, destination, text, server=config['local_ip']): enc_text = urllib.urlencode({'text': text}) if server == config['local_ip']: try: sms_log.info('Send SMS: %s %s' % (source, destination)) res = urllib.urlopen( "http://%s:%d/cgi-bin/sendsms?username=%s&password=%s&charset=%s&coding=%s&to=%s&from=%s&%s"\ % (self.server, self.port, self.username, self.password, self.charset, self.coding, destination, source, enc_text) ).read() if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: raise SMSException('Error connecting to Kannel to send SMS') else: try: sms_log.info('Send SMS to %s: %s %s' % (server, source, destination)) values = { 'source': source, 'destination': destination, 'charset': self.charset, 'coding': self.coding, 'text': text } data = urllib.urlencode(values) res = urllib.urlopen('http://%s:8085/sms' % server, data).read() if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: raise SMSException('Error sending SMS to site %s' % server) def roaming(self, subject): self.numbering = Numbering() self.subscriber = Subscriber() if subject == 'caller': # calling number is roaming # check if destination number is roaming as well if self.numbering.is_number_roaming(self.destination): # well destination number is roaming as well send SMS to current_bts where the subscriber is roaming try: current_bts = self.numbering.get_current_bts( self.destination) sms_log.info( 'Destination number is roaming send SMS to current_bts: %s' % current_bts) if current_bts == config['local_ip']: log.info( 'Current bts same as local site send call to local Kannel' ) self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # send sms to destination site self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, current_bts) except NumberingException as e: sms_log.error(e) else: # destination is not roaming check if destination if local site if self.numbering.is_number_local(self.destination) and len( self.destination) == 11: sms_log.info('Destination is a local number') if self.subscriber.is_authorized(self.destination, 0): sms_log.info('Send sms to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # destination cannot receive SMS inform source self.context = 'SMS_ROAMING_UNAUTH' self.receive(config['smsc'], source, config['sms_destination_unauthorized'], self.charset, self.coding) else: # number is not local check if number is internal if self.numbering.is_number_internal( self.destination) and len(self.destination) == 11: # number is internal send SMS to destination site current_bts = self.numbering.get_site_ip( self.destination) self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, current_bts) else: # check if number is for outbound. # not implemented yet. just return sms_log.info('Invalid destination for SMS') return else: # the destination is roaming send call to current_bts try: current_bts = self.numbering.get_current_bts(self.destination) if current_bts == config['local_ip']: sms_log.info( 'Destination is roaming on our site send SMS to local kannel' ) self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: sms_log.info( 'Destination is roaming send sms to other site') self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, current_bts) except NumberingException as e: sms_log.error(e) def save(self, source, destination, context): # insert SMS in the history try: cur = db_conn.cursor() cur.execute( 'INSERT INTO sms(source_addr,destination_addr,context) VALUES(%s,%s,%s)', (source, destination, context)) except psycopg2.DatabaseError as e: db_conn.rollback() raise SMSException('PG_HLR error saving SMS in the history: %s' % e) finally: cur.close() db_conn.commit() def send_immediate(self, num, text): appstring = 'OpenBSC' appport = 4242 vty = obscvty.VTYInteract(appstring, '127.0.0.1', appport) cmd = 'subscriber extension %s sms sender extension %s send %s' % ( num, config['smsc'], text) vty.command(cmd) def broadcast_to_all_subscribers(self, text, btype): sub = Subscriber() if btype == 'all': subscribers_list = sub.get_all() elif btype == 'notpaid': subscribers_list = sub.get_all_notpaid() elif btype == 'unauthorized': subscribers_list = sub.get_all_unauthorized() elif btype == 'extension': subscribers_list = sub.get_all_5digits() for mysub in subscribers_list: self.send(config['smsc'], mysub[1], text) sms_log.debug('Broadcast message sent to %s' % mysub[1]) time.sleep(1) def send_broadcast(self, text, btype): sms_log.info('Send broadcast SMS to all subscribers. text: %s' % text) t = Thread(target=self.broadcast_to_all_subscribers, args=( text, btype, )) t.start()
class SMS: def __init__(self): self.server = kannel_server self.port = kannel_port self.username = kannel_username self.password = kannel_password self.charset = 'UTF-8' self.coding = 2 self.context = 'SMS_LOCAL' self.source = '' self.destination = '' self.internal_destination = '' self.text = '' self.save_sms = 1 self.numbering = Numbering() def filter(self): if self.destination in extensions_list: return False if len(self.destination) < 5: sms_log.info('Dropping SMS on floor because destinaton: %s' % self.destination) return True if self.charset == '8-BIT' and len(self.destination) < 7: sms_log.info('Dropping 8-BIT SMS with destinaton: %s' % self.destination) return True drop_regexp = [ 'simchautosynchro.+', 'DSAX[0-9]+ND', 'Activate:dt=', 'REG-REQ?v=3;', '^GWDR' ] for regexp in drop_regexp: if re.search(regexp, self.text): sms_log.info('Dropping SMS on floor because text matched %s' % regexp) return True return False def webphone_sms(self, source, destination, text, coding): if not ('webphone_prefix' in globals() and isinstance(webphone_prefix, list) and isinstance(sip_central_ip_address, list)): sms_log.warning('SMS for Webphone but required config missing.') return False if not self.destination[:5] in webphone_prefix: sms_log.warning('WEBPHONE SMS for non webphone extension?') return False sms_log.debug('WEBPHONE SMS from %s for %s [%s] [%s]', (source, destination, text, coding)) self.source = source + '@sip.rhizomatica.org' self.destination = destination self.text = text simple_dest = self.destination + '@' + sip_central_ip_address[0] sip_profile = 'outgoing' try: event = ESL.ESLevent("CUSTOM", "SMS::SEND_MESSAGE") sms_log.debug('SMS to SIP: Source is %s' % self.source) sms_log.debug('SMS to SIP: Dest: %s' % simple_dest) sms_log.debug('Text: %s' % self.text.decode(self.charset, 'replace')) event.addHeader("from", self.source) event.addHeader("to", simple_dest) event.addHeader("sip_profile", sip_profile) event.addHeader("dest_proto", "sip") event.addHeader("type", "text/plain") # Todo, see how we can actually get the result of this back here? #event.addHeader("blocking", "true") event.addBody(text.encode(self.charset, 'replace')) con = ESL.ESLconnection("127.0.0.1", "8021", "ClueCon") ret = con.sendEvent(event) con.disconnect() sms_log.info('WEBPHONE SMS SENT Status:[%s]', ret) return True except Exception as excep: sms_log.info('Exception with Webphone SMS or FS Event: %s' % excep) return False def sip_sms(self): if use_sip != 'yes': return False if self.destination == '': return False try: sip_endpoints = self.numbering.is_number_sip_connected_no_session( self.destination) sip_endpoint = sip_endpoints.split(',')[0] except Exception as e: sms_log.info('Exception: %s' % e) return False sms_log.info('SIP SMS? %s' % sip_endpoint) if not sip_endpoint: return False m = re.compile('sofia/([a-z]*)/sip:(.*)').search(sip_endpoint) if m: sip_profile = m.group(1) sip_contact = (m.group(2)) params = sip_contact.split(';') # Get fs_path param. res = re.compile('^fs_path=') search = filter(res.match, params) if len(search) > 0: # Have fs_path bracket = re.compile('fs_path=%3C(.*)%3E').search(search[0]) if bracket: params = urllib.unquote(bracket.group(1)).split(';') path = params[0].replace('sip:', '') r = re.compile('received=*') rec = filter(r.match, params) received = rec[0].replace('received=sip:', '') else: import code code.interact(local=locals()) path = search[0] received = urllib.unquote(path).split('=')[1].split('@')[1] else: received = 'None' if sip_profile == 'internalvpn': simple_dest = self.destination + '@' + vpn_ip_address if path == sip_central_ip_address: self.source = self.source + '@sip.rhizomatica.org' simple_dest = self.destination + '@' + sip_central_ip_address + ';received=' + received else: simple_dest = sip_profile + '/' + sip_contact try: con = ESL.ESLconnection("127.0.0.1", "8021", "ClueCon") event = ESL.ESLevent("CUSTOM", "SMS::SEND_MESSAGE") sms_log.info('SMS to SIP: Source is %s' % self.source) sms_log.info('SMS to SIP: Dest: %s' % simple_dest) sms_log.info('SMS to SIP: Received: %s' % received) sms_log.info('Text: %s' % self.text.decode(self.charset, 'replace')) sms_log.info('Text: %s' % type(self.text)) sms_log.info('Coding: %s' % self.coding) event.addHeader("from", self.source) event.addHeader("to", simple_dest) event.addHeader("sip_profile", sip_profile) event.addHeader("dest_proto", "sip") event.addHeader("type", "text/plain") if self.coding == '0': msg = self.text.decode('utf8', 'replace') else: msg = self.text.decode(self.charset, 'replace') sms_log.info('Type: %s' % type(msg)) sms_log.info('Text: %s' % msg) event.addBody(msg.encode(self.charset, 'replace')) con.sendEvent(event) return True except Exception as e: api_log.info('Caught Error in sms sip routine: %s' % e) def receive(self, source, destination, text, charset, coding): self.charset = charset self.coding = coding self.source = source self.text = text self.internal_destination = destination if destination.find('+') > 1: destination = destination.split('+')[0] self.destination = destination sms_log.info('Received SMS: %s %s %s %s %s' % (source, destination, text, charset, coding)) #sms_log.info(binascii.hexlify(text)) # SMS_LOCAL | SMS_INTERNAL | SMS_INBOUND | SMS_OUTBOUND | SMS_ROAMING # some seemingly autogenerated SMS we just want to drop on the floor: try: if self.filter(): return except Exception as e: api_log.info('Caught an Error in sms:filter %s' % e) pass #if sip_sms(): # return try: sub = Subscriber() # check if source or destination is roaming try: if not (source == '10000' or self.numbering.is_number_known(source)): sms_log.info('Sender unauthorized send notification') self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return if self.numbering.is_number_roaming(source): sms_log.info('Source number is roaming') self.roaming('caller') return except NumberingException as e: sms_log.info( 'Sender unauthorized send notification message (exception)' ) self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return try: if self.numbering.is_number_roaming(destination): sms_log.info('Destination number is roaming') self.roaming('called') return except NumberingException as e: sms_log.info( 'Destination unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_destination_unauthorized']) return try: source_authorized = sub.is_authorized(source, 0) except SubscriberException as e: source_authorized = False try: destination_authorized = sub.is_authorized(destination, 0) except SubscriberException as e: destination_authorized = False sms_log.info('Source_authorized: %s Destination_authorized: %s' % (str(source_authorized), str(destination_authorized))) if (not source_authorized and not self.numbering.is_number_internal(source) and not self.numbering.is_number_webphone(source)): sms_log.info( 'Sender unauthorized send notification message (EXT)') self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return if self.numbering.is_number_local(destination): sms_log.info('SMS_LOCAL check if subscriber is authorized') # get auth info sub = Subscriber() source_authorized = sub.is_authorized(source, 0) destination_authorized = sub.is_authorized(destination, 0) try: if source_authorized and destination_authorized: sms_log.info('Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_LOCAL' # Decision was not to send coding on here..... self.send(source, destination, text, charset) else: if not self.numbering.is_number_local( source) and destination_authorized: sms_log.info( 'SMS_INTERNAL Forward SMS back to BSC') self.context = 'SMS_INTERNAL' self.send(source, destination, text, charset) else: if destination_authorized and not self.numbering.is_number_local( source): sms_log.info( 'SMS_INBOUND Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_INBOUND' self.send(source, destination, text, charset) else: self.charset = 'UTF-8' self.coding = 2 self.save_sms = 0 self.context = 'SMS_UNAUTH' if not source_authorized and len( destination) != 3: sms_log.info( 'Sender unauthorized send notification message' ) self.send( config['smsc'], source, config['sms_source_unauthorized']) else: sms_log.info( 'Destination unauthorized inform sender with a notification message' ) self.send( config['smsc'], source, config['sms_destination_unauthorized']) except SubscriberException as e: raise SMSException('Receive SMS error: %s' % e) else: # dest number is not local, check if dest number is a shortcode if destination in extensions_list: sms_log.info( 'Destination number is a shortcode, execute shortcode handler' ) extension = importlib.import_module( 'extensions.ext_' + destination, 'extensions') try: sms_log.debug('Exec shortcode handler') extension.handler('', source, destination, text) except ExtensionException as e: raise SMSException('Receive SMS error: %s' % e) else: # check if sms is for another location if self.numbering.is_number_webphone(destination): self.webphone_sms(source, destination, text, self.coding) return if self.numbering.is_number_internal(destination) and len( destination) == 11: sms_log.info('SMS is for another site') try: site_ip = self.numbering.get_site_ip(destination) sms_log.info('Send SMS to site IP: %s' % site_ip) self.context = 'SMS_INTERNAL' self.send(source, destination, text, self.charset, site_ip) except NumberingException as e: raise SMSException('Receive SMS error: %s' % e) elif len(destination) != 3: # dest number is for an external number send sms to sms provider self.context = 'SMS_OUTBOUND' sms_log.info( 'SMS is for an external number send SMS to SMS provider' ) self.send( config['smsc'], source, 'Lo sentimos, destino ' + str(destination) + ' no disponible', 'utf-8') else: sms_log.info('SMS for %s was dropped' % destination) except NumberingException as e: raise SMSException('Receive SMS Error: %s' % e) def prepare_txt_for_kannel(self, text, charset): # Kannel wants a coding param in the POST # GSM 03.38=0 UTF-8=1, UCS2=2 # and we need a str. charset_to_kannel_coding = {'0': 'UTF-8', '2': 'UTF-16BE'} if type(text) == unicode: sms_log.debug('Have unicode') self.coding = self.determine_coding(text) self.charset = charset_to_kannel_coding[self.coding] str_text = text.encode(self.charset) return (str_text, text) try: sms_log.debug('Have string, trying %s', charset) unicode_text = unicode(text, charset) self.coding = self.determine_coding(unicode_text) self.charset = charset_to_kannel_coding[self.coding] str_text = unicode_text.encode(self.charset) return (str_text, unicode_text) except Exception as ex: sms_log.info('Encoding Error: %s', str(ex)) self.charset = self.determine_coding(text, charset) unicode_text = text.decode(self.charset) str_text = text.decode('UTF-8', 'replace').encode(self.charset) return (str_text, unicode_text) def send(self, source, destination, text, charset='utf-8', server=config['local_ip']): ''' Send an SMS either: 1) To the local system via: a) HTTP POST to kannel or b) SUMBIT_SM using libsmpp to local SMSC listener 2) http POST to a remote RAPI:receive() ''' sms_log.info('SMS Send: Text: <%s> Charset: %s' % (text, charset)) # We don't trust the caller to send us unicode, or to send a correct charset, if any. sms_log.info('Type of text: %s', type(text)) if 'use_kannel' in globals() and use_kannel == 'yes': str_text, unicode_text = self.prepare_txt_for_kannel(text, charset) else: str_text = '' if type(text) != unicode: # this could crash if we are fed bullshit. unicode_text = text.decode(charset) else: unicode_text = text if server == config['local_ip']: try: sms_log.info('Send SMS to Local: %s %s %s' % (source, destination, text)) self.local_smpp_submit_sm(source, destination, unicode_text, str_text) if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) return True except SMSException as ex: sms_log.error("Local submit failed: %s" % str(ex)) return False try: sms_log.info('Send SMS to %s: %s %s %s' % (server, source, destination, unicode_text)) if "+" not in self.internal_destination: destination = destination + '+1' else: s = self.internal_destination.split('+') destination = s[0] + '+' + str(int(s[1]) + 1) if int(s[1]) > 4: sms_log.error("!! SMS is Looping(%s)", s[1]) values = { 'source': source, 'destination': destination, 'charset': self.charset, 'coding': self.coding, 'text': str_text, 'btext': '', 'dr': '', 'dcs': '' } data = urllib.urlencode(values) t = Thread(target=self._t_urlopen, args=(server, data)) t.start() sms_log.info('Started Remote RAPI Thread') if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: # Never happen.... raise SMSException('Error sending SMS to site %s' % server) def local_smpp_submit_sm(self, source, destination, unicode_text, str_text=''): if 'use_kannel' in globals() and use_kannel == 'yes': try: enc_text = urllib.urlencode({'text': str_text}) kannel_post = "http://%s:%d/cgi-bin/sendsms?username=%s&password=%s&charset=%s&coding=%s&to=%s&from=%s&%s"\ % (self.server, self.port, self.username, self.password, self.charset, self.coding, destination, source, enc_text) sms_log.info('Kannel URL: %s' % (kannel_post)) res = urllib.urlopen(kannel_post).read() sms_log.info('Kannel Result: %s' % (res)) return except IOError: raise SMSException('Error connecting to Kannel to send SMS') global _sent def _smpp_rx_submit_resp(pdu): global _sent sms_log.info("Sent (%s)", pdu.message_id) if pdu.command == "submit_sm_resp": _sent = pdu.status try: source = network_name if source == '10000' else source if not source.isdigit(): ston = smpplib.consts.SMPP_TON_ALNUM snpi = smpplib.consts.SMPP_NPI_UNK else: ston = smpplib.consts.SMPP_TON_SBSCR snpi = smpplib.consts.SMPP_NPI_ISDN parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts( unicode_text) smpp_client = smpplib.client.Client("127.0.0.1", 2775, 90) smpplib.client.logger.setLevel('INFO') smpp_client.set_message_received_handler(lambda pdu: sms_log.info( "Rcvd while sending (%s)", pdu.command)) smpp_client.set_message_sent_handler(_smpp_rx_submit_resp) smpp_client.connect() smpp_client.bind_transmitter(system_id="ISMPP", password="******") _sent = -1 for part in parts: pdu = smpp_client.send_message( source_addr_ton=ston, source_addr_npi=snpi, source_addr=str(source), dest_addr_ton=smpplib.consts.SMPP_TON_SBSCR, dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN, destination_addr=str(destination), data_coding=encoding_flag, esm_class=msg_type_flag, short_message=part, registered_delivery=False, ) while _sent < 0: smpp_client.read_once() smpp_client.unbind() smpp_client.disconnect() del pdu del smpp_client except (IOError, smpplib.exceptions.ConnectionError) as ex: raise SMSException('Unable to Submit Message via SMPP %s' % str(ex)) except smpplib.exceptions.PDUError as ex: smpp_client.unbind() smpp_client.disconnect() raise SMSException('SMPP Error Submitting Message %s' % str(ex)) def check_decode0338(self, text): try: return text.decode('gsm03.38') except Exception as ex: sms_log.error(str(ex)) try: gsm_shift_codec = gsm0338.Codec( single_shift_decode_map=gsm0338. SINGLE_SHIFT_CHARACTER_SET_SPANISH) return gsm_shift_codec.decode(text)[0] except Exception as ex: sms_log.error(str(ex)) def determine_coding(self, unicode_str): if type(unicode_str) != unicode: raise SMSException('Input is not unicode') try: try: _test0338 = unicode_str.encode('gsm03.38') sms_log.debug('GSM03.38 OK "%s" -> "%s"' % (unicode_str, _test0338.decode('gsm03.38'))) return '0' except ValueError as ex: sms_log.debug( 'Encoding to GSM03.38 default alphabet not possible. %s' % sys.exc_info()[1]) _test0338s = gsm0338.Codec(single_shift_decode_map=gsm0338. SINGLE_SHIFT_CHARACTER_SET_SPANISH) _test = _test0338s.encode(unicode_str)[0] sms_log.debug('GSM03.38 Spanish Shift OK "%s" -> "%s"' % (unicode_str, _test0338s.decode(_test)[0])) return '2' except Exception as ex: template = "exception of type {0}. Arguments:\n{1!r}" print template.format(type(ex).__name__, ex.args) sms_log.debug('Using GSM03.38 Spanish Shift not possible. %s' % sys.exc_info()[1]) return '2' def roaming(self, subject): self.numbering = Numbering() self.subscriber = Subscriber() if subject == 'caller': # calling number is roaming # check if destination number is roaming as well if self.numbering.is_number_roaming(self.destination): # well destination number is roaming as well send SMS to current_bts where the subscriber is roaming try: current_bts = self.numbering.get_current_bts( self.destination) sms_log.info( 'Destination number is roaming send SMS to current_bts: %s' % current_bts) if current_bts == config['local_ip']: log.info( 'Current bts same as local site send call to local Kannel' ) self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text, self.charset) else: # send sms to destination site self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) except NumberingException as e: sms_log.error(e) else: # destination is not roaming check if destination if local site if self.numbering.is_number_local(self.destination) and len( self.destination) == 11: sms_log.info('Destination is a local number') if self.subscriber.is_authorized(self.destination, 0): sms_log.info('Send sms to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # destination cannot receive SMS inform source self.context = 'SMS_ROAMING_UNAUTH' # Why receive here? Why not send? self.receive(config['smsc'], source, config['sms_destination_unauthorized'], self.charset, self.coding) else: # number is not local check if number is internal if self.numbering.is_number_internal( self.destination) and len(self.destination) == 11: # number is internal send SMS to destination site current_bts = self.numbering.get_site_ip( self.destination) self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) else: # check if number is for outbound. # not implemented yet. just return sms_log.info('Invalid destination for SMS') return else: # the destination is roaming send call to current_bts try: current_bts = self.numbering.get_current_bts(self.destination) if current_bts == config['local_ip']: sms_log.info( 'Destination is roaming on our site send SMS to local kannel' ) self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text, self.charset) else: sms_log.info( 'Destination is roaming send sms to other site') self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) except NumberingException as e: sms_log.error(e) def save(self, source, destination, context): # insert SMS in the history try: cur = db_conn.cursor() cur.execute( 'INSERT INTO sms(source_addr,destination_addr,context) VALUES(%s,%s,%s)', (source, destination, context)) except psycopg2.DatabaseError as e: db_conn.rollback() raise SMSException('PG_HLR error saving SMS in the history: %s' % e) finally: db_conn.commit() cur.close() def send_immediate(self, num, text): appstring = 'OpenBSC' appport = 4242 vty = obscvty.VTYInteract(appstring, '127.0.0.1', appport) cmd = 'subscriber extension %s sms sender extension %s send %s' % ( num, config['smsc'], text) vty.command(cmd) def broadcast_to_all_subscribers(self, text, btype, location): sms_log.debug('Broadcast message to [%s], Location:%s' % (btype, location)) if location == "all": location = False sub = Subscriber() try: if btype == 'authorized': subscribers_list = sub.get_all_authorized(location) elif btype == 'unauthorized': subscribers_list = sub.get_all_unauthorized(location) elif btype == 'notpaid': subscribers_list = sub.get_all_notpaid(location) elif btype == 'extension': subscribers_list = sub.get_all_5digits() else: subscribers_list = [] except NoDataException as ex: return False for mysub in subscribers_list: self.send(config['smsc'], mysub[1], text) time.sleep(1) sms_log.debug('Broadcast message sent to [%s] %s' % (btype, mysub[1])) def send_broadcast(self, text, btype, location): sms_log.info('Send broadcast SMS to all subscribers. text: %s' % text) if type(btype) is list: t = {} for bt in btype: t[bt] = Thread(target=self.broadcast_to_all_subscribers, args=(text, bt, location)) t[bt].start() return sms_log.error('Bulk Message had no destinations') def _t_urlopen(self, url, data): try: res = urllib.urlopen('http://%s:8085/sms' % url, data) res.read() res.close() return res except IOError as ex: sms_log.error("FIMXE: SMS is lost (%s)", ex) return False
class SMS: def __init__(self): self.server = kannel_server self.port = kannel_port self.username = kannel_username self.password = kannel_password self.charset = 'UTF-8' self.coding = 2 self.context = 'SMS_LOCAL' self.source = '' self.destination = '' self.text = '' self.save_sms = 1 self.numbering = Numbering() def filter(self): drop_regexp = ['simchautosynchro.+','DSAX[0-9]+ND'] for regexp in drop_regexp: if re.search(regexp,self.text): sms_log.info('Dropping SMS on floor because text matched %s' % regexp) return True return False def receive(self, source, destination, text, charset, coding): self.charset = charset self.coding = coding self.source = source self.destination = destination self.text = text sms_log.info('Received SMS: %s %s %s %s %s' % (source, destination, text, charset, coding)) #sms_log.info(binascii.hexlify(text)) # SMS_LOCAL | SMS_INTERNAL | SMS_INBOUND | SMS_OUTBOUND | SMS_ROAMING # some seemingly autogenerated SMS we just want to drop on the floor: try: if self.filter(): return except: api_log.info('Caught an Error in sms:filter()') pass try: # auth checks # get auth info sub = Subscriber() # check if source or destination is roaming try: if self.numbering.is_number_roaming(source): # FIXME: ^^ Returns False for unregistered or unknown numbers. sms_log.info('Source number is roaming') self.roaming('caller') return except NumberingException as e: sms_log.info('Sender unauthorized send notification message (exception)') self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return try: if self.numbering.is_number_roaming(destination): sms_log.info('Destination number is roaming') self.roaming('called') return except NumberingException as e: sms_log.info('Destination unauthorized send notification message') self.context = 'SMS_UNAUTH' self.send(config['smsc'], source, config['sms_destination_unauthorized']) return try: source_authorized = sub.is_authorized(source, 0) except SubscriberException as e: source_authorized = False try: destination_authorized = sub.is_authorized(destination, 0) except SubscriberException as e: destination_authorized = False sms_log.info('Source_authorized: %s Destination_authorized: %s' % (str(source_authorized), str(destination_authorized))) if not source_authorized and not self.numbering.is_number_internal(source): sms_log.info('Sender unauthorized send notification message (EXT)') self.context = 'SMS_UNAUTH' self.coding = 2 self.send(config['smsc'], source, config['sms_source_unauthorized']) return if self.numbering.is_number_local(destination): sms_log.info('SMS_LOCAL check if subscriber is authorized') # get auth info sub = Subscriber() source_authorized = sub.is_authorized(source, 0) destination_authorized = sub.is_authorized(destination, 0) try: if source_authorized and destination_authorized: sms_log.info('Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_LOCAL' # Decision was not to send coding on here..... self.send(source, destination, text, charset) else: if not self.numbering.is_number_local(source) and destination_authorized: sms_log.info('SMS_INTERNAL Forward SMS back to BSC') self.context = 'SMS_INTERNAL' self.send(source, destination, text, charset) else: if destination_authorized and not self.numbering.is_number_local(source): sms_log.info('SMS_INBOUND Forward SMS back to BSC') # number is local send SMS back to SMSc self.context = 'SMS_INBOUND' self.send(source, destination, text, charset) else: self.charset = 'UTF-8' self.coding = 2 self.save_sms = 0 self.context = 'SMS_UNAUTH' if not source_authorized: sms_log.info('Sender unauthorized send notification message') self.send(config['smsc'], source, config['sms_source_unauthorized']) else: sms_log.info('Destination unauthorized inform sender with a notification message') self.send(config['smsc'], source, config['sms_destination_unauthorized']) except SubscriberException as e: raise SMSException('Receive SMS error: %s' % e) else: # dest number is not local, check if dest number is a shortcode if destination in extensions_list: sms_log.info('Destination number is a shortcode, execute shortcode handler') extension = importlib.import_module('extensions.ext_'+destination, 'extensions') try: sms_log.debug('Exec shortcode handler') extension.handler('', source, destination, text) except ExtensionException as e: raise SMSException('Receive SMS error: %s' % e) else: # check if sms is for another location if self.numbering.is_number_internal(destination) and len(destination) == 11: sms_log.info('SMS is for another site') try: site_ip = self.numbering.get_site_ip(destination) sms_log.info('Send SMS to site IP: %s' % site_ip) self.context = 'SMS_INTERNAL' self.send(source, destination, text, self.charset, site_ip) except NumberingException as e: raise SMSException('Receive SMS error: %s' % e) elif len(destination) != 3: # dest number is for an external number send sms to sms provider self.context = 'SMS_OUTBOUND' sms_log.info('SMS is for an external number send SMS to SMS provider') self.send(config['smsc'], source, 'Lo sentimos, destino '+str(destination)+ ' no disponible', 'utf-8') else: sms_log.info('SMS for %s was dropped' % destination) except NumberingException as e: raise SMSException('Receive SMS Error: %s' % e) def send(self, source, destination, text, charset='utf-8', server=config['local_ip']): sms_log.info('SMS Send: Text: <%s> Charset: %s' % (text, charset) ) try: # because we might be called without charset and sent something unknown. sms_log.info('Type of text: %s', (type(text)) ) if (charset == 'UTF-8' or charset == 'utf-8') and type(text) != unicode: utext=unicode(text,charset).encode('utf-8') elif charset == 'UTF-16BE': utext=text.encode('utf-16be') else: utext=text.encode('utf-8') sms_log.info('Type: %s', (type(utext)) ) # I think we ALWAYS need to send coding=2 to kannel. if type(text) == unicode: self.coding = 2 enc_text = urllib.urlencode({'text': utext }) except: sms_log.info('Encoding Error: %s Line:%s' % (sys.exc_info()[1], sys.exc_info()[2].tb_lineno)) if server == config['local_ip']: try: sms_log.info('Send SMS: %s %s %s %s' % (source, destination, text, enc_text)) kannel_post="http://%s:%d/cgi-bin/sendsms?username=%s&password=%s&charset=%s&coding=%s&to=%s&from=%s&%s"\ % (self.server, self.port, self.username, self.password, self.charset, self.coding, destination, source, enc_text) sms_log.info('Kannel URL: %s' % (kannel_post)) res = urllib.urlopen(kannel_post).read() if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: raise SMSException('Error connecting to Kannel to send SMS') else: try: sms_log.info('Send SMS to %s: %s %s %s' % (server, source, destination, text)) values = {'source': source, 'destination': destination, 'charset': self.charset, 'coding': self.coding, 'text': text, 'btext': '', 'dr': '', 'dcs': ''} data = urllib.urlencode(values) res = urllib.urlopen('http://%s:8085/sms' % server, data).read() if self.save_sms: sms_log.info('Save SMS in the history') self.save(source, destination, self.context) except IOError: raise SMSException('Error sending SMS to site %s' % server) def roaming(self, subject): self.numbering = Numbering() self.subscriber = Subscriber() if subject == 'caller': # calling number is roaming # check if destination number is roaming as well if self.numbering.is_number_roaming(self.destination): # well destination number is roaming as well send SMS to current_bts where the subscriber is roaming try: current_bts = self.numbering.get_current_bts(self.destination) sms_log.info('Destination number is roaming send SMS to current_bts: %s' % current_bts) if current_bts == config['local_ip']: log.info('Current bts same as local site send call to local Kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # send sms to destination site self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) except NumberingException as e: sms_log.error(e) else: # destination is not roaming check if destination if local site if self.numbering.is_number_local(self.destination) and len(self.destination) == 11: sms_log.info('Destination is a local number') if self.subscriber.is_authorized(self.destination, 0): sms_log.info('Send sms to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: # destination cannot receive SMS inform source self.context = 'SMS_ROAMING_UNAUTH' self.receive(config['smsc'], source, config['sms_destination_unauthorized'], self.charset, self.coding) else: # number is not local check if number is internal if self.numbering.is_number_internal(self.destination) and len(self.destination) == 11: # number is internal send SMS to destination site current_bts = self.numbering.get_site_ip(self.destination) self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) else: # check if number is for outbound. # not implemented yet. just return sms_log.info('Invalid destination for SMS') return else: # the destination is roaming send call to current_bts try: current_bts = self.numbering.get_current_bts(self.destination) if current_bts == config['local_ip']: sms_log.info('Destination is roaming on our site send SMS to local kannel') self.context = 'SMS_ROAMING_LOCAL' self.send(self.source, self.destination, self.text) else: sms_log.info('Destination is roaming send sms to other site') self.context = 'SMS_ROAMING_INTERNAL' self.send(self.source, self.destination, self.text, self.charset, current_bts) except NumberingException as e: sms_log.error(e) def save(self, source, destination, context): # insert SMS in the history try: cur = db_conn.cursor() cur.execute('INSERT INTO sms(source_addr,destination_addr,context) VALUES(%s,%s,%s)', (source, destination, context)) except psycopg2.DatabaseError as e: db_conn.rollback() raise SMSException('PG_HLR error saving SMS in the history: %s' % e) finally: cur.close() db_conn.commit() def send_immediate(self, num, text): appstring = 'OpenBSC' appport = 4242 vty = obscvty.VTYInteract(appstring, '127.0.0.1', appport) cmd = 'subscriber extension %s sms sender extension %s send %s' % (num, config['smsc'], text) vty.command(cmd) def broadcast_to_all_subscribers(self, text, btype): sub = Subscriber() if btype == 'all': subscribers_list = sub.get_all() elif btype == 'notpaid': subscribers_list = sub.get_all_notpaid() elif btype == 'unauthorized': subscribers_list = sub.get_all_unauthorized() elif btype == 'extension': subscribers_list = sub.get_all_5digits() for mysub in subscribers_list: self.send(config['smsc'], mysub[1], text) sms_log.debug('Broadcast message sent to %s' % mysub[1]) time.sleep(1) def send_broadcast(self, text, btype): sms_log.info('Send broadcast SMS to all subscribers. text: %s' % text) t = Thread(target=self.broadcast_to_all_subscribers, args=(text, btype, )) t.start()