From 9690cdf98c69cda1dd047de0d32018d87b0c07f4 Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Sun, 5 Sep 2021 19:22:10 -0700 Subject: [PATCH] working SMS generation, not correct though --- data_gateway-SAMPLE.cfg | 363 +++++++++++++++++++++++++++++++++++ data_gateway.py | 339 +++++++++++++++++++++++++++++++-- data_gateway_config.py | 409 ++++++++++++++++++++++++++++++++++++++++ hblink.py | 5 +- 4 files changed, 1101 insertions(+), 15 deletions(-) create mode 100644 data_gateway-SAMPLE.cfg create mode 100644 data_gateway_config.py diff --git a/data_gateway-SAMPLE.cfg b/data_gateway-SAMPLE.cfg new file mode 100644 index 0000000..0ec13a7 --- /dev/null +++ b/data_gateway-SAMPLE.cfg @@ -0,0 +1,363 @@ +# PROGRAM-WIDE PARAMETERS GO HERE +# PATH - working path for files, leave it alone unless you NEED to change it +# PING_TIME - the interval that peers will ping the master, and re-try registraion +# - how often the Master maintenance loop runs +# MAX_MISSED - how many pings are missed before we give up and re-register +# - number of times the master maintenance loop runs before de-registering a peer +# +# ACLs: +# +# Access Control Lists are a very powerful tool for administering your system. +# But they consume packet processing time. Disable them if you are not using them. +# But be aware that, as of now, the configuration stanzas still need the ACL +# sections configured even if you're not using them. +# +# REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!! +# +# The 'action' May be PERMIT|DENY +# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999) +# Format: +# ACL = 'action:id|start-end|,id|start-end,....' +# --for example-- +# SUB_ACL: DENY:1,1000-2000,4500-60000,17 +# +# ACL Types: +# REG_ACL: peer radio IDs for registration (only used on HBP master systems) +# SUB_ACL: subscriber IDs for end-users +# TGID_TS1_ACL: destination talkgroup IDs on Timeslot 1 +# TGID_TS2_ACL: destination talkgroup IDs on Timeslot 2 +# +# ACLs may be repeated for individual systems if needed for granularity +# Global ACLs will be processed BEFORE the system level ACLs +# Packets will be matched against all ACLs, GLOBAL first. If a packet 'passes' +# All elements, processing continues. Packets are discarded at the first +# negative match, or 'reject' from an ACL element. +# +# If you do not wish to use ACLs, set them to 'PERMIT:ALL' +# TGID_TS1_ACL in the global stanza is used for OPENBRIDGE systems, since all +# traffic is passed as TS 1 between OpenBridges +[GLOBAL] +PATH: ./ +PING_TIME: 5 +MAX_MISSED: 3 +USE_ACL: True +REG_ACL: PERMIT:ALL +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + + +# NOT YET WORKING: NETWORK REPORTING CONFIGURATION +# Enabling "REPORT" will configure a socket-based reporting +# system that will send the configuration and other items +# to a another process (local or remote) that may process +# the information for some useful purpose, like a web dashboard. +# +# REPORT - True to enable, False to disable +# REPORT_INTERVAL - Seconds between reports +# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK +# REPORT_CLIENTS - comma separated list of IPs you will allow clients +# to connect on. Entering a * will allow all. +# +# ****FOR NOW MUST BE TRUE - USE THE LOOPBACK IF YOU DON'T USE THIS!!!**** +[REPORTS] +REPORT: True +REPORT_INTERVAL: 60 +REPORT_PORT: 4329 +REPORT_CLIENTS: 127.0.0.1 + + +# SYSTEM LOGGER CONFIGURAITON +# This allows the logger to be configured without chaning the individual +# python logger stuff. LOG_FILE should be a complete path/filename for *your* +# system -- use /dev/null for non-file handlers. +# LOG_HANDLERS may be any of the following, please, no spaces in the +# list if you use several: +# null +# console +# console-timed +# file +# file-timed +# syslog +# LOG_LEVEL may be any of the standard syslog logging levels, though +# as of now, DEBUG, INFO, WARNING and CRITICAL are the only ones +# used. +# +[LOGGER] +LOG_FILE: /tmp/hblink.log +LOG_HANDLERS: console-timed +LOG_LEVEL: DEBUG +LOG_NAME: HBlink + +# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES +# Ok, not the TGID, there's no master list I know of to download +# This is intended as a facility for other applcations built on top of +# HBlink to use, and will NOT be used in HBlink directly. +# STALE_DAYS is the number of days since the last download before we +# download again. Don't be an ass and change this to less than a few days. +[ALIASES] +TRY_DOWNLOAD: True +PATH: ./ +PEER_FILE: peer_ids.json +SUBSCRIBER_FILE: subscriber_ids.json +TGID_FILE: talkgroup_ids.json +PEER_URL: https://www.radioid.net/static/rptrs.json +SUBSCRIBER_URL: https://www.radioid.net/static/users.json +STALE_DAYS: 7 + +# USER MANAGER +# This is where to configure the details for use with a user managment script +[WEB_SERVICE] +THIS_SERVER_NAME: DATA_GATEWAY +REMOTE_CONFIG_ENABLED: False +# URL of the user managment server +URL: http://localhost:8080/svr +# Integer appended to DMR ID during the generation of a passphrase +APPEND_INT: 1 +EXTRA_INT_1: 5 +EXTRA_INT_2: 8 +EXTRA_1: TeSt +EXTRA_2: DmR4 +# Secret used to authenticate with user managment server, before checking if user login is approved +SHARED_SECRET: test +# Shorten passphrases +SHORTEN_PASSPHRASE: True +SHORTEN_SAMPLE: 4 +SHORTEN_LENGTH: 4 +BURN_FILE: ./burn_ids.txt +BURN_INT: 5 + +[DATA_CONFIG] +DATA_DMR_ID: 9099 +CALL_TYPE: both +UNIT_SMS_TS: 2 + +USER_APRS_SSID: 5 +USER_APRS_COMMENT: HBNet APRS Gateway +APRS_SERVER: hbl.ink +APRS_PORT: 14580 +APRS_LOGIN_CALL: N0CALL +APRS_LOGIN_PASSCODE: 12345 +APRS_FILTER: r/47/-120/500 t/m + +# The following settings are only applicable if you are using the gps_data_beacon_igate script. +# They do not affect the operation gps_data itself. +# Time in minutes. +IGATE_BEACON_TIME = 45 +IGATE_BEACON_COMMENT = HBLink3 D-APRS Gateway +IGATE_BEACON_ICON = /I +IGATE_LATITUDE = 4730. N +IGATE_LONGITUDE = 11930. W + +# The following settings are for the static positions only, for hotspots or repeaters connected to MASTER stanzas. +# Implementation by IU7IGU +# REPORT_INTERVAL in Minute (ALLOW only > 3 Minutes) +# MESSAGE: This message will print on APRS description together RX and TX Frequency +APRS_STATIC_REPORT_INTERVAL: 15 +APRS_STATIC_MESSAGE:Connected to HBLink + +# The options below are required for operation of the dashboard and will cause errors in gps_data.py +# if configured wrong. Leave them as default unless you know what you are doing. +# If you do change, you must use absolute paths. +LOCATION_FILE: /tmp/gps_data_user_loc.txt +BULLETIN_BOARD_FILE: /tmp/gps_data_user_bb.txt +MAILBOX_FILE: /tmp/gps_data_user_mailbox.txt +EMERGENCY_SOS_FILE: /tmp/gps_data_user_sos.txt +SMS_FILE: /tmp/gps_data_user_sms.txt + +# User settings file, MUST configure using absolute path. +USER_SETTINGS_FILE: /tmp/user_settings.txt + +# API settings +# Authorized Apps file - data used for the dashboard API +USE_API: True +AUTHORIZED_APPS_FILE: /tmp/authorized_apps.txt +AUTHORIZED_TOKENS_FILE: /tmp/hblink_auth_tokens.txt +AUTHORIZED_USERS_FILE: /home/eric/Sync/hblink3_sms_dev/authorized_users.txt +ACCESS_SYSTEMS_FILE: /home/eric/Sync/hblink3_sms_dev/access_systems.txt +MY_SERVER_SHORTCUT: XYZ +SERVER_NAME: Test HBLink Network +USE_PUBLIC_APPS: True +PUBLIC_APPS_LIST: https://raw.githubusercontent.com/kf7eel/hblink_sms_external_apps/main/public_systems.txt +RULES_PATH: /home/eric/Sync/hblink3_sms_dev/rules.py + +# The following options are used for the dashboard. The dashboard is optional. +# Title of the Dashboard +DASHBOARD_TITLE: HBNet D-APRS Dashboard +# Used for API, RSS feed link, etc +DASHBOARD_URL: http://localhost:8092 + +# Logo used on dashboard page +LOGO: https://raw.githubusercontent.com/kf7eel/hblink3/gps/HBlink.png + +# Port to run server +DASH_PORT: 8092 + +# IP to run server on +DASH_HOST: 127.0.0.1 + +#Description of dashboard to show on main page +DESCRIPTION: Welcome to the dashboard. + +# Gateway contact info displayed on about page. +CONTACT_NAME: your name +CONTACT_CALL: N0CALL +CONTACT_EMAIL: email@example.org +CONTACT_WEBSITE: https://hbl.ink + +# Time format for display +TIME_FORMAT: %%H:%%M:%%S - %%m/%%d/%%y + +# Center dashboard map over these coordinates +MAP_CENTER_LAT: 47.00 +MAP_CENTER_LON: -120.00 +ZOOM_LEVEL: 7 + +# List and preview of some map themes at http://leaflet-extras.github.io/leaflet-providers/preview/ +# The following are options for map themes and just work, you should use one of these: “OpenStreetMap”, “Stamen” (Terrain, Toner, and Watercolor), +MAP_THEME: Stamen Toner + + +# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS +# OpenBridge is a protocol originall created by DMR+ for connection between an +# IPSC2 server and Brandmeister. It has been implemented here at the suggestion +# of the Brandmeister team as a way to legitimately connect HBlink to the +# Brandemiester network. +# It is recommended to name the system the ID of the Brandmeister server that +# it connects to, but is not necessary. TARGET_IP and TARGET_PORT are of the +# Brandmeister or IPSC2 server you are connecting to. PASSPHRASE is the password +# that must be agreed upon between you and the operator of the server you are +# connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that +# will be sent to the other server to identify this connection. +# other parameters follow the other system types. +# +# ACLs: +# OpenBridge does not 'register', so registration ACL is meaningless. +# Proper OpenBridge passes all traffic on TS1. +# HBlink can extend OPB to use both slots for unit calls only. +# Setting "BOTH_SLOTS" True ONLY affects unit traffic! +# Otherwise ACLs work as described in the global stanza +[OBP-1] +MODE: OPENBRIDGE +ENABLED: True +IP: +PORT: 62036 +NETWORK_ID: 1234 +PASSPHRASE: passw0rd +TARGET_IP: 127.0.0.1 +TARGET_PORT: 62037 +BOTH_SLOTS: True +USE_ACL: True +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL +USE_ENCRYPTION: False +ENCRYPTION_KEY: + +# MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS +# HomeBrew Protocol Master instances go here. +# IP may be left blank if there's one interface on your system. +# Port should be the port you want this master to listen on. It must be unique +# and unused by anything else. +# Repeat - if True, the master repeats traffic to peers, False, it does nothing. +# +# MAX_PEERS -- maximun number of peers that may be connect to this master +# at any given time. This is very handy if you're allowing hotspots to +# connect, or using a limited computer like a Raspberry Pi. +# +# ACLs: +# See comments in the GLOBAL stanza +[MASTER-1] +MODE: MASTER +ENABLED: True + +# Use the user manager? If False, MASTER instance will operate as normal. +USE_USER_MAN: False + +REPEAT: True +MAX_PEERS: 3 +EXPORT_AMBE: False +IP: +PORT: 62033 +PASSPHRASE: passw0rd +GROUP_HANGTIME: 5 +USE_ACL: True +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + +# PEER INSTANCES - DUPLICATE SECTION FOR MULTIPLE PEERS +# There are a LOT of errors in the HB Protocol specifications on this one! +# MOST of these items are just strings and will be properly dealt with by the program +# The TX & RX Frequencies are 9-digit numbers, and are the frequency in Hz. +# Latitude is an 8-digit unsigned floating point number. +# Longitude is a 9-digit signed floating point number. +# Height is in meters +# Setting Loose to True relaxes the validation on packets received from the master. +# This will allow HBlink to connect to a non-compliant system such as XLXD, DMR+ etc. +# +# ACLs: +# See comments in the GLOBAL stanza +[REPEATER-1] +MODE: PEER +ENABLED: False +LOOSE: False +EXPORT_AMBE: False +IP: +PORT: 54001 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 54000 +PASSPHRASE: homebrew +CALLSIGN: W1ABC +RADIO_ID: 312000 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +OPTIONS: +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + +[XLX-1] +MODE: XLXPEER +ENABLED: False +LOOSE: True +EXPORT_AMBE: False +IP: +PORT: 54002 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 62030 +PASSPHRASE: passw0rd +CALLSIGN: W1ABC +RADIO_ID: 312000 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +XLXMODULE: 4004 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL diff --git a/data_gateway.py b/data_gateway.py index cbc26df..ecdfffa 100644 --- a/data_gateway.py +++ b/data_gateway.py @@ -38,7 +38,7 @@ from twisted.internet import reactor, task # Things we import from the main hblink module from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases, config_reports -from dmr_utils3.utils import bytes_3, int_id, get_alias +from dmr_utils3.utils import bytes_3, int_id, get_alias, bytes_4 from dmr_utils3 import decode, bptc, const import data_gateway_config import log @@ -89,6 +89,8 @@ import threading import libscrc import random from bitarray.util import hex2ba as hex2bits +import traceback + ################################# @@ -567,22 +569,326 @@ def process_sms(_rf_src, sms, call_type): if call_type == 'vcsbk': send_sms(False, 9, 0000, 0000, 'group', 'APRS Messaging must be enabled. Send command "@APRS ON" or use dashboard to enable.') - try: - if sms in cmd_list: - logger.info('Executing command/script.') - os.popen(cmd_list[sms]).read() - packet_assembly = '' - except Exception as error_exception: - logger.info('Exception. Command possibly not in list, or other error.') - logger.info(error_exception) - logger.info(str(traceback.extract_tb(error_exception.__traceback__))) - packet_assembly = '' +## try: +## if sms in cmd_list: +## logger.info('Executing command/script.') +## os.popen(cmd_list[sms]).read() +## packet_assembly = '' +## except Exception as error_exception: +## logger.info('Exception. Command possibly not in list, or other error.') +## logger.info(error_exception) +## logger.info(str(traceback.extract_tb(error_exception.__traceback__))) +## packet_assembly = '' else: pass -# Module gobal varaibles +##### SMS encode ######### +############## SMS Que and functions ########### +def create_crc16(fragment_input): + crc16 = libscrc.gsm16(bytearray.fromhex(fragment_input)) + return fragment_input + re.sub('x', '0', str(hex(crc16 ^ 0xcccc))[-4:]) + +def create_crc32(fragment_input): + # Create and append CRC32 to data + # Create list of hex + word_list = [] + count_index = 0 + while count_index < len(fragment_input): + word_list.append((fragment_input[count_index:count_index + 2])) + count_index = count_index + 2 + # Create string of rearranged word_list to match ETSI 102 361-1 pg 141 + lst_index = 0 + crc_string = '' + for i in (word_list): + #print(lst_index) + if lst_index % 2 == 0: + crc_string = crc_string + word_list[lst_index + 1] + #print(crc_string) + if lst_index % 2 == 1: + crc_string = crc_string + word_list[lst_index - 1] + #print(crc_string) + lst_index = lst_index + 1 + # Create bytearray of word_list_string + # print(crc_string) + word_array = libscrc.posix(bytearray.fromhex(crc_string)) + # XOR to get almost final CRC + pre_crc = str(hex(word_array ^ 0xffffffff))[2:] + # Rearrange pre_crc for transmission + crc = '' + c = 8 + while c > 0: + crc = crc + pre_crc[c-2:c] + c = c - 2 + #crc = crc.zfill(8) + crc = crc.ljust(8, '0') + # Return original data and append CRC32 + print('Output: ' + fragment_input + crc) + return fragment_input + crc + +def create_crc16_csbk(fragment_input): + crc16_csbk = libscrc.gsm16(bytearray.fromhex(fragment_input)) + return fragment_input + re.sub('x', '0', str(hex(crc16_csbk ^ 0xa5a5))[-4:]) +def csbk_gen(to_id, from_id): + csbk_lst = ['BD00801a', 'BD008019', 'BD008018', 'BD008017', 'BD008016'] + + send_seq_list = '' + for block in csbk_lst: + block = block + to_id + from_id + block = create_crc16_csbk(block) + print(block) + send_seq_list = send_seq_list + block + print(send_seq_list) + return send_seq_list + +def mmdvm_encapsulate(dst_id, src_id, peer_id, _seq, _slot, _call_type, _dtype_vseq, _stream_id, _dmr_data): + signature = 'DMRD' + # needs to be in bytes + frame_type = 0x10 #bytes_2(int(10)) + #print((frame_type)) + dest_id = bytes_3(int(dst_id, 16)) + #print(ahex(dest_id)) + source_id = bytes_3(int(src_id, 16)) + via_id = bytes_4(int(peer_id, 16)) + #print(ahex(via_id)) + seq = int(_seq).to_bytes(1, 'big') + #print(ahex(seq)) + # Binary, 0 for 1, 1 for 2 + slot = bitarray(str(_slot)) + #print(slot) + # binary, 0 for group, 1 for unit, bin(1) + call_type = bitarray(str(_call_type)) + #print(call_type) + #0x00 for voice, 0x01 for voice sync, 0x10 for data + #frame_type = int(16).to_bytes(1, 'big') + frame_type = bitarray('10') + #print(frame_type) + # Observed to be always 7, int. Will be 6 for header + #dtype_vseq = hex(int(_dtype_vseq)).encode() + if _dtype_vseq == 6: + dtype_vseq = bitarray('0110') + if _dtype_vseq == 7: + dtype_vseq = bitarray('0111') + if _dtype_vseq == 3: + dtype_vseq = bitarray('0011') + # 9 digit integer in hex + stream_id = bytes_4(_stream_id) + #print(ahex(stream_id)) + + middle_guts = slot + call_type + frame_type + dtype_vseq + #print(middle_guts) + dmr_data = str(_dmr_data)[2:-1] #str(re.sub("b'|'", '', str(_dmr_data))) + complete_packet = signature.encode() + seq + dest_id + source_id + via_id + middle_guts.tobytes() + stream_id + bytes.fromhex((dmr_data)) + bitarray('0000000000101111').tobytes()#bytes.fromhex(dmr_data) + #print('Complete: ' + str(ahex(complete_packet))) + return complete_packet + + +# Break long string into block sequence +def block_sequence(input_string): + seq_blocks = len(input_string)/24 + n = 0 + block_seq = [] + while n < seq_blocks: + if n == 0: + block_seq.append(bytes.fromhex(input_string[:24].ljust(24,'0'))) + n = n + 1 + else: + block_seq.append(bytes.fromhex(input_string[n*24:n*24+24].ljust(24,'0'))) + n = n + 1 + return block_seq + +# Takes list of DMR packets, 12 bytes, then encodes them +def dmr_encode(packet_list, _slot): + send_seq = [] + for i in packet_list: + stitched_pkt = bptc.interleave_19696(bptc.encode_19696(i)) + l_slot = bitarray('0111011100') + r_slot = bitarray('1101110001') + #Mobile Station + #sync_data = bitarray('110101011101011111110111011111111101011101010111') + if _slot == 0: + # TS1 - F7FDD5DDFD55 + sync_data = bitarray('111101111111110111010101110111011111110101010101') + if _slot == 1: + #TS2 - D7557F5FF7F5 + sync_data = bitarray('110101110101010101111111010111111111011111110101') + + # Data sync? 110101011101011111110111011111111101011101010111 - D5D7F77FD757 + new_pkt = ahex(stitched_pkt[:98] + l_slot + sync_data + r_slot + stitched_pkt[98:]) + send_seq.append(new_pkt) + return send_seq + + +def create_sms_seq(dst_id, src_id, peer_id, _slot, _call_type, dmr_string): + rand_seq = random.randint(1, 999999) + block_seq = block_sequence(dmr_string) + dmr_list = dmr_encode(block_seq, _slot) + cap_in = 0 + mmdvm_send_seq = [] + for i in dmr_list: + if use_csbk == True: + if cap_in < 5: + the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 3, rand_seq, i) + #print(block_seq[cap_in]) + #print(3) + if cap_in == 5: + #print(block_seq[cap_in]) + #print(6) + the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 6, rand_seq, i) #(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4]))))) + if cap_in > 5: + #print(block_seq[cap_in]) + #print(7) + the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 7, rand_seq, i)#(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4]))))) + mmdvm_send_seq.append(ahex(the_mmdvm_pkt)) + cap_in = cap_in + 1 + if use_csbk == False: + if cap_in == 0: + the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 6, rand_seq, i) #(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4]))))) + else: + the_mmdvm_pkt = mmdvm_encapsulate(dst_id, src_id, peer_id, cap_in, _slot, _call_type, 7, rand_seq, i)#(bytes.fromhex(re.sub("b'|'", '', str(orig_cap[cap_in][20:-4]))))) + mmdvm_send_seq.append(ahex(the_mmdvm_pkt)) + cap_in = cap_in + 1 + print(ahex(the_mmdvm_pkt)) + systems['OBP-2'].send_system(the_mmdvm_pkt) + + with open('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/' + str(random.randint(1000, 9999)) + '.mmdvm_seq', "w") as packet_write_file: + packet_write_file.write(str(mmdvm_send_seq)) + + return mmdvm_send_seq + +# Built for max length msg, will improve later +def sms_headers(to_id, from_id): +## #ETSI 102 361-2 uncompressed ipv4 +## # UDP header, src and dest ports are 4007, 0fa7 +## udp_ports = '0fa70fa7' +## # Length, of what? +## udp_length = '00da' +## # Checksum +## udp_checksum = '4b37' +## +## # IPV4 +## #IPV4 version and header length, always 45 +## ipv4_v_l = '45' +## #Type of service, always 00 +## ipv4_svc = '00' +## #length, always 00ee +## ipv4_len = '00ee' +## #ID always 000d +## ipv4_id = '000d' +## #Flags and offset always0 +## ipv4_flag_off = '0000' +## #TTL and Protocol always 4011, no matter what +## ipv4_ttl_proto = '4011' + #ipv4 = '450000ee000d0000401100000c' + from_id + '0c' + to_id + ipv4 = '450000ee00000000401100000c' + from_id + '0c' + to_id + count_index = 0 + hdr_lst = [] + while count_index < len(ipv4): + hdr_lst.append((ipv4[count_index:count_index + 4])) + count_index = count_index + 4 + sum = 0 + for i in hdr_lst: + sum = sum + int(i, 16) + flipped = '' + for i in str(bin(sum))[2:]: + if i == '1': + flipped = flipped + '0' + if i == '0': + flipped = flipped + '1' + ipv4_chk_sum = str(hex(int(flipped, 2)))[2:] + # UDP checksum is optional per ETSI, zero for now as Anytone is not affected. + header = ipv4[:20] + ipv4_chk_sum + ipv4[24:] + '0fa70fa700da000000d0a00081040d000a' + return header + +def format_sms(msg, to_id, from_id): + msg_bytes = str.encode(msg) + encoded = "".join([str('00' + x) for x in re.findall('..',bytes.hex(msg_bytes))] ) + final = encoded + while len(final) < 400: + final = final + '002e' + final = final + '0000000000000000000000' + headers = sms_headers(to_id, from_id) + return headers + final + +def gen_header(to_id, from_id, call_type): + if call_type == 1: + seq_header = '024A' + to_id + from_id + '9550' + if call_type == 0: + seq_header = '824A' + to_id + from_id + '9550' + return seq_header + +def send_sms(csbk, to_id, from_id, peer_id, call_type, msg): + global use_csbk + use_csbk = csbk + to_id = str(hex(to_id))[2:].zfill(6) + from_id = str(hex(from_id))[2:].zfill(6) + peer_id = str(hex(peer_id))[2:].zfill(8) + if call_type == 'unit': + call_type = 1 + # Try to find slot from UNIT_MAP + try: + #Slot 2 + if UNIT_MAP[bytes.fromhex(to_id)][2] == 2: + slot = 1 + # Slot 1 + if UNIT_MAP[bytes.fromhex(to_id)][2] == 1: + slot = 0 + except Exception as e: + logger.info(e) + # Change to config value later + slot = 1 + if call_type == 'group': + call_type = 0 + # Send all Group data to TS 2, need to fix later. + slot = 1 + if csbk == 'yes': + use_csbk = True + create_sms_seq(to_id, from_id, peer_id, int(slot), new_call_type, csbk_gen(to_id, from_id) + create_crc16(gen_header(to_id, from_id, new_call_type)) + create_crc32(format_sms(msg, to_id, from_id))) + else: + create_sms_seq(to_id, from_id, peer_id, int(slot), call_type, create_crc16(gen_header(to_id, from_id, call_type)) + create_crc32(format_sms(str(msg), to_id, from_id))) + +def data_que_check(): + l=task.LoopingCall(data_que_send) + l.start(1) +def data_que_send(): + #logger.info('Check SMS que') + try: + #logger.info(UNIT_MAP) + for packet_file in os.listdir('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/'): + logger.info('Sending SMS') + logger.info(os.listdir('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/')) + snd_seq = ast.literal_eval(os.popen('cat /tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/' + packet_file).read()) + for data in snd_seq: + # Get dest id + dst_id = bytes.fromhex(str(data[10:16])[2:-1]) + call_type = hex2bits(data)[121:122] + # Handle UNIT calls + if call_type[0] == True: + # If destination ID in map, route call only there + if dst_id in UNIT_MAP: + data_target = UNIT_MAP[dst_id][0] + reactor.callFromThread(systems[data_target].send_system,bytes.fromhex(re.sub("b'|'", '', str(data)))) + logger.info('Sending data to ' + str(data[10:16])[2:-1] + ' on system ' + data_target) + # Flood all systems + elif dst_id not in UNIT_MAP: + for i in UNIT: + reactor.callFromThread(systems[i].send_system,bytes.fromhex(re.sub("b'|'", '', str(data)))) + logger.info('Sending data to ' + str(data[10:16])[2:-1] + ' on system ' + i) + # Handle group calls + elif call_type[0] == False: + for i in BRIDGES.items(): + for d in i[1]: + if dst_id == d['TGID']: + data_target = d['SYSTEM'] + reactor.callFromThread(systems[data_target].send_system,bytes.fromhex(re.sub("b'|'", '', str(data)))) + logger.info('Sending data to ' + str(data[10:16])[2:-1] + ' on system ' + data_target) + + os.system('rm /tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/' + packet_file) + + #routerHBP.send_peer('MASTER-2', bytes.fromhex(re.sub("b'|'", '', str(data)))) + ## os.system('rm /tmp/.hblink_data_que/' + packet_file) + except Exception as e: + logger.info(e) -##class DATA(): ##### DMR data function #### def data_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): # Capture data headers @@ -951,7 +1257,12 @@ if __name__ == '__main__': with open(sms_file, 'w') as user_sms_file: user_sms_file.write("[]") user_sms_file.close() - + try: + Path('/tmp/.hblink_data_que_' + str(CONFIG['DATA_CONFIG']['APRS_LOGIN_CALL']).upper() + '/').mkdir(parents=True, exist_ok=True) + logger.info('Created que directory') + except: + logger.info('Unable to create data que directory') + pass # Start the system logger if cli_args.LOG_LEVEL: diff --git a/data_gateway_config.py b/data_gateway_config.py new file mode 100644 index 0000000..9a5f6e4 --- /dev/null +++ b/data_gateway_config.py @@ -0,0 +1,409 @@ +#!/usr/bin/env python +# +############################################################################### +# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +############################################################################### + +''' +This module generates the configuration data structure for hblink.py and +assoicated programs that use it. It has been seaparated into a different +module so as to keep hblink.py easeier to navigate. This file only needs +updated if the items in the main configuraiton file (usually hblink.cfg) +change. +''' + +import configparser +import sys +import const + +from socket import gethostbyname + +# Does anybody read this stuff? There's a PEP somewhere that says I should do this. +__author__ = 'Cortney T. Buffington, N0MJS' +__copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' +__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' +__license__ = 'GNU GPLv3' +__maintainer__ = 'Cort Buffington, N0MJS' +__email__ = 'n0mjs@me.com' + +# Processing of ALS goes here. It's separated from the acl_build function because this +# code is hblink config-file format specific, and acl_build is abstracted +def process_acls(_config): + # Global registration ACL + _config['GLOBAL']['REG_ACL'] = acl_build(_config['GLOBAL']['REG_ACL'], const.PEER_MAX) + + # Global subscriber and TGID ACLs + for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: + _config['GLOBAL'][acl] = acl_build(_config['GLOBAL'][acl], const.ID_MAX) + + # System level ACLs + for system in _config['SYSTEMS']: + # Registration ACLs (which make no sense for peer systems) + if _config['SYSTEMS'][system]['MODE'] == 'MASTER': + _config['SYSTEMS'][system]['REG_ACL'] = acl_build(_config['SYSTEMS'][system]['REG_ACL'], const.PEER_MAX) + + # Subscriber and TGID ACLs (valid for all system types) + for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: + _config['SYSTEMS'][system][acl] = acl_build(_config['SYSTEMS'][system][acl], const.ID_MAX) + +# Create an access control list that is programatically useable from human readable: +# ORIGINAL: 'DENY:1-5,3120101,3120124' +# PROCESSED: (False, set([(1, 5), (3120124, 3120124), (3120101, 3120101)])) +def acl_build(_acl, _max): + if not _acl: + return(True, set((const.ID_MIN, _max))) + + acl = [] #set() + sections = _acl.split(':') + + if sections[0] == 'PERMIT': + action = True + else: + action = False + + for entry in sections[1].split(','): + if entry == 'ALL': + acl.append((const.ID_MIN, _max)) + break + + elif '-' in entry: + start,end = entry.split('-') + start,end = int(start), int(end) + if (const.ID_MIN <= start <= _max) or (const.ID_MIN <= end <= _max): + acl.append((start, end)) + else: + sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE ({} - {})IN RANGE-BASED ENTRY: {}'.format(const.ID_MIN, _max, entry)) + else: + id = int(entry) + if (const.ID_MIN <= id <= _max): + acl.append((id, id)) + else: + sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE ({} - {}) IN SINGLE ID ENTRY: {}'.format(const.ID_MIN, _max, entry)) + + return (action, acl) + +def build_config(_config_file): + config = configparser.ConfigParser() + + if not config.read(_config_file): + sys.exit('Configuration file \''+_config_file+'\' is not a valid configuration file! Exiting...') + + CONFIG = {} + CONFIG['GLOBAL'] = {} + CONFIG['REPORTS'] = {} + CONFIG['LOGGER'] = {} + CONFIG['ALIASES'] = {} + CONFIG['WEB_SERVICE'] = {} + CONFIG['DATA_CONFIG'] = {} + CONFIG['SYSTEMS'] = {} + + try: + for section in config.sections(): + if section == 'GLOBAL': + CONFIG['GLOBAL'].update({ + 'PATH': config.get(section, 'PATH'), + 'PING_TIME': config.getint(section, 'PING_TIME'), + 'MAX_MISSED': config.getint(section, 'MAX_MISSED'), + 'USE_ACL': config.get(section, 'USE_ACL'), + 'REG_ACL': config.get(section, 'REG_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') + }) + + elif section == 'REPORTS': + CONFIG['REPORTS'].update({ + 'REPORT': config.getboolean(section, 'REPORT'), + 'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'), + 'REPORT_PORT': config.getint(section, 'REPORT_PORT'), + 'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(',') + }) + + elif section == 'LOGGER': + CONFIG['LOGGER'].update({ + 'LOG_FILE': config.get(section, 'LOG_FILE'), + 'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'), + 'LOG_LEVEL': config.get(section, 'LOG_LEVEL'), + 'LOG_NAME': config.get(section, 'LOG_NAME') + }) + if not CONFIG['LOGGER']['LOG_FILE']: + CONFIG['LOGGER']['LOG_FILE'] = '/dev/null' + + elif section == 'ALIASES': + CONFIG['ALIASES'].update({ + 'TRY_DOWNLOAD': config.getboolean(section, 'TRY_DOWNLOAD'), + 'PATH': config.get(section, 'PATH'), + 'PEER_FILE': config.get(section, 'PEER_FILE'), + 'SUBSCRIBER_FILE': config.get(section, 'SUBSCRIBER_FILE'), + 'TGID_FILE': config.get(section, 'TGID_FILE'), + 'PEER_URL': config.get(section, 'PEER_URL'), + 'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'), + 'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400, + }) + + elif section == 'WEB_SERVICE': + CONFIG['WEB_SERVICE'].update({ + 'THIS_SERVER_NAME': config.get(section, 'THIS_SERVER_NAME'), + 'URL': config.get(section, 'URL'), + 'REMOTE_CONFIG_ENABLED': config.getboolean(section, 'REMOTE_CONFIG_ENABLED'), + 'APPEND_INT': config.getint(section, 'APPEND_INT'), + 'EXTRA_INT_1': config.getint(section, 'EXTRA_INT_1'), + 'EXTRA_INT_2': config.getint(section, 'EXTRA_INT_2'), + 'EXTRA_1': config.get(section, 'EXTRA_1'), + 'EXTRA_2': config.get(section, 'EXTRA_2'), + 'SHARED_SECRET': config.get(section, 'SHARED_SECRET'), + 'SHORTEN_PASSPHRASE': config.getboolean(section, 'SHORTEN_PASSPHRASE'), + 'SHORTEN_SAMPLE': config.get(section, 'SHORTEN_SAMPLE'), + 'SHORTEN_LENGTH': config.get(section, 'SHORTEN_LENGTH'), + 'BURN_FILE': config.get(section, 'BURN_FILE'), + 'BURN_INT': config.getint(section, 'BURN_INT'), + + + }) + + elif section == 'DATA_CONFIG': + CONFIG['DATA_CONFIG'].update({ + 'DATA_DMR_ID': config.get(section, 'DATA_DMR_ID'), + 'USER_APRS_SSID': config.get(section, 'USER_APRS_SSID'), + 'CALL_TYPE': config.get(section, 'CALL_TYPE'), +## 'UNIT_SMS_TS': config.get(section, 'UNIT_SMS_TS'), + 'USER_APRS_COMMENT': config.get(section, 'USER_APRS_COMMENT'), + 'APRS_LOGIN_CALL': config.get(section, 'APRS_LOGIN_CALL'), + 'APRS_LOGIN_PASSCODE': config.get(section, 'APRS_LOGIN_PASSCODE'), + 'APRS_SERVER': config.get(section, 'APRS_SERVER'), + 'APRS_PORT': config.get(section, 'APRS_PORT'), + 'APRS_FILTER': config.get(section, 'APRS_FILTER'), + 'IGATE_BEACON_TIME': config.get(section, 'IGATE_BEACON_TIME'), + 'IGATE_BEACON_ICON': config.get(section, 'IGATE_BEACON_ICON'), + 'IGATE_BEACON_COMMENT': config.get(section, 'IGATE_BEACON_COMMENT'), + 'IGATE_LATITUDE': config.get(section, 'IGATE_LATITUDE'), + 'IGATE_LONGITUDE': config.get(section, 'IGATE_LONGITUDE'), + 'APRS_STATIC_REPORT_INTERVAL': config.get(section, 'APRS_STATIC_REPORT_INTERVAL'), + 'APRS_STATIC_MESSAGE': config.get(section, 'APRS_STATIC_MESSAGE'), +## 'EMAIL_SENDER': config.get(section, 'EMAIL_SENDER'), +## 'EMAIL_PASSWORD': config.get(section, 'EMAIL_PASSWORD'), +## 'SMTP_SERVER': config.get(section, 'SMTP_SERVER'), +## 'SMTP_PORT': config.get(section, 'SMTP_PORT'), + 'LOCATION_FILE': config.get(section, 'LOCATION_FILE'), + 'BULLETIN_BOARD_FILE': config.get(section, 'BULLETIN_BOARD_FILE'), + 'MAILBOX_FILE': config.get(section, 'MAILBOX_FILE'), + 'SMS_FILE': config.get(section, 'SMS_FILE'), + 'EMERGENCY_SOS_FILE': config.get(section, 'EMERGENCY_SOS_FILE'), + 'USER_SETTINGS_FILE': config.get(section, 'USER_SETTINGS_FILE'), +## 'USE_API': config.getboolean(section, 'USE_API'), +## 'AUTHORIZED_TOKENS_FILE': config.get(section, 'AUTHORIZED_TOKENS_FILE'), +## 'USE_PUBLIC_APPS': config.getboolean(section, 'USE_PUBLIC_APPS'), +## 'PUBLIC_APPS_LIST': config.get(section, 'PUBLIC_APPS_LIST'), +## 'MY_SERVER_SHORTCUT': config.get(section, 'MY_SERVER_SHORTCUT'), +## 'DASHBOARD_URL': config.get(section, 'DASHBOARD_URL'), +## 'SERVER_NAME': config.get(section, 'SERVER_NAME'), +## 'RULES_PATH': config.get(section, 'RULES_PATH'), + + + }) + + + elif config.getboolean(section, 'ENABLED'): + if config.get(section, 'MODE') == 'PEER': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'LOOSE': config.getboolean(section, 'LOOSE'), + 'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')), + 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), + 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), + 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), + 'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'), + 'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'), + 'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'), + 'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'), + 'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'), + 'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'), + 'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'), + 'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'), + 'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'), + 'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'), + 'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'), + 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), + 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), + 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), + 'OPTIONS': b''.join([b'Type=HBlink;', bytes(config.get(section, 'OPTIONS'), 'utf-8')]), + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') + }}) + CONFIG['SYSTEMS'][section].update({'STATS': { + 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES + 'CONNECTED': None, + 'PINGS_SENT': 0, + 'PINGS_ACKD': 0, + 'NUM_OUTSTANDING': 0, + 'PING_OUTSTANDING': False, + 'LAST_PING_TX_TIME': 0, + 'LAST_PING_ACK_TIME': 0, + }}) + + if config.get(section, 'MODE') == 'XLXPEER': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'LOOSE': config.getboolean(section, 'LOOSE'), + 'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')), + 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), + 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), + 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), + 'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'), + 'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'), + 'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'), + 'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'), + 'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'), + 'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'), + 'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'), + 'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'), + 'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'), + 'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'), + 'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'), + 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), + 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), + 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), + 'XLXMODULE': config.getint(section, 'XLXMODULE'), + 'OPTIONS': '', + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') + }}) + CONFIG['SYSTEMS'][section].update({'XLXSTATS': { + 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES + 'CONNECTED': None, + 'PINGS_SENT': 0, + 'PINGS_ACKD': 0, + 'NUM_OUTSTANDING': 0, + 'PING_OUTSTANDING': False, + 'LAST_PING_TX_TIME': 0, + 'LAST_PING_ACK_TIME': 0, + }}) + + elif config.get(section, 'MODE') == 'MASTER': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'USE_USER_MAN': config.getboolean(section, 'USE_USER_MAN'), + 'REPEAT': config.getboolean(section, 'REPEAT'), + 'MAX_PEERS': config.getint(section, 'MAX_PEERS'), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), + 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'REG_ACL': config.get(section, 'REG_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') + }}) + CONFIG['SYSTEMS'][section].update({'PEERS': {}}) + + elif config.get(section, 'MODE') == 'OPENBRIDGE': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'NETWORK_ID': config.getint(section, 'NETWORK_ID').to_bytes(4, 'big'), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], 'utf-8'), + 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), + 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), + 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), + 'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'), + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_ACL'), + 'TG2_ACL': 'PERMIT:ALL', + 'USE_ENCRYPTION': config.getboolean(section, 'USE_ENCRYPTION'), + 'ENCRYPTION_KEY': bytes(config.get(section, 'ENCRYPTION_KEY'), 'utf-8'), + }}) + elif config.get(section, 'MODE') == 'PROXY': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'EXTERNAL_PROXY_SCRIPT': config.getboolean(section, 'EXTERNAL_PROXY_SCRIPT'), + 'STATIC_APRS_POSITION_ENABLED': config.getboolean(section, 'STATIC_APRS_POSITION_ENABLED'), + 'REPEAT': config.getboolean(section, 'REPEAT'), + 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), + 'EXTERNAL_PORT': config.getint(section, 'EXTERNAL_PORT'), + 'INTERNAL_PORT_START': config.getint(section, 'INTERNAL_PORT_START'), + 'INTERNAL_PORT_STOP': config.getint(section, 'INTERNAL_PORT_STOP'), + 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'REG_ACL': config.get(section, 'REG_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TG1_ACL'), + 'TG2_ACL': config.get(section, 'TG2_ACL'), + }}) + CONFIG['SYSTEMS'][section].update({'PEERS': {}}) + + except configparser.Error as err: + sys.exit('Error processing configuration file -- {}'.format(err)) + + process_acls(CONFIG) + + return CONFIG + +# Used to run this file direclty and print the config, +# which might be useful for debugging +if __name__ == '__main__': + import sys + import os + import argparse + from pprint import pprint + from dmr_utils3.utils import int_id + + # Change the current directory to the location of the application + os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) + + # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') + cli_args = parser.parse_args() + + + # Ensure we have a path for the config file, if one wasn't specified, then use the execution directory + if not cli_args.CONFIG_FILE: + cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' + + CONFIG = build_config(cli_args.CONFIG_FILE) + pprint(CONFIG) + + def acl_check(_id, _acl): + id = int_id(_id) + for entry in _acl[1]: + if entry[0] <= id <= entry[1]: + return _acl[0] + return not _acl[0] + + print(acl_check(b'\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL'])) diff --git a/hblink.py b/hblink.py index fe23ea8..bad3c85 100755 --- a/hblink.py +++ b/hblink.py @@ -196,9 +196,12 @@ class OPENBRIDGE(DatagramProtocol): _data = _packet[:53] _hash = _packet[53:] _ckhs = hmac_new(self._config['PASSPHRASE'],_data,sha1).digest() + print(ahex(_ckhs)) + print(ahex(_hash)) + + print(compare_digest(_hash, _ckhs)) if compare_digest(_hash, _ckhs) and _sockaddr == self._config['TARGET_SOCK']: - print('good data') _peer_id = _data[11:15] _seq = _data[4] _rf_src = _data[5:8]