From 3ea552e2dbde2d5ecfc8a48f0e525fe297787454 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 26 Feb 2022 01:43:28 +0000 Subject: [PATCH] OPtionally, enforce registered DMR IDs. Also, check peridically (STALE_TIME setting) for new files. New config option: ALLOW_UNREG_ID=[True|False] - in MASTER definition Squashed commit of the following: commit 082fd63b96c272415a01d5dadc8d38bf0842737d Author: Simon Date: Sat Feb 26 01:40:11 2022 +0000 Periodic download of alias files commit cb33388509978686ee6bfc19cddb863379872c7b Author: Simon Date: Thu Feb 24 00:17:38 2022 +0000 server check id commit 5428503e17ae32234a58c997037e70abe2a784c7 Author: Simon Date: Thu Feb 24 00:16:46 2022 +0000 server check id commit 65fcf2eddffa426caacdbf1a59f529c995045fb5 Author: Simon Date: Thu Feb 24 00:06:24 2022 +0000 server check id commit ea0ea8d6b13ba7e20492f11226de854cecbccc46 Author: Simon Date: Wed Feb 23 23:49:07 2022 +0000 reverse logic commit 9815210d5adb2ee92d1b9914753d4799988b3415 Author: Simon Date: Wed Feb 23 23:24:34 2022 +0000 reverse logic commit 0d9945d23fcce62378cace97cdc012c3fdd04276 Author: Simon Date: Wed Feb 23 23:23:26 2022 +0000 reverse logic commit 54cc752c4cac52e903422b9aae38e61f722c23f1 Author: Simon Date: Wed Feb 23 23:20:16 2022 +0000 reverse logic commit 182a0e99829ee7383ae72b52d8514aa826e31260 Author: Simon Date: Wed Feb 23 23:17:16 2022 +0000 typo commit e244c1b20b9c927efb1a95e30ff08d3120a83b7e Author: Simon Date: Wed Feb 23 23:16:28 2022 +0000 typo commit ba822143bd97261f1a65fc9d489bc570dea3749d Author: Simon Date: Wed Feb 23 23:12:54 2022 +0000 further updates to validate_id commit 8266db1820ee09b3bb3d4731e0cf019df40c638e Author: Simon Date: Tue Feb 22 00:48:31 2022 +0000 mike commit 2658e5842c24a4d72d17a46d1792811f2b01bbbc Author: Simon Date: Tue Feb 22 00:46:07 2022 +0000 int - string trickey commit 51479c0c572c75b9a8c9ea19e1aa6c5e3692d5a4 Author: Simon Date: Tue Feb 22 00:41:30 2022 +0000 validate_id commit 012ea165cebe414800815362d4f9da1b7399bcad Author: Simon Date: Tue Feb 22 00:40:00 2022 +0000 validate_id commit eff8b31bb5cadc08093f333afc7cb6a423e05675 Author: Simon Date: Tue Feb 22 00:36:46 2022 +0000 validate_id commit fb3158c4d0d44663c4e511163b60b5a04f392123 Author: Simon Date: Tue Feb 22 00:05:01 2022 +0000 make sure hblink.py can see sub ids commit 17111430fd561ebf2a431c615cd7521884a55ccd Author: Simon Date: Mon Feb 21 23:35:32 2022 +0000 add get_alias commit e98ea4c7f3d27c7539607a31948249ad1e6feb85 Author: Simon Date: Mon Feb 21 23:28:29 2022 +0000 fix line commit d027373fb944ea3fa60c5993bd91d114bb935721 Author: Simon Date: Mon Feb 21 23:27:01 2022 +0000 fix isinstance commit 106e2bcda6bc87f5a00ee9b99dbafdacb79d3d8d Author: Simon Date: Mon Feb 21 23:22:47 2022 +0000 don't allow login if ID and callsign don't match database --- bridge_master.py | 27 +++++++++++++++ config.py | 3 +- hblink.py | 88 ++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 103 insertions(+), 15 deletions(-) diff --git a/bridge_master.py b/bridge_master.py index 65c3ec1..d8fdfa7 100644 --- a/bridge_master.py +++ b/bridge_master.py @@ -82,6 +82,7 @@ from binascii import b2a_hex as ahex from AMI import AMI + ##from hmac import new as hmac_new, compare_digest ##from hashlib import sha256, hash @@ -635,6 +636,17 @@ def threadIdent(): def threadedMysql(): logger.debug('(MYSQL) Starting MySQL thread') reactor.callInThread(mysqlGetConfig) + +def threadAlias(): + logger.debug('(ALIAS) starting alias thread') + reactor.callInThread(aliasb) + +def setAlias(_peer_ids,_subscriber_ids, _talkgroup_ids): + peer_ids, subscriber_ids, talkgroup_ids = _peer_ids, _subscriber_ids, _talkgroup_ids + +def aliasb(): + _peer_ids, _subscriber_ids, _talkgroup_ids = mk_aliases(CONFIG) + reactor.callFromThread(setAlias,_peer_ids, _subscriber_ids, _talkgroup_ids) def ident(): for system in systems: @@ -2795,6 +2807,15 @@ if __name__ == '__main__': # Create the name-number mapping dictionaries peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) + #Add special IDs to DB + subscriber_ids[900999] = 'D-APRS' + subscriber_ids[4294967295] = 'SC' + + CONFIG['_SUB_IDS'] = subscriber_ids + CONFIG['_PEER_IDS'] = peer_ids + + + # Import the ruiles file as a module, and create BRIDGES from it spec = importlib.util.spec_from_file_location("module.name", cli_args.RULES_FILE) rules_module = importlib.util.module_from_spec(spec) @@ -2942,6 +2963,12 @@ if __name__ == '__main__': identa = ident_task.start(914) identa.addErrback(loopingErrHandle) + #Alias reloader + alias_time = CONFIG['ALIASES']['STALE_TIME'] * 86400 + aliasa_task = task.LoopingCall(threadAlias) + aliasa = aliasa_task.start(alias_time) + aliasa.addErrback(loopingErrHandle) + #Options parsing options_task = task.LoopingCall(options_config) options = options_task.start(26) diff --git a/config.py b/config.py index 4444466..0a18f20 100755 --- a/config.py +++ b/config.py @@ -325,7 +325,8 @@ def build_config(_config_file): 'TS2_STATIC': config.get(section,'TS2_STATIC'), 'DEFAULT_REFLECTOR': config.getint(section, 'DEFAULT_REFLECTOR'), 'GENERATOR': config.getint(section, 'GENERATOR'), - 'ANNOUNCEMENT_LANGUAGE': config.get(section, 'ANNOUNCEMENT_LANGUAGE') + 'ANNOUNCEMENT_LANGUAGE': config.get(section, 'ANNOUNCEMENT_LANGUAGE'), + 'ALLOW_UNREG_ID': config.getboolean(section,'ALLOW_UNREG_ID') }}) CONFIG['SYSTEMS'][section].update({'PEERS': {}}) diff --git a/hblink.py b/hblink.py index 391886b..2bd58dc 100755 --- a/hblink.py +++ b/hblink.py @@ -45,7 +45,7 @@ from twisted.internet import reactor, task import log import config from const import * -from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict +from dmr_utils3.utils import int_id, bytes_4, mk_id_dict # Imports for the reporting server import pickle @@ -57,6 +57,12 @@ logger = logging.getLogger(__name__) from functools import partial, partialmethod +import ssl + +from os.path import isfile, getmtime +from urllib.request import urlopen + + logging.TRACE = 5 logging.addLevelName(logging.TRACE, 'TRACE') logging.Logger.trace = partialmethod(logging.Logger.log, logging.TRACE) @@ -139,7 +145,6 @@ class OPENBRIDGE(DatagramProtocol): self._bcve = self._bcve_task.start(60) self._bcve.addErrback(self.loopingErrHandle) - def dereg(self): logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) @@ -797,6 +802,23 @@ class HBSYSTEM(DatagramProtocol): def peer_dereg(self): self.send_master(RPTCL + self._config['RADIO_ID']) logger.info('(%s) De-Registration sent to Master: %s:%s', self._system, self._config['MASTER_SOCKADDR'][0], self._config['MASTER_SOCKADDR'][1]) + + def validate_id(self,_peer_id): + if self._config['ALLOW_UNREG_ID']: + return True + + _int_peer_id = int_id(_peer_id) + _int_peer_id = str(_int_peer_id)[:7] + _int_peer_id = int(_int_peer_id) + _subscriber_ids = self._CONFIG['_SUB_IDS'] + _peer_ids = self._CONFIG['_PEER_IDS'] + if _int_peer_id in _subscriber_ids: + return _subscriber_ids[_int_peer_id] + elif _int_peer_id in _peer_ids: + return _peer_ids[_int_peer_id] + else: + return False + # Aliased in __init__ to datagramReceived if system is a master def master_datagramReceived(self, _data, _sockaddr): @@ -882,7 +904,7 @@ class HBSYSTEM(DatagramProtocol): # Check to see if we've reached the maximum number of allowed peers if len(self._peers) < self._config['MAX_PEERS'] or _peer_id in self._peers: # Check for valid Radio ID - if acl_check(_peer_id, self._CONFIG['GLOBAL']['REG_ACL']) and acl_check(_peer_id, self._config['REG_ACL']): + if acl_check(_peer_id, self._CONFIG['GLOBAL']['REG_ACL']) and acl_check(_peer_id, self._config['REG_ACL']) and self.validate_id(_peer_id): # Build the configuration data strcuture for the peer self._peers.update({_peer_id: { 'CONNECTION': 'RPTL-RECEIVED', @@ -919,7 +941,7 @@ class HBSYSTEM(DatagramProtocol): logger.info('(%s) Sent Challenge Response to %s for login: %s', self._system, int_id(_peer_id), self._peers[_peer_id]['SALT']) else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) - logger.warning('(%s) Invalid Login from %s Radio ID: %s Denied by Registation ACL', self._system, _sockaddr[0], int_id(_peer_id)) + logger.warning('(%s) Invalid Login from %s Radio ID: %s Denied by Registation ACL or not registered ID', self._system, _sockaddr[0], int_id(_peer_id)) else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Registration denied from Radio ID: %s Maximum number of peers exceeded', self._system, int_id(_peer_id)) @@ -991,9 +1013,14 @@ class HBSYSTEM(DatagramProtocol): _this_peer['URL'] = _data[98:222] _this_peer['SOFTWARE_ID'] = _data[222:262] _this_peer['PACKAGE_ID'] = _data[262:302] - - self.send_peer(_peer_id, b''.join([RPTACK, _peer_id])) - logger.info('(%s) Peer %s (%s) has sent repeater configuration, Package ID: %s, Software ID: %s, Desc: %s', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID'],self._peers[_peer_id]['PACKAGE_ID'].decode().rstrip(),self._peers[_peer_id]['SOFTWARE_ID'].decode().rstrip(),self._peers[_peer_id]['DESCRIPTION'].decode().rstrip()) + + if not self._config['ALLOW_UNREG_ID'] and _this_peer['CALLSIGN'].decode('utf8').rstrip() != self.validate_id(_peer_id): + del self._peers[_peer_id] + self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) + logger.info('(%s) Callsign does not match subscriber database: ID: %s, Sent Call: %s, DB call %s', self._system, int_id(_peer_id),_this_peer['CALLSIGN'].decode('utf8').rstrip(),self.validate_id(_peer_id)) + else: + self.send_peer(_peer_id, b''.join([RPTACK, _peer_id])) + logger.info('(%s) Peer %s (%s) has sent repeater configuration, Package ID: %s, Software ID: %s, Desc: %s', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID'],self._peers[_peer_id]['PACKAGE_ID'].decode().rstrip(),self._peers[_peer_id]['SOFTWARE_ID'].decode().rstrip(),self._peers[_peer_id]['DESCRIPTION'].decode().rstrip()) else: self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.info('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) @@ -1245,6 +1272,33 @@ class reportFactory(Factory): logger.debug('(REPORT) Send config') self.send_clients(b''.join([REPORT_OPCODES['CONFIG_SND'], serialized])) +#Use this try_download instead of that from dmr_utils3 +def try_download(_path, _file, _url, _stale,): + no_verify = ssl._create_unverified_context() + now = time() + file_exists = isfile(_path+_file) == True + if file_exists: + file_old = (getmtime(_path+_file) + _stale) < now + if not file_exists or (file_exists and file_old): + try: + with urlopen(_url, context=no_verify) as response: + data = response.read() + #outfile.write(data) + response.close() + result = 'ID ALIAS MAPPER: \'{}\' successfully downloaded'.format(_file) + except IOError: + result = 'ID ALIAS MAPPER: \'{}\' could not be downloaded due to an IOError'.format(_file) + try: + with open(_path+_file, 'wb') as outfile: + outfile.write(data) + outfile.close() + except IOError: + result = 'ID ALIAS mapper \'{}\' file could not be written due to an IOError'.format(_file) + else: + result = 'ID ALIAS MAPPER: \'{}\' is current, not downloaded'.format(_file) + + return result + # ID ALIAS CREATION # Download @@ -1252,29 +1306,35 @@ def mk_aliases(_config): if _config['ALIASES']['TRY_DOWNLOAD'] == True: # Try updating peer aliases file result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE'], _config['ALIASES']['PEER_URL'], _config['ALIASES']['STALE_TIME']) - logger.info('(GLOBAL) %s', result) + logger.info('(ALIAS) %s', result) # Try updating subscriber aliases file result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['SUBSCRIBER_FILE'], _config['ALIASES']['SUBSCRIBER_URL'], _config['ALIASES']['STALE_TIME']) - logger.info('(GLOBAL) %s', result) + logger.info('(ALIAS) %s', result) #Try updating tgid aliases file result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['TGID_FILE'], _config['ALIASES']['TGID_URL'], _config['ALIASES']['STALE_TIME']) - logger.info('(GLOBAL) %s', result) + logger.info('(ALIAS) %s', result) + # Make Dictionaries peer_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE']) if peer_ids: - logger.info('(GLOBAL) ID ALIAS MAPPER: peer_ids dictionary is available') + logger.info('(ALIAS) ID ALIAS MAPPER: peer_ids dictionary is available') subscriber_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['SUBSCRIBER_FILE']) + #Add special IDs to DB + subscriber_ids[900999] = 'D-APRS' + subscriber_ids[4294967295] = 'SC' + if subscriber_ids: - logger.info('(GLOBAL) ID ALIAS MAPPER: subscriber_ids dictionary is available') + logger.info('(ALIAS) ID ALIAS MAPPER: subscriber_ids dictionary is available') talkgroup_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['TGID_FILE']) if talkgroup_ids: - logger.info('(GLOBAL) ID ALIAS MAPPER: talkgroup_ids dictionary is available') - + logger.info('(ALIAS) ID ALIAS MAPPER: talkgroup_ids dictionary is available') + return peer_ids, subscriber_ids, talkgroup_ids + #************************************************ # MAIN PROGRAM LOOP STARTS HERE #************************************************