#!/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 ############################################################################### import ConfigParser import sys from socket import getaddrinfo, IPPROTO_UDP # 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' __license__ = 'GNU GPLv3' __maintainer__ = 'Cort Buffington, N0MJS' __email__ = 'n0mjs@me.com' def get_address(_config): ipv4 = '' ipv6 = '' socket_info = getaddrinfo(_config, None, 0, 0, IPPROTO_UDP) for item in socket_info: if item[0] == 2: ipv4 = item[4][0] elif item[0] == 30: ipv6 = item[4][0] if ipv4: return ipv4 if ipv6: return ipv6 return 'invalid address' 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['SYSTEMS'] = {} try: for section in config.sections(): if section == 'GLOBAL': CONFIG['GLOBAL'].update({ 'PATH': config.get(section, 'PATH') }) elif section == 'REPORTS': CONFIG['REPORTS'].update({ 'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'), 'REPORT_RCM': config.get(section, 'REPORT_RCM'), 'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'), 'REPORT_PORT': config.get(section, 'REPORT_PORT'), 'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(','), 'PRINT_PEERS_INC_MODE': config.getboolean(section, 'PRINT_PEERS_INC_MODE'), 'PRINT_PEERS_INC_FLAGS': config.getboolean(section, 'PRINT_PEERS_INC_FLAGS') }) if CONFIG['REPORTS']['REPORT_PORT']: CONFIG['REPORTS']['REPORT_PORT'] = int(CONFIG['REPORTS']['REPORT_PORT']) if CONFIG['REPORTS']['REPORT_RCM']: CONFIG['REPORTS']['REPORT_RCM'] = bool(CONFIG['REPORTS']['REPORT_RCM']) 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') }) 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'), 'LOCAL_FILE': config.get(section, 'LOCAL_FILE'), 'PEER_URL': config.get(section, 'PEER_URL'), 'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'), 'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400, }) elif config.getboolean(section, 'ENABLED'): CONFIG['SYSTEMS'].update({section: {'LOCAL': {}, 'MASTER': {}, 'PEERS': {}}}) CONFIG['SYSTEMS'][section]['LOCAL'].update({ # In case we want to keep config, but not actually connect to the network 'ENABLED': config.getboolean(section, 'ENABLED'), # These items are used to create the MODE byte 'PEER_OPER': config.getboolean(section, 'PEER_OPER'), 'IPSC_MODE': config.get(section, 'IPSC_MODE'), 'TS1_LINK': config.getboolean(section, 'TS1_LINK'), 'TS2_LINK': config.getboolean(section, 'TS2_LINK'), 'MODE': '', # These items are used to create the multi-byte FLAGS field 'AUTH_ENABLED': config.getboolean(section, 'AUTH_ENABLED'), 'CSBK_CALL': config.getboolean(section, 'CSBK_CALL'), 'RCM': config.getboolean(section, 'RCM'), 'CON_APP': config.getboolean(section, 'CON_APP'), 'XNL_CALL': config.getboolean(section, 'XNL_CALL'), 'XNL_MASTER': config.getboolean(section, 'XNL_MASTER'), 'DATA_CALL': config.getboolean(section, 'DATA_CALL'), 'VOICE_CALL': config.getboolean(section, 'VOICE_CALL'), 'MASTER_PEER': config.getboolean(section, 'MASTER_PEER'), 'FLAGS': '', # Things we need to know to connect and be a peer in this IPSC 'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'), 'IP': config.get(section, 'IP'), 'PORT': config.getint(section, 'PORT'), 'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'), 'MAX_MISSED': config.getint(section, 'MAX_MISSED'), 'AUTH_KEY': (config.get(section, 'AUTH_KEY').rjust(40,'0')).decode('hex'), 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), 'NUM_PEERS': 0, }) # Master means things we need to know about the master peer of the network CONFIG['SYSTEMS'][section]['MASTER'].update({ 'RADIO_ID': '\x00\x00\x00\x00', 'MODE': '\x00', 'MODE_DECODE': '', 'FLAGS': '\x00\x00\x00\x00', 'FLAGS_DECODE': '', 'STATUS': { 'CONNECTED': False, 'PEER_LIST': False, 'KEEP_ALIVES_SENT': 0, 'KEEP_ALIVES_MISSED': 0, 'KEEP_ALIVES_OUTSTANDING': 0, 'KEEP_ALIVES_RECEIVED': 0, 'KEEP_ALIVE_RX_TIME': 0 }, 'IP': '', 'PORT': '' }) if not CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']: CONFIG['SYSTEMS'][section]['MASTER'].update({ 'IP': get_address(config.get(section, 'MASTER_IP')), 'PORT': config.getint(section, 'MASTER_PORT') }) # Temporary locations for building MODE and FLAG data MODE_BYTE = 0 FLAG_1 = 0 FLAG_2 = 0 # Construct and store the MODE field if CONFIG['SYSTEMS'][section]['LOCAL']['PEER_OPER']: MODE_BYTE |= 1 << 6 if CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'ANALOG': MODE_BYTE |= 1 << 4 elif CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'DIGITAL': MODE_BYTE |= 1 << 5 if CONFIG['SYSTEMS'][section]['LOCAL']['TS1_LINK']: MODE_BYTE |= 1 << 3 else: MODE_BYTE |= 1 << 2 if CONFIG['SYSTEMS'][section]['LOCAL']['TS2_LINK']: MODE_BYTE |= 1 << 1 else: MODE_BYTE |= 1 << 0 CONFIG['SYSTEMS'][section]['LOCAL']['MODE'] = chr(MODE_BYTE) # Construct and store the FLAGS field if CONFIG['SYSTEMS'][section]['LOCAL']['CSBK_CALL']: FLAG_1 |= 1 << 7 if CONFIG['SYSTEMS'][section]['LOCAL']['RCM']: FLAG_1 |= 1 << 6 if CONFIG['SYSTEMS'][section]['LOCAL']['CON_APP']: FLAG_1 |= 1 << 5 if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL']: FLAG_2 |= 1 << 7 if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']: FLAG_2 |= 1 << 6 elif CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and not CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']: FLAG_2 |= 1 << 5 if CONFIG['SYSTEMS'][section]['LOCAL']['AUTH_ENABLED']: FLAG_2 |= 1 << 4 if CONFIG['SYSTEMS'][section]['LOCAL']['DATA_CALL']: FLAG_2 |= 1 << 3 if CONFIG['SYSTEMS'][section]['LOCAL']['VOICE_CALL']: FLAG_2 |= 1 << 2 if CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']: FLAG_2 |= 1 << 0 CONFIG['SYSTEMS'][section]['LOCAL']['FLAGS'] = '\x00\x00'+chr(FLAG_1)+chr(FLAG_2) except ConfigParser.Error, err: print(err) sys.exit('Could not parse configuration file, exiting...') 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 # 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 dmrlink.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__))+'/../dmrlink.cfg' pprint(build_config(cli_args.CONFIG_FILE))