From 7825de8d31412a05da57396c2b5e6896cc12c8a8 Mon Sep 17 00:00:00 2001 From: Antonio Matraia <63372602+iu5jae@users.noreply.github.com> Date: Fri, 19 Mar 2021 21:31:20 +0100 Subject: [PATCH] Add files via upload --- YSFReflector | 558 +++++++++++++++++++++++++++++++++++++++++++++++ YSFReflector.ini | 23 ++ deny.db | 11 + 3 files changed, 592 insertions(+) create mode 100644 YSFReflector create mode 100644 YSFReflector.ini create mode 100644 deny.db diff --git a/YSFReflector b/YSFReflector new file mode 100644 index 0000000..df0402c --- /dev/null +++ b/YSFReflector @@ -0,0 +1,558 @@ +#!/usr/bin/python3 + +# pYSFReflector +# +# Created by Antonio Matraia (IU5JAE) on 20/02/2021. +# Copyright 2021 Antonio Matraia (IU5JAE). All rights reserved. + +# 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, see . + + +import socket +import threading +import queue +import sys +import os +import time +import re +import configparser +import datetime +import signal +from datetime import datetime +import bisect +import struct + + +def ip2long(ip): + packed = socket.inet_aton(ip) + lng = struct.unpack("!L", packed)[0] + return lng + + +def long2ip(lng): + packed = struct.pack("!L", lng) + ip = socket.inet_ntoa(packed) + return ip + + +## gestione liste LH ecc ## +def inserisci_lista(lista, elemento, n_max): + if (len(lista) < n_max): + lista.append(elemento) + else: + for i in range(n_max - 1): + lista[i] = lista[i+1] + lista[n_max-1] = elemento + +def stampa_lista(lista): + for i in range(len(lista)): + print(lista[len(lista)-i-1]) + +################################# + +def inlist(a, x): + i = bisect.bisect_left(a, x) + if i != len(a) and a[i] == x: + return True + else: + return False + +def check_string(l): + s = '' + for c in l: + if ((not c.isprintable()) and (ord(c) != 10)): + s += '<' +str(ord(c)) + '>' + if c.isprintable(): + s += c + return s + + +def RecvData(sock,recvPackets): + while True: + data,addr = sock.recvfrom(1024) # bloccante se non ricevo niente + recvPackets.put((data,addr)) + + +def CalcID(ref): + c = ref.strip().ljust(16) + u = 0 + + for a in c: + u = (u + ord(a)) & 0xFFFFFFFF + u = (u + (u << 10) & 0xFFFFFFFF) & 0xFFFFFFFF + u = (u ^ (u >> 6)) & 0xFFFFFFFF + + u = (u + (u << 3) & 0xFFFFFFFF) & 0xFFFFFFFF + u = (u ^ (u >> 11))& 0xFFFFFFFF + u = (u + (u << 15)& 0xFFFFFFFF)& 0xFFFFFFFF + + u = u % 100000 + return u + + +def getidgw(cl, adr): + i=0 + for c in cl: + if ((c[0] == adr[0]) and (c[1] == adr[1])): + i = [c[4], c[2]] + break + return i + + +def ElencoNodi(cl): + while True: + time.sleep(120) + cl_lo = [] + if (len(cl) == 0): + printlog('No repeaters/gateways linked') + else: + printlog('Currently linked repeaters/gateways:') + for c in cl: + printlog(' ' + c[2].ljust(10) + ': ' + str(c[0]) + ':' + str(c[1]) + ' ' + str(c[3]) + '/60') + if (c[5] == 1): + cl_lo.append(c) + if (len(cl_lo) == 0): + printlog('No repeaters/gateways muted') + else: + printlog('Currently muted repeaters/gateways:') + for c in cl_lo: + printlog(' ' + c[2].ljust(10) + ': ' + str(c[0]) + ':' + str(c[1]) + ' ' + str(c[3]) + '/60') + + +def TimeoutNodi(cl): + while True: + time.sleep(1) + for c in cl: + c[3] += 1 + if (c[3] > 60): + printlog('Removing ' + c[2].ljust(10) + ' (' + c[0] + ':' + str(c[1]) + ') disappeared') + cl.remove(c) + + +def TimeoutTX(t, t_lock, r_lock, lista_lh): + while True: + if (t[1] < 5): + t[1] += 0.1 + if ((t[1] > 1.0) and (t[0] != 0)): + t[0] = 0 + printlog('Network watchdog has expired') + inserisci_lista(lista_lh, [check_string(t[2]), check_string(t[3]), check_string(t[4]), t[5], datetime.fromtimestamp(t[6]).strftime("%d-%m-%Y %H-%M-%S"), round(time.time() - t[6]) ], 20) + t[0] = 0 + t[2] = '' + t[3] = '' + t[4] = '' + t[5] = 0 + t[6] = 0 + pop_list = [] + for d in t_lock: + if (t_lock[d] < 5): + t_lock[d] += 0.1 + if ((t_lock[d] > 1.5) and (t_lock[d] != 0)): + pop_list.append(d) + r_lock.remove(d) + + for x in pop_list: + t_lock.pop(x) + printlog('Removed from blockeds queue ' + str(x)) + time.sleep(0.1) + + +def canTrasmit(cs): + global BLACK_LIST + call_sp = re.split(r'[-/]', cs) + call = call_sp[0] + if inlist(BLACK_LIST, call): + return False + if (len(call_sp) > 1): + if (call_sp[1] == 'RPT'): + return False + if (re.match(r'^\d?[A-Z]{1,2}\d{1,4}[A-Z]{1,3}$',call,re.IGNORECASE) and (len(call) <= 8)): + return True + else: + pass + return False + + +def lista_gw(cl): + info = '' + for c in cl: + info += c[2] + ':' + c[0] + ':' + str(c[1]) + ';' + return info + + +def lista_invio(lista): + info = '' + for i in range(len(lista)): + info += lista[len(lista)-i-1][0] + ':' + lista[len(lista)-i-1][1] + ':' + lista[len(lista)-i-1][2] + ':' + str(lista[len(lista)-i-1][3]) + ':' + lista[len(lista)-i-1][4] + ':' + str(lista[len(lista)-i-1][5]) + ';' + return info + + +def update_clients(cl): + global GW_BL + global IP_BL + for c in cl: + if (inlist(GW_BL, c[2]) or inlist(IP_BL, ip2long(c[0]))): + c[5] = 1 + else: + c[5] = 0 + + +def blacklist(f_bl, t_reload, cli): + global BLACK_LIST + global GW_BL + global IP_BL + f_time_old = 0 + try: + f_time = os.stat(f_bl).st_mtime + except: + pass + + while True: + f_time = os.stat(f_bl).st_mtime + if (f_time != f_time_old): + try: + file = open(f_bl) + BL_TMP = [] + GW_TMP = [] + IP_TMP = [] + printlog('Reload the Blacklist from File') + for row in file: + content = row.strip() + # riga valida + if ((len(content) > 3) and (content[0] != '#')): + c_split = content.split(':') + + # CALL + if (len(c_split) == 1 or c_split[0] == 'CS'): + if (len(c_split) == 1): + cont = content + if (c_split[0] == 'CS'): + cont = c_split[1] + if ((len(cont) <= 8) and (len(cont) >= 3)): + if (not inlist(BL_TMP, cont)): + bisect.insort(BL_TMP,cont) + + # GW + if (len(c_split) == 2 and c_split[0] == 'GW'): + if (not inlist(GW_TMP, c_split[1])): + bisect.insort(GW_TMP,c_split[1]) + + # IP + if (len(c_split) == 2 and c_split[0] == 'IP'): + try: + ipa = socket.gethostbyname(c_split[1]) + ipl = ip2long(ipa) + except: + ipl = 0 + printlog('Invalid hostname ' + c_split[1]) + if (ipl > 0): + if (not inlist(IP_TMP, ipl)): + bisect.insort(IP_TMP, ipl) + + file.close() + except Exception as ex: + printlog('Failed to load Blacklist from File ') + BLACK_LIST = BL_TMP.copy() + GW_BL = GW_TMP.copy() + IP_BL = IP_TMP.copy() + f_time_old = f_time + update_clients(cli) + else: + pass + time.sleep(t_reload) + + +## lettura file configurazione ## +def ReadConfig(f,p): + config = configparser.ConfigParser() + + config_file = f.strip() + config.read(config_file) + name = config['Info']['Name'] + description = config['Info']['Description'] + try: + id = int(config['Info']['id']) + except: + id = 0 + log_path = config['Log']['FilePath'] + log_name = config['Log']['FileRoot'] + try: + port = int(config['Network']['Port']) + except: + port = 42000 + try: + file_blacklist = config['Block List']['File'] + except: + file_blacklist = '' + + try: + t_reload_blacklist = float(config['Block List']['Time']) + except: + t_reload_blacklist = 5.0 + if (t_reload_blacklist < 0.1): + t_reload_blacklist = 0.1 + + p.append(id) # 0 + p.append(name) # 1 + p.append(description) # 2 + p.append(log_path) # 3 + p.append(log_name) # 4 + p.append(port) # 5 + p.append(file_blacklist) # 6 + p.append(t_reload_blacklist) # 7 + + +def RunServer(config): + global filelog + global version + global BLACK_LIST + global GW_BL + global IP_BL + host = '0.0.0.0' + port = config[5] + s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) + s.setblocking(1) + s.bind((host,port)) + clients = [] # addr, port, gw, t_corr, ID, lonly + c = [] + rx_lock = [] + rx_lock_tout = {} + + # lista LH + LH = [] + # lista bloccati + BL = [] + + id = 1 # id nodo + id_str = 1 # id stream + tx = [0, 0, '', '', '', 0, 0] # id_nodo, tout, gateway, src, dest, id_stream, start_time + + refl_name = config[1] + refl_desc = config[2] + + if ((config[0] > 0) and (config[0] < 1000000)): + refl_id = str(config[0]).zfill(5) + else: + refl_id = CalcID(refl_name) + + f_blacklist = config[6] + tr_blacklist = config[7] * 60.0 + + recvPackets = queue.Queue() + + print('Starting YSFReflector-' + version) + printlog('Starting YSFReflector-' + version) + threading.Thread(target=RecvData,args=(s,recvPackets)).start() + threading.Thread(target=ElencoNodi,args=(clients,)).start() + threading.Thread(target=TimeoutNodi,args=(clients,)).start() + threading.Thread(target=TimeoutTX,args=(tx,rx_lock_tout,rx_lock,LH)).start() + if (len(f_blacklist) > 0): + threading.Thread(target=blacklist,args=(f_blacklist,tr_blacklist,clients)).start() + + time_start = time.time() + + + while True: + data,addr = recvPackets.get() # bloccante se coda vuota + cmd = data[0:4] + if (cmd == b'YSFP'): + pres = False + for c in clients: + if ((c[0] == addr[0]) and (c[1] == addr[1])): + pres = True + c[3] = 0 + break + if not pres: + lonly = 0 + if inlist(GW_BL, (data[4:14]).decode().strip()): + lonly = 1 + if inlist(IP_BL, ip2long(addr[0])): + lonly = 1 + c=[addr[0], addr[1], (data[4:14]).decode().strip(), 0, id, lonly] + id += 1 + clients.append(c) + printlog('Adding ' + c[2].ljust(10) + ' (' + c[0] + ':' + str(c[1]) + ')') + s.sendto(b'YSFPREFLECTOR ',addr) + + if (cmd == b'YSFU'): + for c in clients: + if ((c[0] == addr[0]) and (c[1] == addr[1])): + printlog('Removing ' + c[2].ljust(10) + ' (' + c[0] + ':' + str(c[1]) + ') unlinked') + clients.remove(c) + break + + if ((cmd == b'YSFD') and (len(data) == 155)): + [id_corr, gw_corr] = getidgw(clients, addr) + if (tx[0] == 0): + if inlist(GW_BL, gw_corr): + tx_ok = False + block_r = 'GW' + else: + if inlist(IP_BL, ip2long(addr[0])): + tx_ok = False + block_r = 'IP' + else: + tx_ok = canTrasmit(data[14:24].decode().strip()) + block_r = 'CS' + if tx_ok: + if ((tx[0] == 0) and (id_corr != 0)): + tx[0] = id_corr + # gateway + tx[2] = data[4:14].decode().strip() + # src + tx[3] = data[14:24].decode().strip() + # dest + tx[4] = data[24:34].decode().strip() + # stream ID + tx[5] = id_str + # time start + tx[6] = time.time() + id_str += 1 + printlog('Received data from ' + tx[3].ljust(10) + ' to ' + tx[4].ljust(10) + ' at ' + tx[2].ljust(10)) + + else: + if (id_corr not in rx_lock): + rx_lock.append(id_corr) + inserisci_lista(BL, [check_string(data[4:14].decode().strip()), check_string(data[14:24].decode().strip()), check_string(data[24:34].decode().strip()), -1, datetime.fromtimestamp(time.time()).strftime("%d-%m-%Y %H-%M-%S"), -1 ], 20) + printlog('Data from ' + data[14:24].decode().strip().ljust(10) + ' at ' + data[4:14].decode().strip().ljust(10) + ' blocked/' + block_r) + # printlog('Bloccato: ' + data[14:24].decode().strip() + ' via ' + data[4:14].decode().strip()) + rx_lock_tout[id_corr] = 0 + + if ((id_corr == tx[0]) and (id_corr != 0)): + tx[1] = 0 + + for c in clients: + if (((c[0] != addr[0]) or (c[1] != addr[1])) and (id_corr == tx[0]) and (id_corr != 0) and (id_corr not in rx_lock)): + s.sendto(data,(c[0], c[1])) + + if ((data[34] & 0x01) == 0x01): + if (tx[0] != 0): + printlog('Received end of transmission') + inserisci_lista(LH, [check_string(tx[2]), check_string(tx[3]), check_string(tx[4]), tx[5], datetime.fromtimestamp(tx[6]).strftime("%d-%m-%Y %H-%M-%S"), round(time.time() - tx[6]) ], 20) + tx[0] = 0 + tx[2] = '' + tx[3] = '' + tx[4] = '' + tx[5] = 0 + tx[6] = 0 + + if (cmd == b'YSFS'): + # printlog('YSF server status enquiry from ' + addr[0] + ':' + str(addr[1])) + if (len(clients) > 999): + num_cli = 999 + else: + num_cli = len(clients) + info = 'YSFS' + str(refl_id).zfill(5) + refl_name.ljust(16) + refl_desc.ljust(14) + str(num_cli).zfill(3) + s.sendto(str.encode(info),addr) + + + ## messaggi per report attivo ## + if (cmd == b'QSRU'): + printlog('Received command ' + cmd.decode() + ' from: ' + addr[0] + ':' + str(addr[1])) + info = 'ASRU;' + str(round(time.time()-time_start)) + ';' + s.sendto(str.encode(info),addr) + + + if (cmd == b'QSRI'): + printlog('Received command ' + cmd.decode() + ' from: ' + addr[0] + ':' + str(addr[1])) + info = 'ASRI;' + str(refl_id) + ':' + refl_name + ':' + refl_desc + ';' + s.sendto(str.encode(info),addr) + + + if (cmd == b'QGWL'): + printlog('Received command ' + cmd.decode() + ' from: ' + addr[0] + ':' + str(addr[1])) + info = 'AGWL;' + lista_gw(clients) + s.sendto(str.encode(info),addr) + + if (cmd == b'QLHL'): + printlog('Received command ' + cmd.decode() + ' from: ' + addr[0] + ':' + str(addr[1])) + info = 'ALHL;' + lista_invio(LH) + s.sendto(str.encode(info),addr) + + if (cmd == b'QREJ'): + printlog('Received command ' + cmd.decode() + ' from: ' + addr[0] + ':' + str(addr[1])) + info = 'AREJ;' + lista_invio(BL) + s.sendto(str.encode(info),addr) + + if (cmd == b'QBLK'): + printlog('Received command ' + cmd.decode() + ' from: ' + addr[0] + ':' + str(addr[1])) + s_info = '' + for c in BLACK_LIST: + s_info += c + ';' + info = 'ABLK;' + s_info + s.sendto(str.encode(info),addr) + + + s.close() + + +def printlog(mess): + global filelog + global log_basename + log_file = log_basename + '-' + str(datetime.utcnow().strftime('%Y-%m-%d')) + '.log' + try: + if not os.path.isfile(log_file): + filelog.flush() + filelog.close() + filelog = open(log_file,'x') + except: + pass + str_log = check_string('M: ' + str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'))[:-3] + ' ' + mess) + try: + filelog.write(str_log + '\n') + filelog.flush() + except: + pass + + +######## main ######## + +version = '20210317' + +if (len(sys.argv) != 2): + print('Invalid Number of Arguments') + print('use: YSFReflector ') + sys.exit() + +if (sys.argv[1].strip() == '-v'): + print('YSFReflector version ' + version) + sys.exit() + +## lettura configurazione ## +config=[] +try: + ReadConfig(sys.argv[1].strip(), config) +except: + print('Unable to read configuration file') + sys.exit() + +log_basename = config[3] + '/' + config[4] +### log +log_file = log_basename + '-' + str(datetime.utcnow().strftime('%Y-%m-%d')) + '.log' +try: + if os.path.isfile(log_file): + filelog = open(log_file,'a') + else: + filelog = open(log_file,'x') +except: + print('Unable to Open Log File') + sys.exit() + +BLACK_LIST = [] +GW_BL = [] +IP_BL = [] + +RunServer(config) + + diff --git a/YSFReflector.ini b/YSFReflector.ini new file mode 100644 index 0000000..e759a92 --- /dev/null +++ b/YSFReflector.ini @@ -0,0 +1,23 @@ +[General] +Daemon=0 + +[Info] +# Remember to register your YSFReflector at: +# https://register.ysfreflector.de +Name= +Description= + +[Log] +# Logging levels, 0=No logging +DisplayLevel=1 +FileLevel=1 +FilePath=/var/log +FileRoot=YSFReflector + +[Network] +Port=42395 +Debug=0 + +[Block List] +File=/usr/local/etc/deny.db +Time=5 diff --git a/deny.db b/deny.db new file mode 100644 index 0000000..4e73cef --- /dev/null +++ b/deny.db @@ -0,0 +1,11 @@ +########################## +## Example of blocklist ## +########################## +## Block callsign +# CS:IU5JAE +# IU5JAE +## Block gateway +# GW:TESTBDG +## Block IP +# IP:80.181.214.194 +