From ea1c04528c3beff92fdbe6789dc7710b00b484b9 Mon Sep 17 00:00:00 2001
From: KF7EEL 1: Log into your Pi-Star device. Note: Link can be used only once. To run the script again, simply reload the page and paste a new command into the command line. DMR ID: ''' + str(i[0]) + ''': Error Your passphrase for ''' + str(i[0]) + ''': Copy and paste: ''' + str(gen_passphrase(int(i[0]))) + ''' Phonetically spelled: ''' + convert_nato(str(gen_passphrase(int(i[0])))) + ''' Your passphrase for ''' + str(i[0]) + ''': Copy and paste: ''' + str(gen_passphrase(int(i[0]))) + ''' Phonetically spelled: ''' + convert_nato(str(gen_passphrase(int(i[0])))) + ''' Your passphrase for ''' + str(i[0]) + ''': Copy and paste: ''' + legacy_passphrase + ''' Phonetically spelled: ''' + convert_nato(legacy_passphrase) + ''' Your passphrase for ''' + str(i[0]) + ''': Copy and paste: ''' + str(i[1]) + ''' Phonetically spelled: ''' + convert_nato(str(i[1])) + ''' Use this page to sync changes from RadioID.net with this system (such as a new DMR ID, name change, etc.). Updating your information from RadioID.net will overwrite any custom authentication passphrases, your city, and name in the database. Are you sure you want to continue? Sent email to: ' + u.email + ' Find user in "List Users", then click on the email link.' User ''' + str(user) + ''' has been enabled. User ''' + str(user) + ''' has been disabled. User ''' + str(user) + ''' changed to ''' + request.form.get('username') + '''. Changed email for user: ''' + str(user) + ''' to ''' + request.form.get('email') + ''' Changed notes for user: ''' + str(user) + '''. Changed password for user: ''' + str(user) + ''' Changed authentication settings for user: ''' + str(user) + ''' Deleted user: ''' + str(delete_user.username) + ''' User now Admin: ''' + str(request.args.get('callsign')) + ''' Admin now a user: ''' + str(request.args.get('callsign') ) + ''' User approved: ''' + str(request.args.get('callsign')) + ''' Email verified for: ''' + str(request.args.get('callsign')) + ''' Verify email - ''' + str(u.username) + ''' Give Admin role: ''' + str(u.username) + ''' Revert to User role: ''' + str(u.username) + ''' Email confirmed: ''' + str(u.email_confirmed_at) + ''' {DMR ID: Method, 2nd DMR ID: Method} Example: Flushed entire auth DB. Flushed auth DB for: ''' + request.args.get('portal_username') + ''' Flushed auth DB for: ''' + request.args.get('mmdvm_server') + ''' Flushed auth DB for: ''' + request.args.get('peer_ip') + ''' Flushed auth DB for: ''' + request.args.get('dmr_id') + ''' Flush auth log for: ''' + request.args.get('portal_username') + ''' Flush auth log for: ''' + request.args.get('dmr_id') + ''' Log for: ''' + g_arg + ''' Flush authentication log for server: ''' + request.args.get('mmdvm_server') + ''' Log for MMDVM server: ''' + request.args.get('mmdvm_server') + ''' Flush authentication log for IP: ''' + request.args.get('peer_ip') + ''' Log for IP address: ''' + request.args.get('peer_ip') + ''' Flush entire authentication log Un-registered authentication attempts Authentication log by DMR ID Currently active talkgroups. Updated every 2 minutes. DMR ID: ''' + str(i[0]) + ''' Redirecting in 3 seconds. Redirecting in 3 seconds. Redirecting in 3 seconds. Redirecting in 3 seconds.
+
+
+
+
+
+
+
+Pi-Star Instructions
+
2: Change to Read-Write mode of the device by issuing the command:rpi-rw
+
3a: Change to the root user by issuing the command:sudo su -
+
3b: Now type pwd and verify you get a return indicating you are in the /root directory. If you are in the wrong directory, it is because you're not following the instructions and syntax above! This is a show stopper, and your attempt to load the files correctly, will fail !
4: Issue one of the commands below for the chosen DMR ID:bash <(curl -s "''' + str(url) + '/get_script?dmr_id=' + str(i[0]) + '&number=' + str(link_num) + '''")
5: When asked for server ports, use the information above to populate the correct fields.
6: Reboot your Pi-Star device
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Updated your information.
'
+ update_from_radioid(request.args.get('callsign'))
+ else:
+ content = '''
+Yes, update my information.
+
+'''
+ return render_template('flask_user_layout.html', markup_content = Markup(content))
+
+
+ @app.route('/email_user', methods=['POST', 'GET'])
+ @roles_required('Admin')
+ @login_required # User must be authenticated
+ def email_user():
+
+ if request.method == 'GET' and request.args.get('callsign'):
+ content = '''
+Send email to user: ''' + request.args.get('callsign') + '''
+
+
+
+
+
+
+
+ List/edit users:
+
+
+
+ '''
+ for i in u:
+ u_list = u_list + '''
+Callsign
+Name
+Enabled
+DMR ID:Authentication
+Notes
+
+
+'''+ '\n'
+ content = u_list + '''
+ ''' + str(i.username) + '''
+ ''' + str(i.first_name) + ' ' + str(i.last_name) + '''
+ ''' + str(i.active) + '''
+ ''' + str(i.dmr_ids) + '''
+ ''' + str(i.notes) + '''
+Users waiting for approval:
+
+
+
+ '''
+ for i in u:
+## print(i.username)
+## print(i.initial_admin_approved)
+ if i.initial_admin_approved == False:
+ wait_list = wait_list+ '''
+Callsign
+Name
+Enabled
+DMR ID:Authentication
+
+
+'''+ '\n'
+ content = wait_list + '''
+ ''' + str(i.username) + '''
+ ''' + str(i.first_name) + ' ' + str(i.last_name) + '''
+ ''' + str(i.active) + '''
+ ''' + str(i.dmr_ids) + '''
+
+
+
'
+ content = '''
+
+ '''
+ for i in id_dict.items():
+ print(i[1])
+ if isinstance(i[1], int) == True:
+ passphrase_list = passphrase_list + '''
+DMR ID
+Passphrase
+
+ \n'''
+ if i[1] == '':
+ passphrase_list = passphrase_list + '''''' + str(i[0]) + '''
+''' + str(gen_passphrase(int(i[0]))) + '''
+
+ \n'''
+ if not isinstance(i[1], int) == True and i[1] != '':
+ passphrase_list = passphrase_list + '''''' + str(i[0]) + '''
+''' + legacy_passphrase + '''
+
+ \n'''
+
+ passphrase_list = passphrase_list + '''' + str(i[0]) + '''
+''' + str(i[1]) + '''
+
+
+
+
+
+First Name
+Last Name
+
+
+ ''' + u.first_name + '''
+ ''' + u.last_name + '''
+
+
+
+City
+''' + u.city + '''
+ Options for: ''' + u.username + '''
+
+
+
+
+
+
+
+
+
+
+ ''' + confirm_link + '''
+
+
+
+
+
+ ''' + role_link + '''
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Passphrase Authentication Method Key
+
+
+
+
+
+Calculated
+Legacy (config)
+Custom
+
+
+
+0 - default,
+
1-999 - new calculation''
+'passphrase'
+
{1234567: '', 134568: 0, 1234569: 'passphr8s3'}
+
+
+
+
+
+
+
+
+
'
+
+ elif request.args.get('mmdvm_server') and not request.args.get('flush_db_mmdvm'):
+ a = AuthLog.query.filter_by(server_name=request.args.get('mmdvm_server')).order_by(AuthLog.login_time.desc()).all()
+ content = '''
+
+ \n'''
+ for i in a:
+ if i.login_type == 'Attempt':
+ content = content + '''
+
+
+ DMR ID
+
+
+ Portal Username
+
+
+ Login IP
+
+
+ Passphrase
+
+
+ Server
+
+
+ Time (UTC)
+
+
+ Login Status
+
+
+'''
+ if i.login_type == 'Confirmed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+ if i.login_type == 'Failed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+ content = content + ' ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+
'
+
+ elif request.args.get('peer_ip') and not request.args.get('flush_db_ip'):
+ a = AuthLog.query.filter_by(peer_ip=request.args.get('peer_ip')).order_by(AuthLog.login_time.desc()).all()
+ content = '''
+
+ \n'''
+ for i in a:
+ if i.login_type == 'Attempt':
+ content = content + '''
+
+
+ DMR ID
+
+
+ Portal Username
+
+
+ Login IP
+
+
+ Passphrase
+
+
+ Server
+
+
+ Time (UTC)
+
+
+ Login Status
+
+
+'''
+ if i.login_type == 'Confirmed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + i.server_name + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+ if i.login_type == 'Failed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + i.server_name + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+ content = content + ' ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + i.server_name + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+
'
+
+ else:
+ #a = AuthLog.query.all()
+## a = AuthLog.query.order_by(AuthLog.login_time.desc()).limit(300).all()
+ a = AuthLog.query.order_by(AuthLog.login_time.desc()).all()
+ recent_list = []
+## r = AuthLog.query.order_by(AuthLog.login_dmr_id.desc()).all()
+ content = '''
+
+ \n'''
+ for i in a:
+ if i.login_type == 'Attempt':
+ content = content + '''
+
+
+ DMR ID
+
+
+ Portal Username
+
+
+ Login IP
+
+
+ Passphrase
+
+
+ Server
+
+
+ Time (UTC)
+
+
+ Login Status
+
+
+'''
+ if i.login_type == 'Confirmed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + i.peer_ip + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+ if i.login_type == 'Failed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + i.peer_ip + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+ content = content + ' ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + i.peer_ip + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+
'
+ return render_template('flask_user_layout.html', markup_content = Markup(content))
+
+ @app.route('/user_tg')
+ def tg_status():
+ cu = current_user
+ u = User.query.filter_by(username=cu.username).first()
+ sl = ServerList.query.all()
+ user_ids = ast.literal_eval(u.dmr_ids)
+ content = '
+ \n'''
+ for i in a:
+ if i.login_dmr_id not in recent_list:
+ recent_list.append(i.login_dmr_id)
+ if i.login_type == 'Attempt':
+ content = content + '''
+
+
+ DMR ID
+
+
+ Portal Username
+
+
+ Login IP
+
+
+ Passphrase
+
+
+ Server
+
+
+ Time (UTC)
+
+
+ Last Login Status
+
+
+'''
+ if i.login_type == 'Confirmed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+ if i.login_type == 'Failed':
+ content = content + '''
+ ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+'''
+
+ content = content + ' ''' + str(i.login_dmr_id) + '''
+ ''' + i.portal_username + '''
+ ''' + str(i.peer_ip) + '''
+ ''' + i.login_auth_method + '''
+ ''' + str(i.server_name) + '''
+ ''' + str(i.login_time) + '''
+ ''' + str(i.login_type) + '''
+
+
+
'''
+## except:
+## pass
+
+
+## #TS1
+## for tg in active_tgs[s.name][i[0]][1]['2']:
+## content = content + '''
+
+
+
+Server: ''' + str(s.name) + '''
+
+
+
+
+
+
+
+
+
+
+Timeslot 1
+ ''' + str(active_tgs[s.name][ts[0]][0]['1'])[1:-1] + '''
+
+
+
+Timeslot 2
+ ''' + str(active_tgs[s.name][ts[0]][1]['2'])[1:-1] + '''
+ ''' + str(tg) + '''
+##'''
+## print(active_tgs[s.name][i[0]])
+## content = active_tgs[s.name][i[0]][1]['2']
+## content = 'hji'
+
+ return render_template('flask_user_layout.html', markup_content = Markup(content))
+
+
+ @app.route('/test')
+ def test_peer():
+ #user = User(
+ # username='admin3',
+ # email_confirmed_at=datetime.datetime.utcnow(),
+ # password=user_manager.hash_password('admin'),
+ # )
+ #user.roles.append(Role(name='Admin'))
+ #user.roles.append(Role(name='User'))
+ #user.add_roles('Admin')
+ #db.session.add(user)
+ #db.session.commit()
+ u = User.query.filter_by(username='admin').first()
+ #u = Role.query.all()
+## u = User.query.filter(User.dmr_ids.contains('3153591')).first()
+ #u = User.query.all()
+## #tu = User.query().all()
+#### print((tu.dmr_ids))
+#### #print(tu.dmr_ids)
+#### return str(tu.dmr_ids) #str(get_ids('kf7eel'))
+## login_passphrase = ast.literal_eval(u.dmr_ids)
+## print('|' + login_passphrase[3153591] + '|')
+## #print(u.dmr_ids)
+## #tu.dmr_ids = 'jkgfldj'
+## #db.session.commit()
+## return str(u.dmr_ids)
+## u = User.query.filter(User.dmr_ids.contains('3153591')).first()
+## #tu = User.query.all()
+## #tu = User.query().all()
+#### print((tu.dmr_ids))
+#### #print(tu.dmr_ids)
+#### return str(tu.dmr_ids) #str(get_ids('kf7eel'))
+## print(u)
+## login_passphrase = ast.literal_eval(u.dmr_ids)
+##
+## #tu.dmr_ids = 'jkgfldj'
+## #db.session.commit()
+## return str([u.is_active, login_passphrase[3153591]])
+ #edit_user = User.query.filter(User.username == 'bob').first()
+ #edit_user.active = False
+
+ #db.session.commit()
+ #print((current_user.has_roles('Admin')))
+ #u.roles.append(Role(name='Admin'))
+ #print((current_user.has_roles('Admin')))
+ #db.session.commit()
+ #db.session.add(u)
+ #db.session.commit()
+## admin_role = UserRoles(
+## user_id=3,
+## role_id=1,
+## )
+## user_role = UserRoles(
+## user_id=3,
+## role_id=2,
+## )
+## db.session.add(user_role)
+## db.session.add(admin_role)
+## db.session.commit()
+ #print(role)
+## for i in u:
+## print(i.username)
+ #u = User.query.filter_by(username='kf7eel').first()
+ #print(u.id)
+ #u_role = UserRoles.query.filter_by(user_id=u.id).first()
+ #if u_role.role_id == 2:
+ # print('userhasjkdhfdsejksfdahjkdhjklhjkhjkl')
+## print(u.has_roles('Admin'))
+ #u_role.role_id = 1
+ #print(u)
+ # for i in u:
+ ##print(i.initial_admin_approved)
+ #if not i.initial_admin_approved:
+ #print(i.username)
+ # print(i)
+ #u_role = UserRoles.query.filter_by(id=2).first().role_id
+ #u_role = 1
+ # db.session.commit()
+ #u_role = UserRoles.query.filter_by(id=u.id).first().role_id
+ #print(u_role)
+ #return str(u)
+## if not u.active:
+## flash('We come in peace', 'success')
+## content = 'hello'
+ #add
+## burn_list = BurnList(
+## dmr_id=3153595,
+## version=1,
+## )
+## db.session.add(burn_list)
+## db.session.commit()
+##
+ #generate dict
+## b = BurnList.query.all()
+## print(b)
+## burn_dict = {}
+## for i in b:
+## print(i.dmr_id)
+## burn_dict[i.dmr_id] = i.version
+## content = burn_dict
+## # delete
+#### delete_b = BurnList.query.filter_by(dmr_id=3153591).first()
+#### db.session.delete(delete_b)
+#### db.session.commit()
+## a = AuthLog.query.all()
+## print(a)
+## authlog_flush()
+## peer_delete('mmdvm', 1)
+ user_ids = ast.literal_eval(u.dmr_ids)
+ for i in user_ids.items():# active_tgs:
+ print(active_tgs['test'][i[0]])
+ content = active_tgs['test'][i[0]][1]['2']
+## content = user_ids
+ return render_template('flask_user_layout.html', markup_content = Markup(content))
+
+ def get_peer_configs(_server_name):
+ mmdvm_pl = mmdvmPeer.query.filter_by(server=_server_name).filter_by(enabled=True).all()
+ xlx_pl = xlxPeer.query.filter_by(server=_server_name).filter_by(enabled=True).all()
+## print(mmdvm_pl)
+ peer_config_list = {}
+ for i in mmdvm_pl:
+## print(i.master_ip)
+ peer_config_list.update({i.name: {
+ 'MODE': 'PEER',
+ 'ENABLED': i.enabled,
+ 'LOOSE': i.loose,
+ 'SOCK_ADDR': (gethostbyname(i.ip), i.port),
+ 'IP': i.ip,
+ 'PORT': i.port,
+ 'MASTER_SOCKADDR': (gethostbyname(i.master_ip), i.master_port),
+ 'MASTER_IP': i.master_ip,
+ 'MASTER_PORT': i.master_port,
+
+ 'PASSPHRASE': i.passphrase,
+ 'CALLSIGN': i.callsign,
+ 'RADIO_ID': int(i.radio_id), #int(i.radio_id).to_bytes(4, 'big'),
+ 'RX_FREQ': i.rx_freq,
+ 'TX_FREQ': i.tx_freq,
+ 'TX_POWER': i.tx_power,
+ 'COLORCODE': i.color_code,
+ 'LATITUDE': i.latitude,
+ 'LONGITUDE': i.longitude,
+ 'HEIGHT': i.height,
+ 'LOCATION': i.location,
+ 'DESCRIPTION': i.description,
+ 'SLOTS': i.slots,
+ 'URL': i.url,
+ 'GROUP_HANGTIME': i.group_hangtime,
+ 'OPTIONS': i.options,
+ 'USE_ACL': i.use_acl,
+ 'SUB_ACL': i.sub_acl,
+ 'TG1_ACL': i.tg1_acl,
+ 'TG2_ACL': i.tg2_acl
+ }})
+ for i in xlx_pl:
+ peer_config_list.update({i.name: {
+ 'MODE': 'XLXPEER',
+ 'ENABLED': i.enabled,
+ 'LOOSE': i.loose,
+ 'SOCK_ADDR': (gethostbyname(i.ip), i.port),
+ 'IP': i.ip,
+ 'PORT': i.port,
+ 'MASTER_SOCKADDR': (gethostbyname(i.master_ip), i.master_port),
+ 'MASTER_IP': i.master_ip,
+ 'MASTER_PORT': i.master_port,
+
+ 'PASSPHRASE': i.passphrase,
+ 'CALLSIGN': i.callsign,
+ 'RADIO_ID': int(i.radio_id), #int(i.radio_id).to_bytes(4, 'big'),
+ 'RX_FREQ': i.rx_freq,
+ 'TX_FREQ': i.tx_freq,
+ 'TX_POWER': i.tx_power,
+ 'COLORCODE': i.color_code,
+ 'LATITUDE': i.latitude,
+ 'LONGITUDE': i.longitude,
+ 'HEIGHT': i.height,
+ 'LOCATION': i.location,
+ 'DESCRIPTION': i.description,
+ 'SLOTS': i.slots,
+ 'URL': i.url,
+ 'OPTIONS': i.options,
+ 'GROUP_HANGTIME': i.group_hangtime,
+ 'XLXMODULE': i.xlxmodule,
+ 'USE_ACL': i.use_acl,
+ 'SUB_ACL': i.sub_acl,
+ 'TG1_ACL': i.tg1_acl,
+ 'TG2_ACL': i.tg2_acl
+ }})
+#### print('peers')
+## print('----------------')
+ return peer_config_list
+
+ def get_burnlist():
+ b = BurnList.query.all()
+ #print(b)
+ burn_dict = {}
+ for i in b:
+ #print(i.dmr_id)
+ burn_dict[i.dmr_id] = i.version
+ return burn_dict
+
+ def add_burnlist(_dmr_id, _version):
+ burn_list = BurnList(
+ dmr_id=_dmr_id,
+ version=_version,
+ )
+ db.session.add(burn_list)
+ db.session.commit()
+
+ def update_burnlist(_dmr_id, _version):
+ update_b = BurnList.query.filter_by(dmr_id=_dmr_id).first()
+ update_b.version=_version
+ db.session.commit()
+ def delete_burnlist(_dmr_id):
+ delete_b = BurnList.query.filter_by(dmr_id=_dmr_id).first()
+ db.session.delete(delete_b)
+ db.session.commit()
+
+ def authlog_add(_dmr_id, _peer_ip, _server_name, _portal_username, _auth_method, _login_type):
+ auth_log_add = AuthLog(
+ login_dmr_id=_dmr_id,
+ login_time=datetime.datetime.utcnow(),
+ portal_username = _portal_username,
+ peer_ip = _peer_ip,
+ server_name = _server_name,
+ login_auth_method=_auth_method,
+ login_type=_login_type
+ )
+ db.session.add(auth_log_add)
+ db.session.commit()
+
+ def authlog_flush():
+ AuthLog.query.delete()
+ db.session.commit()
+
+ def authlog_flush_user(_user):
+ flush_e = AuthLog.query.filter_by(portal_username=_user).all()
+ for i in flush_e:
+ db.session.delete(i)
+ db.session.commit()
+
+ def authlog_flush_dmr_id(_dmr_id):
+ flush_e = AuthLog.query.filter_by(login_dmr_id=_dmr_id).all()
+ for i in flush_e:
+ db.session.delete(i)
+ db.session.commit()
+ def authlog_flush_mmdvm_server(_mmdvm_serv):
+ flush_e = AuthLog.query.filter_by(server_name=_mmdvm_serv).all()
+ for i in flush_e:
+ db.session.delete(i)
+ db.session.commit()
+ def authlog_flush_ip(_ip):
+ flush_e = AuthLog.query.filter_by(peer_ip=_ip).all()
+ for i in flush_e:
+ db.session.delete(i)
+ db.session.commit()
+## def peer_delete(_mode, _id):
+## if _mode == 'xlx':
+## p = xlxPeer.query.filter_by(id=_id).first()
+## if _mode == 'mmdvm':
+## p = mmdvmPeer.query.filter_by(id=_id).first()
+## db.session.delete(p)
+## db.session.commit()
+
+ def server_delete(_name):
+ s = ServerList.query.filter_by(name=_name).first()
+ m = MasterList.query.filter_by(server=_name).all()
+ p = ProxyList.query.filter_by(server=_name).all()
+ o = OBP.query.filter_by(server=_name).all()
+ dr = BridgeRules.query.filter_by(server=_name).all()
+ mp = mmdvmPeer.query.filter_by(server=_name).all()
+ xp = xlxPeer.query.filter_by(server=_name).all()
+ for d in m:
+ db.session.delete(d)
+ for d in p:
+ db.session.delete(d)
+ for d in o:
+ db.session.delete(d)
+ for d in dr:
+ db.session.delete(d)
+ for d in mp:
+ db.session.delete(d)
+ for d in xp:
+ db.session.delete(d)
+ db.session.delete(s)
+
+ db.session.commit()
+ def peer_delete(_mode, _server, _name):
+ if _mode == 'mmdvm':
+ p = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first()
+ if _mode == 'xlx':
+ p = xlxPeer.query.filter_by(server=_server).filter_by(name=_name).first()
+ dr = BridgeRules.query.filter_by(server=_server).filter_by(system_name=_name).all()
+ for d in dr:
+ db.session.delete(d)
+ db.session.delete(p)
+ db.session.commit()
+
+ def shared_secrets():
+ s = ServerList.query.all() #filter_by(name=_name).first()
+ r_list = []
+ for i in s:
+ r_list.append(str(i.secret))
+ return r_list
+
+ def bridge_add(_name, _desc, _public, _tg):
+ add_bridge = BridgeList(
+ bridge_name = _name,
+ description = _desc,
+ public_list = _public,
+ tg = _tg
+ )
+ db.session.add(add_bridge)
+ db.session.commit()
+ def update_bridge_list(_name, _desc, _public, _new_name, _tg):
+ bl = BridgeList.query.filter_by(bridge_name=_name).first()
+ bl.bridge_name = _new_name
+ bl.description = _desc
+ bl.public_list = _public
+ bl.tg = _tg
+ db.session.commit()
+
+ def bridge_delete(_name): #, _server):
+ bl = BridgeList.query.filter_by(bridge_name=_name).first()
+ db.session.delete(bl)
+ sl = ServerList.query.all()
+ for i in sl:
+ delete_system_bridge(_name, i.name)
+ db.session.commit()
+
+ def generate_rules(_name):
+
+ # generate UNIT list
+## print('get rules')
+## print(_name)
+ xlx_p = xlxPeer.query.filter_by(server=_name).all()
+ mmdvm_p = mmdvmPeer.query.filter_by(server=_name).all()
+ all_m = MasterList.query.filter_by(server=_name).all()
+ all_o = OBP.query.filter_by(server=_name).all()
+ all_p = ProxyList.query.filter_by(server=_name).all()
+ rules = BridgeRules.query.filter_by(server=_name).all()
+ UNIT = []
+ BRIDGES = {}
+ disabled = {}
+ for i in all_m:
+ if i.active == False:
+ disabled[i.name] = i.name
+ else:
+ if i.enable_unit == True:
+ UNIT.append(i.name)
+ for i in all_p:
+ if i.active == False:
+ disabled[i.name] = i.name
+ else:
+ if i.enable_unit == True:
+ n_systems = i.internal_stop_port - i.internal_start_port
+ n_count = 0
+ while n_count < n_systems:
+ UNIT.append(i.name + '-' + str(n_count))
+ n_count = n_count + 1
+ for i in all_o:
+ if i.enabled == False:
+ disabled[i.name] = i.name
+ else:
+ if i.enable_unit == True:
+ UNIT.append(i.name)
+ for i in xlx_p:
+ if i.enabled == False:
+ disabled[i.name] = i.name
+ else:
+ if i.enable_unit == True:
+ UNIT.append(i.name)
+ for i in mmdvm_p:
+ if i.enabled == False:
+ disabled[i.name] = i.name
+ else:
+ if i.enable_unit == True:
+ UNIT.append(i.name)
+ temp_dict = {}
+ # populate dict with needed bridges
+ for r in rules:
+## print(r.bridge_name)
+## b = BridgeRules.query.filter_by(server=_name).filter_by(server=_name).all()
+## for d in temp_dict.items():
+## if r.bridge_name == d[0]:
+## print('update rule')
+## if r.bridge_name != d[0]:
+## print('add dict entry and rule')
+ temp_dict[r.bridge_name] = []
+## print(temp_dict)
+ BRIDGES = temp_dict.copy()
+ for r in temp_dict.items():
+ b = BridgeRules.query.filter_by(bridge_name=r[0]).filter_by(server=_name).all()
+ for s in b:
+ try:
+ if s.system_name == disabled[s.system_name]:
+ pass
+ except:
+ if s.timeout == '':
+ timeout = 0
+ else:
+ timeout = int(s.timeout)
+ if s.proxy == True:
+ p = ProxyList.query.filter_by(server=_name).filter_by(name=s.system_name).first()
+ print(p.external_port)
+ n_systems = p.internal_stop_port - p.internal_start_port
+ n_count = 0
+ while n_count < n_systems:
+ BRIDGES[r[0]].append({'SYSTEM': s.system_name + '-' + str(n_count), 'TS': s.ts, 'TGID': s.tg, 'ACTIVE': s.active, 'TIMEOUT': timeout, 'TO_TYPE': s.to_type, 'ON': ast.literal_eval(str('[' + s.on + ']')), 'OFF': ast.literal_eval(str('[4000,' + s.off + ']')), 'RESET': ast.literal_eval(str('[' + s.reset + ']'))})
+ n_count = n_count + 1
+
+ else:
+ BRIDGES[r[0]].append({'SYSTEM': s.system_name, 'TS': s.ts, 'TGID': s.tg, 'ACTIVE': s.active, 'TIMEOUT': timeout, 'TO_TYPE': s.to_type, 'ON': ast.literal_eval(str('[' + s.on + ']')), 'OFF': ast.literal_eval(str('[' + s.off + ']')), 'RESET': ast.literal_eval(str('[' + s.reset + ']'))})
+
+## for d in b:
+## print(b.system_name)
+
+## if r.bridge_name == d[0]:
+## print('update rule')
+## if r.bridge_name != d[0]:
+## print('add dict entry and rule')
+
+## print(r.tg)
+## print(BRIDGES)
+ return [UNIT, BRIDGES]
+
+
+ def server_get(_name):
+## print(_name)
+ #s = ServerList.query.filter_by(name=_name).first()
+ # print(s.name)
+ i = ServerList.query.filter_by(name=_name).first()
+## print(i.name)
+ s_config = {}
+ s_config['GLOBAL'] = {}
+ s_config['REPORTS'] = {}
+ s_config['ALIASES'] = {}
+ s_config['USER_MANAGER'] = {}
+
+ s_config['GLOBAL'].update({
+ 'PATH': i.global_path,
+ 'PING_TIME': i.global_ping_time,
+ 'MAX_MISSED': i.global_max_missed,
+ 'USE_ACL': i.global_use_acl,
+ 'REG_ACL': i.global_reg_acl,
+ 'SUB_ACL': i.global_sub_acl,
+ 'TG1_ACL': i.global_tg1_acl,
+ 'TG2_ACL': i.global_tg2_acl
+ })
+
+ s_config['REPORTS'].update({
+ 'REPORT': i.report_enable,
+ 'REPORT_INTERVAL': i.report_interval,
+ 'REPORT_PORT': i.report_port,
+ 'REPORT_CLIENTS': i.report_clients.split(',')
+ })
+ s_config['ALIASES'].update({
+ 'TRY_DOWNLOAD':i.ai_try_download,
+ 'PATH': i.ai_path,
+ 'PEER_FILE': i.ai_peer_file,
+ 'SUBSCRIBER_FILE': i.ai_subscriber_file,
+ 'TGID_FILE': i.ai_tgid_file,
+ 'PEER_URL': i.ai_peer_url,
+ 'SUBSCRIBER_URL': i.ai_subs_url,
+ 'STALE_TIME': i.ai_stale * 86400,
+ })
+ s_config['USER_MANAGER'].update({
+ 'SHORTEN_LENGTH': shorten_length,
+ 'SHORTEN_SAMPLE': shorten_sample,
+ 'EXTRA_1': extra_1,
+ 'EXTRA_2': extra_2,
+ 'EXTRA_INT_1': extra_int_1,
+ 'EXTRA_INT_2': extra_int_2,
+ 'APPEND_INT': append_int,
+ 'SHORTEN_PASSPHRASE': i.um_shorten_passphrase,
+ 'BURN_FILE': i.um_burn_file,
+ 'BURN_INT': burn_int,
+
+
+ })
+ print(s_config['REPORTS'])
+ return s_config
+ def masters_get(_name):
+## # print(_name)
+ #s = ServerList.query.filter_by(name=_name).first()
+ # print(s.name)
+ i = MasterList.query.filter_by(server=_name).filter_by(active=True).all()
+ o = OBP.query.filter_by(server=_name).filter_by(enabled=True).all()
+ p = ProxyList.query.filter_by(server=_name).filter_by(active=True).all()
+ # print('get masters')
+ master_config_list = {}
+## master_config_list['SYSTEMS'] = {}
+ # print(i)
+ for m in i:
+## print (m.name)
+ master_config_list.update({m.name: {
+ 'MODE': 'MASTER',
+ 'ENABLED': m.active,
+ 'USE_USER_MAN': m.enable_um,
+ 'STATIC_APRS_POSITION_ENABLED': m.static_positions,
+ 'REPEAT': m.repeat,
+ 'MAX_PEERS': m.max_peers,
+ 'IP': m.ip,
+ 'PORT': m.port,
+ 'PASSPHRASE': m.passphrase, #bytes(m.passphrase, 'utf-8'),
+ 'GROUP_HANGTIME': m.group_hang_time,
+ 'USE_ACL': m.use_acl,
+ 'REG_ACL': m.reg_acl,
+ 'SUB_ACL': m.sub_acl,
+ 'TG1_ACL': m.tg1_acl,
+ 'TG2_ACL': m.tg2_acl
+ }})
+ master_config_list[m.name].update({'PEERS': {}})
+ for obp in o:
+## print(type(obp.network_id))
+ master_config_list.update({obp.name: {
+ 'MODE': 'OPENBRIDGE',
+ 'ENABLED': obp.enabled,
+ 'NETWORK_ID': obp.network_id, #int(obp.network_id).to_bytes(4, 'big'),
+ 'IP': gethostbyname(obp.ip),
+ 'PORT': obp.port,
+ 'PASSPHRASE': obp.passphrase, #bytes(obp.passphrase.ljust(20,'\x00')[:20], 'utf-8'),
+ 'TARGET_SOCK': (obp.target_ip, obp.target_port),
+ 'TARGET_IP': gethostbyname(obp.target_ip),
+ 'TARGET_PORT': obp.target_port,
+ 'BOTH_SLOTS': obp.both_slots,
+ 'USE_ACL': obp.use_acl,
+ 'SUB_ACL': obp.sub_acl,
+ 'TG1_ACL': obp.tg_acl,
+ 'TG2_ACL': 'PERMIT:ALL'
+ }})
+ for pr in p:
+ master_config_list.update({pr.name: {
+ 'MODE': 'PROXY',
+ 'ENABLED': pr.active,
+ 'EXTERNAL_PROXY_SCRIPT': pr.external_proxy,
+ 'STATIC_APRS_POSITION_ENABLED': pr.static_positions,
+ 'USE_USER_MAN': pr.enable_um,
+ 'REPEAT': pr.repeat,
+ 'PASSPHRASE': pr.passphrase, #bytes(pr.passphrase, 'utf-8'),
+ 'EXTERNAL_PORT': pr.external_port,
+ 'INTERNAL_PORT_START': pr.internal_start_port,
+ 'INTERNAL_PORT_STOP': pr.internal_stop_port,
+ 'GROUP_HANGTIME': pr.group_hang_time,
+ 'USE_ACL': pr.use_acl,
+ 'REG_ACL': pr.reg_acl,
+ 'SUB_ACL': pr.sub_acl,
+ 'TG1_ACL': pr.tg1_acl,
+ 'TG2_ACL': pr.tg2_acl
+ }})
+ master_config_list[pr.name].update({'PEERS': {}})
+
+ # print(master_config_list)
+ return master_config_list
+
+ def add_system_rule(_bridge_name, _system_name, _ts, _tg, _active, _timeout, _to_type, _on, _off, _reset, _server, _public_list):
+ proxy = ProxyList.query.filter_by(server=_server).filter_by(name=_system_name).first()
+ is_proxy = False
+ try:
+ if _system_name == proxy.name:
+ is_proxy = True
+ except:
+ pass
+ add_system = BridgeRules(
+ bridge_name = _bridge_name,
+ system_name = _system_name,
+ ts = _ts,
+ tg = _tg,
+ active = _active,
+ timeout = _timeout,
+ to_type = _to_type,
+ on = _on,
+ off = _off,
+ reset = _reset,
+ server = _server,
+ public_list = _public_list,
+ proxy = is_proxy
+ )
+ db.session.add(add_system)
+ db.session.commit()
+
+ def edit_system_rule(_bridge_name, _system_name, _ts, _tg, _active, _timeout, _to_type, _on, _off, _reset, _server, _public_list):
+ proxy = ProxyList.query.filter_by(server=_server).filter_by(name=_system_name).first()
+ is_proxy = False
+ try:
+ if _system_name == proxy.name:
+ is_proxy = True
+ except:
+ pass
+ r = BridgeRules.query.filter_by(system_name=_system_name).filter_by(bridge_name=_bridge_name).first()
+ print('---')
+ print(_system_name)
+ print(_bridge_name)
+ print(r)
+## for i in r:
+## print(i.name)
+## add_system = BridgeRules(
+ r.bridge_name = _bridge_name
+ r.system_name = _system_name
+ r.ts = _ts
+ r.tg = _tg
+ r.active = _active
+ r.timeout = _timeout
+ r.to_type = _to_type
+ r.on = _on
+ r.off = _off
+ r.reset = _reset
+ r.server = _server
+ r.public_list = _public_list
+ r.proxy = is_proxy
+## db.session.add(add_system)
+ db.session.commit()
+
+ def delete_system_bridge(_name, _server):
+ dr = BridgeRules.query.filter_by(server=_server).filter_by(bridge_name=_name).all()
+ for i in dr:
+ db.session.delete(i)
+ db.session.commit()
+
+ def delete_system_rule(_name, _server, _system):
+ dr = BridgeRules.query.filter_by(server=_server).filter_by(bridge_name=_name).filter_by(system_name=_system).first()
+ db.session.delete(dr)
+ db.session.commit()
+
+
+ def server_edit(_name, _secret, _ip, _public_list, _port, _global_path, _global_ping_time, _global_max_missed, _global_use_acl, _global_reg_acl, _global_sub_acl, _global_tg1_acl, _global_tg2_acl, _ai_subscriber_file, _ai_try_download, _ai_path, _ai_peer_file, _ai_tgid_file, _ai_peer_url, _ai_subs_url, _ai_stale, _um_shorten_passphrase, _um_burn_file, _report_enable, _report_interval, _report_port, _report_clients, _unit_time, _notes):
+ s = ServerList.query.filter_by(name=_name).first()
+ # print(_name)
+ if _secret == '':
+ s.secret = s.secret
+ else:
+ s.secret = hashlib.sha256(_secret.encode()).hexdigest()
+ s.public_list = _public_list
+ s.ip = _ip
+ s.port = _port
+ s.global_path =_global_path
+ s.global_ping_time = _global_ping_time
+ s.global_max_missed = _global_max_missed
+ s.global_use_acl = _global_use_acl
+ s.global_reg_acl = _global_reg_acl
+ s.global_sub_acl = _global_sub_acl
+ s.global_tg1_acl = _global_tg1_acl
+ s.global_tg2_acl = _global_tg2_acl
+ s.ai_try_download = _ai_try_download
+ s.ai_path = _ai_path
+ s.ai_peer_file = _ai_peer_file
+ s.ai_subscriber_file = _ai_subscriber_file
+ s.ai_tgid_file = _ai_tgid_file
+ s.ai_peer_url = _ai_peer_url
+ s.ai_subs_url = _ai_subs_url
+ s.ai_stale = _ai_stale
+ # Pull from config file for now
+## um_append_int = db.Column(db.Integer(), primary_key=False, server_default='2')
+ s.um_shorten_passphrase = _um_shorten_passphrase
+ s.um_burn_file = _um_burn_file
+ # Pull from config file for now
+## um_burn_int = db.Column(db.Integer(), primary_key=False, server_default='6')
+ s.report_enable = _report_enable
+ s.report_interval = _report_interval
+ s.report_port = _report_port
+ s.report_clients = _report_clients
+ s.unit_time = int(_unit_time)
+ s.notes = _notes
+ db.session.commit()
+
+ def master_delete(_mode, _server, _name):
+ if _mode == 'MASTER':
+ m = MasterList.query.filter_by(server=_server).filter_by(name=_name).first()
+ if _mode == 'PROXY':
+ m = ProxyList.query.filter_by(server=_server).filter_by(name=_name).first()
+ if _mode == 'OBP':
+ m = OBP.query.filter_by(server=_server).filter_by(name=_name).first()
+ dr = BridgeRules.query.filter_by(server=_server).filter_by(system_name=_name).all()
+ for d in dr:
+ db.session.delete(d)
+ db.session.delete(m)
+ db.session.commit()
+
+ def edit_master(_mode, _name, _server, _static_positions, _repeat, _active, _max_peers, _ip, _port, _enable_um, _passphrase, _group_hang_time, _use_acl, _reg_acl, _sub_acl, _tg1_acl, _tg2_acl, _enable_unit, _notes, _external_proxy, _int_start_port, _int_stop_port, _network_id, _target_ip, _target_port, _both_slots, _public):
+## print(_mode)
+#### print(_server)
+## print(_name)
+ if _mode == 'MASTER':
+## print(_name)
+ m = MasterList.query.filter_by(server=_server).filter_by(name=_name).first()
+## m.name = _name,
+ m.static_positions = _static_positions
+ m.repeat = _repeat
+ m.active = _active
+ m.max_peers = int(_max_peers)
+ m.ip = _ip
+ m.port = int(_port)
+ m.enable_um = _enable_um
+ m.passphrase = str(_passphrase)
+ m.group_hang_time = int(_group_hang_time)
+ m.use_acl = _use_acl
+ m.reg_acl = _reg_acl
+ m.sub_acl = _sub_acl
+ m.tg1_acl = _tg1_acl
+ m.tg2_acl = _tg2_acl
+ m.enable_unit = _enable_unit
+## m.server = _server
+ m.notes = _notes
+ m.public_list = _public
+ db.session.commit()
+ if _mode == 'OBP':
+ # print(_enable_unit)
+## print(enable_unit)
+ o = OBP.query.filter_by(server=_server).filter_by(name=_name).first()
+ o.enabled = _active
+ o.network_id = _network_id
+ o.ip = _ip
+ o.port = _port
+ o.passphrase = _passphrase
+ o.target_ip = _target_ip
+ o.target_port = _target_port
+ o.both_slots = _both_slots
+ o.use_acl = _use_acl
+ o.sub_acl = _sub_acl
+ o.tg1_acl = _tg1_acl
+ o.tg2_acl = _tg2_acl
+ o.enable_unit = _enable_unit
+ o.notes = _notes
+ db.session.commit()
+ if _mode == 'PROXY':
+## print(_int_start_port)
+## print(_int_stop_port)
+ p = ProxyList.query.filter_by(server=_server).filter_by(name=_name).first()
+ p.name = _name
+ p.static_positions = _static_positions
+ p.repeat = _repeat
+ p.active = _active
+ p.enable_um = _enable_um
+ p.passphrase = _passphrase
+ p.external_proxy = _external_proxy
+ external_port = int(_port)
+ p.group_hang_time = int(_group_hang_time)
+ p.internal_start_port = _int_start_port
+ p.internal_stop_port = _int_stop_port
+ p.use_acl = _use_acl
+ p.reg_acl = _reg_acl
+ p.sub_acl = _sub_acl
+ p.tg1_acl = _tg1_acl
+ p.tg2_acl = _tg2_acl
+ p.enable_unit = _enable_unit
+ p.server = _server
+ p.notes = _notes
+ p.public_list = _public
+ db.session.commit()
+## add_proxy = ProxyList(
+## name = _name,
+## static_positions = _static_positions,
+## repeat = _repeat,
+## active = _active,
+## enable_um = _enable_um,
+## passphrase = _passphrase,
+## external_proxy = _external_proxy,
+## group_hang_time = int(_group_hang_time),
+## internal_start_port = int(_int_start_port),
+## internal_stop_port = int(_int_stop_port),
+## use_acl = _use_acl,
+## reg_acl = _reg_acl,
+## sub_acl = _sub_acl,
+## tg1_acl = _tg1_acl,
+## tg2_acl = _tg2_acl,
+## enable_unit = _enable_unit,
+## server = _server,
+## notes = _notes
+## )
+## db.session.add(add_master)
+
+ def add_master(_mode, _name, _server, _static_positions, _repeat, _active, _max_peers, _ip, _port, _enable_um, _passphrase, _group_hang_time, _use_acl, _reg_acl, _sub_acl, _tg1_acl, _tg2_acl, _enable_unit, _notes, _external_proxy, _int_start_port, _int_stop_port, _network_id, _target_ip, _target_port, _both_slots, _public):
+ # print(_mode)
+ if _mode == 'MASTER':
+ add_master = MasterList(
+ name = _name,
+ static_positions = _static_positions,
+ repeat = _repeat,
+ active = _active,
+ max_peers = int(_max_peers),
+ ip = _ip,
+ port = int(_port),
+ enable_um = _enable_um,
+ passphrase = _passphrase,
+ group_hang_time = int(_group_hang_time),
+ use_acl = _use_acl,
+ reg_acl = _reg_acl,
+ sub_acl = _sub_acl,
+ tg1_acl = _tg1_acl,
+ tg2_acl = _tg2_acl,
+ enable_unit = _enable_unit,
+ server = _server,
+ notes = _notes,
+ public_list = _public
+ )
+ db.session.add(add_master)
+ db.session.commit()
+ if _mode == 'PROXY':
+ add_proxy = ProxyList(
+ name = _name,
+ static_positions = _static_positions,
+ repeat = _repeat,
+ active = _active,
+ enable_um = _enable_um,
+ passphrase = _passphrase,
+ external_proxy = _external_proxy,
+ external_port = int(_port),
+ group_hang_time = int(_group_hang_time),
+ internal_start_port = int(_int_start_port),
+ internal_stop_port = int(_int_stop_port),
+ use_acl = _use_acl,
+ reg_acl = _reg_acl,
+ sub_acl = _sub_acl,
+ tg1_acl = _tg1_acl,
+ tg2_acl = _tg2_acl,
+ enable_unit = _enable_unit,
+ server = _server,
+ notes = _notes,
+ public_list = _public
+ )
+ db.session.add(add_proxy)
+ db.session.commit()
+ if _mode == 'OBP':
+ # print(_name)
+ # print(_network_id)
+ add_OBP = OBP(
+ name = _name,
+ enabled = _active,
+ network_id = _network_id, #
+ ip = _ip,
+ port = _port,
+ passphrase = _passphrase,
+ target_ip = _target_ip,#
+ target_port = _target_port,#
+ both_slots = _both_slots,#
+ use_acl = _use_acl,
+ sub_acl = _sub_acl,
+ tg_acl = _tg1_acl,
+ enable_unit = _enable_unit,
+ server = _server,
+ notes = _notes,
+ )
+ db.session.add(add_OBP)
+ db.session.commit()
+
+
+ def server_add(_name, _secret, _ip, _port, _global_path, _global_ping_time, _global_max_missed, _global_use_acl, _global_reg_acl, _global_sub_acl, _global_tg1_acl, _global_tg2_acl, _ai_subscriber_file, _ai_try_download, _ai_path, _ai_peer_file, _ai_tgid_file, _ai_peer_url, _ai_subs_url, _ai_stale, _um_shorten_passphrase, _um_burn_file, _report_enable, _report_interval, _report_port, _report_clients, _unit_time, _notes):
+ add_server = ServerList(
+ name = _name,
+ secret = hashlib.sha256(_secret.encode()).hexdigest(),
+## public_list = _public_list,
+ ip = _ip,
+ port = _port,
+ global_path =_global_path,
+ global_ping_time = _global_ping_time,
+ global_max_missed = _global_max_missed,
+ global_use_acl = _global_use_acl,
+ global_reg_acl = _global_reg_acl,
+ global_sub_acl = _global_sub_acl,
+ global_tg1_acl = _global_tg1_acl,
+ global_tg2_acl = _global_tg2_acl,
+ ai_try_download = _ai_try_download,
+ ai_path = _ai_path,
+ ai_peer_file = _ai_peer_file,
+ ai_subscriber_file = _ai_subscriber_file,
+ ai_tgid_file = _ai_tgid_file,
+ ai_peer_url = _ai_peer_url,
+ ai_subs_url = _ai_subs_url,
+ ai_stale = _ai_stale,
+ # Pull from config file for now
+## um_append_int = db.Column(db.Integer(), primary_key=False, server_default='2')
+ um_shorten_passphrase = _um_shorten_passphrase,
+ um_burn_file = _um_burn_file,
+ # Pull from config file for now
+## um_burn_int = db.Column(db.Integer(), primary_key=False, server_default='6')
+ report_enable = _report_enable,
+ report_interval = _report_interval,
+ report_port = _report_port,
+ report_clients = _report_clients,
+ unit_time = int(_unit_time),
+ notes = _notes
+ )
+ db.session.add(add_server)
+ db.session.commit()
+ def peer_add(_mode, _name, _enabled, _loose, _ip, _port, _master_ip, _master_port, _passphrase, _callsign, _radio_id, _rx, _tx, _tx_power, _cc, _lat, _lon, _height, _loc, _desc, _slots, _url, _grp_hang, _xlx_mod, _opt, _use_acl, _sub_acl, _1_acl, _2_acl, _svr, _enable_unit, _notes):
+ if _mode == 'xlx':
+ xlx_peer_add = xlxPeer(
+ name = _name,
+ enabled = _enabled,
+ loose = _loose,
+ ip = _ip,
+ port = _port,
+ master_ip = _master_ip,
+ master_port = _master_port,
+ passphrase = _passphrase,
+ callsign = _callsign,
+ radio_id = _radio_id,
+ rx_freq = _rx,
+ tx_freq = _tx,
+ tx_power = _tx_power,
+ color_code = _cc,
+ latitude = _lat,
+ longitude = _lon,
+ height = _height,
+ location = _loc,
+ description = _desc,
+ slots = _slots,
+ xlxmodule = _xlx_mod,
+ url = _url,
+ enable_unit = _enable_unit,
+ group_hangtime = _grp_hang,
+ use_acl = _use_acl,
+ sub_acl = _sub_acl,
+ tg1_acl = _1_acl,
+ tg2_acl = _2_acl,
+ server = _svr,
+ notes = _notes
+ )
+ db.session.add(xlx_peer_add)
+ db.session.commit()
+ if _mode == 'mmdvm':
+ mmdvm_peer_add = mmdvmPeer(
+ name = _name,
+ enabled = _enabled,
+ loose = _loose,
+ ip = _ip,
+ port = _port,
+ master_ip = _master_ip,
+ master_port = _master_port,
+ passphrase = _passphrase,
+ callsign = _callsign,
+ radio_id = _radio_id,
+ rx_freq = _rx,
+ tx_freq = _tx,
+ tx_power = _tx_power,
+ color_code = _cc,
+ latitude = _lat,
+ longitude = _lon,
+ height = _height,
+ location = _loc,
+ description = _desc,
+ slots = _slots,
+ url = _url,
+ enable_unit = _enable_unit,
+ group_hangtime = _grp_hang,
+ use_acl = _use_acl,
+ sub_acl = _sub_acl,
+ tg1_acl = _1_acl,
+ tg2_acl = _2_acl,
+ server = _svr,
+ notes = _notes
+ )
+ db.session.add(mmdvm_peer_add)
+ db.session.commit()
+ def peer_edit(_mode, _server, _name, _enabled, _loose, _ip, _port, _master_ip, _master_port, _passphrase, _callsign, _radio_id, _rx, _tx, _tx_power, _cc, _lat, _lon, _height, _loc, _desc, _slots, _url, _grp_hang, _xlx_mod, _opt, _use_acl, _sub_acl, _1_acl, _2_acl, _enable_unit, _notes):
+## print(_mode)
+ if _mode == 'mmdvm':
+## print(_server)
+## print(_name)
+## print(_name)
+## s = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first()
+ p = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first()
+ p.enabled = _enabled
+ p.loose = _loose
+ p.ip = _ip
+ p.port = _port
+ p.master_ip = _master_ip
+ p.master_port = _master_port
+ p.passphrase = _passphrase
+ p.callsign = _callsign
+ p.radio_id = _radio_id
+ p.rx_freq = _rx
+ p.tx_freq = _tx
+ p.tx_power = _tx_power
+ p.color_code = _cc
+ p.latitude = _lat
+ p.longitude = _lon
+ p.height = _height
+ p.location = _loc
+ p.description = _desc
+ p.slots = _slots
+ p.url = _url
+ p.enable_unit = _enable_unit
+ p.group_hangtime = _grp_hang
+ p.options = _opt
+ p.use_acl = _use_acl
+ p.sub_acl = _sub_acl
+ p.tg1_acl = _1_acl
+ p.tg2_acl = _2_acl
+ p.notes = _notes
+ if _mode == 'xlx':
+## print(type(_server))
+## print(type(_name))
+## print(type(_enabled))
+## print((_enable_unit))
+## print(type(_use_acl))
+#### print(_port)
+
+
+## s = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first()
+ p = xlxPeer.query.filter_by(server=_server).filter_by(name=_name).first()
+ # print(type(p.enable_unit))
+ p.enabled = _enabled
+ p.loose = _loose
+ p.ip = _ip
+ p.port = _port
+ p.master_ip = _master_ip
+ p.master_port = _master_port
+ p.passphrase = _passphrase
+ p.callsign = _callsign
+ p.radio_id = _radio_id
+ p.rx_freq = _rx
+ p.tx_freq = _tx
+ p.tx_power = _tx_power
+ p.color_code = _cc
+ p.latitude = _lat
+ p.longitude = _lon
+ p.height = _height
+ p.location = _loc
+ p.description = _desc
+ p.slots = _slots
+ p.url = _url
+ p.options = _opt
+ p.enable_unit = _enable_unit
+ p.xlxmodule = _xlx_mod
+ p.group_hangtime = _grp_hang
+ p.use_acl = _use_acl
+ p.sub_acl = _sub_acl
+ p.tg1_acl = _1_acl
+ p.tg2_acl = _2_acl
+ p.notes = _notes
+ db.session.commit()
+
+
+
+
+# Test server configs
+
+ @app.route('/manage_servers', methods=['POST', 'GET'])
+ @login_required
+ @roles_required('Admin')
+ def edit_server_db():
+ # Edit server
+ if request.args.get('save_mode'):# == 'new' and request.form.get('server_name'):
+ _port = int(request.form.get('server_port'))
+ _global_ping_time = int(request.form.get('ping_time'))
+ _global_max_missed = int(request.form.get('max_missed'))
+ _ai_stale = int(request.form.get('stale_days'))
+ _report_interval = int(request.form.get('report_interval'))
+ _report_port = int(request.form.get('report_port'))
+ if request.form.get('use_acl') == 'True':
+ _global_use_acl = True
+ if request.form.get('aliases_enabled') == 'True':
+ _ai_try_download = True
+ if request.form.get('um_shorten_passphrase') == 'True':
+ _um_shorten_passphrase = True
+ if request.form.get('report') == 'True':
+ _report_enabled = True
+## if request.form.get('public_list') == 'True':
+## public_list = True
+ else:
+ _global_use_acl = False
+ _ai_try_download = False
+ _um_shorten_passphrase = False
+ _report_enabled = False
+## public_list = False
+
+ if request.args.get('save_mode') == 'new':
+ if request.form.get('server_name') == '':
+ content = '''Server can't have blank name.
+Server saved.
+ Server changed.
+Server deleted.
+
+''' + # Add new server + elif request.args.get('add'): # == 'yes': + content = ''' + +
+''' + else: + all_s = ServerList.query.all() + p_list = ''' +
| Add Server Config | +
+ +
Name |
+Notes |
+
+'''
+ for s in all_s:
+ p_list = p_list + '''
+
| ''' + str(s.name) + ''' | +''' + s.notes + ''' | +
Redirecting in 3 seconds.
+''' + else: + if request.args.get('save_mode') == 'mmdvm_peer': + peer_add('mmdvm', request.form.get('name_text'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), 'MMDVM', request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), request.form.get('server'), unit_enabled, request.form.get('notes')) + content = '''Redirecting in 3 seconds.
+ ''' + if request.args.get('save_mode') == 'xlx_peer': + peer_add('xlx', request.form.get('name_text'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), request.form.get('xlxmodule'), request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), request.form.get('server'), unit_enabled, request.form.get('notes')) + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('add') == 'mmdvm' or request.args.get('add') == 'xlx': + s = ServerList.query.all() + if request.args.get('add') == 'mmdvm': + mode = 'MMDVM' + submit_link = 'manage_peers?save_mode=mmdvm_peer' + xlx_module = '' + if request.args.get('add') == 'xlx': + xlx_module = ''' ++
Redirecting in 3 seconds.
+''' + elif request.args.get('edit_mmdvm') == 'save' or request.args.get('edit_xlx') == 'save': + peer_enabled = False + use_acl = False + peer_loose = True + unit_enabled = False + if request.form.get('enabled') == 'true': + peer_enabled = True +## if request.form.get('loose') == 'true': +## peer_loose = True + if request.form.get('use_acl') == 'True': + use_acl = True + if request.form.get('enable_unit') == 'True': + unit_enabled = True +## else: +## peer_loose = False +## print((unit_enabled)) +## print(type(peer_enabled)) +## print(type(use_acl)) + if request.args.get('edit_mmdvm') == 'save': + peer_edit('mmdvm', request.args.get('server'), request.args.get('name'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), 'MMDVM', request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), unit_enabled, request.form.get('notes')) + content = '''Redirecting in 3 seconds.
+''' + if request.args.get('edit_xlx') == 'save': + peer_edit('xlx', request.args.get('server'), request.args.get('name'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), request.form.get('xlxmodule'), request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), unit_enabled, request.form.get('notes')) + content = '''Redirecting in 3 seconds.
+''' + elif request.args.get('server') and request.args.get('peer_name') and request.args.get('mode'): # and request.args.get('edit_peer') and request.args.get('mode') == 'mmdvm': + if request.args.get('mode') == 'mmdvm': + p = mmdvmPeer.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('peer_name')).first() + xlx_module = '' + mode = "MMDVM" + form_submit = ''' + ++''' + else: + all_s = ServerList.query.all() + p_list = '' + for s in all_s: + # print(s.name) + p_list = p_list + ''' +
| Name | +Mode | +Notes | + +
| ''' + str(p.name) + ''' | +MMDVM | +''' + p.notes + ''' | + +
| ''' + str(x.name) + ''' | +XLX | +''' + x.notes + ''' | + +
| Add MMDVM peer | +Add XLX peer | +
+ +''' + p_list + + return render_template('flask_user_layout.html', markup_content = Markup(content)) + + + @app.route('/manage_masters', methods=['POST', 'GET']) + @login_required + @roles_required('Admin') + def manage_masters(): + #PROXY + if request.args.get('proxy_save'): + active = False + use_acl = False + enable_unit = False + repeat = True + aprs_pos = False + enable_um = True + external_proxy = False + public = False + if request.form.get('enable_um') == 'False': + enable_um = False + if request.form.get('aprs_pos') == 'True': + aprs_pos = True + if request.form.get('enabled') == 'True': + active = True + if request.form.get('use_acl') == 'True': + use_acl = True + if request.form.get('enable_unit') == 'True': + enable_unit = True + if request.form.get('repeat') == 'False': + repeat = False + if request.form.get('external_proxy') == 'True': + external_proxy = True + if request.form.get('public_list') == 'True': + public = True + if request.args.get('proxy_save') == 'add': + if request.form.get('name_text') == '': + content = '''
Redirecting in 3 seconds.
+''' + else: + add_master('PROXY', request.form.get('name_text'), request.form.get('server'), aprs_pos, repeat, active, 0, request.form.get('ip'), request.form.get('external_port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), external_proxy, request.form.get('int_port_start'), request.form.get('int_port_stop'), '', '', '', '', public) + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('proxy_save') == 'edit': +## print(request.args.get('name')) + edit_master('PROXY', request.args.get('name'), request.args.get('server'), aprs_pos, repeat, active, 0, request.form.get('ip'), request.form.get('external_port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), external_proxy, request.form.get('int_port_start'), request.form.get('int_port_stop'), '', '', '', '', public) + content = '''Redirecting in 3 seconds.
+''' + elif request.args.get('proxy_save') == 'delete': + master_delete('PROXY', request.args.get('server'), request.args.get('name')) + content = '''Redirecting in 3 seconds.
+''' + # OBP + elif request.args.get('OBP_save'): + enabled = False + use_acl = False + enable_unit = False + both_slots = True + if request.form.get('enabled') == 'True': + enabled = True + if request.form.get('use_acl') == 'True': + use_acl = True + if request.form.get('enable_unit') == 'True': + enable_unit = True + if request.form.get('both_slots') == 'False': + both_slots = False + if request.args.get('OBP_save') == 'add': + if request.form.get('name_text') == '': + content = '''Redirecting in 3 seconds.
+''' + else: + add_master('OBP', request.form.get('name_text'), request.form.get('server'), '', '', enabled, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), '', request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('tg_acl'), '', enable_unit, request.form.get('notes'), '', '', '', request.form.get('network_id'), request.form.get('target_ip'), request.form.get('target_port'), both_slots, '') + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('OBP_save') == 'edit': + edit_master('OBP', request.args.get('name'), request.args.get('server'), '', '', enabled, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), '', request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('tg_acl'), '', enable_unit, request.form.get('notes'), '', '', '', request.form.get('network_id'), request.form.get('target_ip'), request.form.get('target_port'), both_slots, '') + content = '''Redirecting in 3 seconds.
+''' + elif request.args.get('OBP_save') == 'delete': + master_delete('OBP', request.args.get('server'), request.args.get('name')) + content = '''Redirecting in 3 seconds.
+''' + # MASTER + elif request.args.get('master_save'): + aprs_pos = False + repeat = False + active = False + use_acl = False + enable_um = False + enable_unit = False + public = False + if request.form.get('aprs_pos') == 'True': + aprs_pos = True + if request.form.get('repeat') == 'True': + repeat = True + if request.form.get('enabled') == 'True': + active = True + if request.form.get('use_acl') == 'True': + use_acl = True + if request.form.get('enable_um') == 'True': + enable_um = True + if request.form.get('enable_unit') == 'True': + enable_unit = True + if request.form.get('public_list') == 'True': + public = True + if request.args.get('master_save') == 'add': + if request.form.get('name_text') == '': + content = '''Redirecting in 3 seconds.
+''' + else: + add_master('MASTER', request.form.get('name_text'), request.form.get('server'), aprs_pos, repeat, active, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), '', '', '', '', '', '', '', public) + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('master_save') == 'edit': + edit_master('MASTER', request.args.get('name'), request.args.get('server'), aprs_pos, repeat, active, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), '', '', '', '', '', '', '', public) + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('master_save') == 'delete': + master_delete('MASTER', request.args.get('server'), request.args.get('name')) + content = '''Redirecting in 3 seconds.
+''' + elif request.args.get('add_OBP'): + s = ServerList.query.all() + server_options = '' + for i in s: + server_options = server_options + '''\n''' + content = ''' ++ +
+ + +
+ +''' + elif request.args.get('edit_proxy'): + # print(request.args.get('server')) + # print(request.args.get('edit_proxy')) + p = ProxyList.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('edit_proxy')).first() + content = ''' +
+ +
+''' + + elif request.args.get('add_proxy'): + s = ServerList.query.all() + server_options = '' + for i in s: + server_options = server_options + '''\n''' + content = ''' +
+ +
+ + +
+''' + + + elif request.args.get('add_master'): + s = ServerList.query.all() + server_options = '' + for i in s: + server_options = server_options + '''\n''' + + content = ''' +
+
+ +
+''' + elif request.args.get('edit_OBP'): +## print(request.args.get('server')) +## print(request.args.get('edit_OBP')) +## s = ServerList.query.all() + o = OBP.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('edit_OBP')).first() +## print(o.notes) + content = ''' +
+
+ + ''' + + elif request.args.get('edit_master'): +## s = ServerList.query.all() + m = MasterList.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('edit_master')).first() + + content = ''' +
+
+''' +## elif not request.args.get('edit_master') and not request.args.get('edit_OBP') and not request.args.get('add_OBP') and not request.args.get('add_master'): +## content = 'jglkdjklsd' + else: + #elif not request.args.get('add_proxy') or not request.args.get('add_OBP') or not request.args.get('add_master'): # or not request.args.get('proxy_save') or not request.args.get('master_save') or not request.args.get('OBP_save'): + all_s = ServerList.query.all() + m_list = '' + for s in all_s: +## print(s.name) + m_list = m_list + ''' +
| Name | +Mode | +Notes | + +
| ''' + str(o.name) + ''' | +OpenBridge | +''' + str(o.notes) + ''' | + +
| ''' + str(p.name) + ''' | +PROXY | +''' + str(p.notes) + ''' | + +
| ''' + str(x.name) + ''' | +MASTER | +''' + str(x.notes) + ''' | + +
| Add MASTER | +Add PROXY | +Add OpenBridge | + +
+ +''' + m_list + + return render_template('flask_user_layout.html', markup_content = Markup(content)) + + + @app.route('/add_user', methods=['POST', 'GET']) + @login_required + @roles_required('Admin') + def add_admin(): + if request.method == 'GET': + content = ''' +
+''' + elif request.method == 'POST' and request.form.get('username'): + if not User.query.filter(User.username == request.form.get('username')).first(): + radioid_data = ast.literal_eval(get_ids(request.form.get('username'))) + user = User( + username=request.form.get('username'), + email=request.form.get('email'), + email_confirmed_at=datetime.datetime.utcnow(), + password=user_manager.hash_password(request.form.get('password')), + dmr_ids = str(radioid_data[0]), + initial_admin_approved = True, + first_name = str(radioid_data[1]), + last_name = str(radioid_data[2]), + city = str(radioid_data[3]) + + ) + + db.session.add(user) + u = User.query.filter_by(username=request.form.get('username')).first() + user_role = UserRoles( + user_id=u.id, + role_id=2, + ) + db.session.add(user_role) + db.session.commit() + content = '''
Created user: ''' + str(request.form.get('username')) + '''
\n''' + elif User.query.filter(User.username == request.form.get('username')).first(): + content = 'Existing user: ' + str(request.form.get('username') + '. New user not created.') + + return render_template('flask_user_layout.html', markup_content = Markup(content)) + + @app.route('/manage_rules', methods=['POST', 'GET']) + @login_required + @roles_required('Admin') + def manage_rules(): + + if request.args.get('save_bridge') == 'save': + public = False + if request.form.get('public_list') == 'True': + public = True + if request.form.get('bridge_name') == '': + content = '''Redirecting in 3 seconds.
+''' + else: + bridge_add(request.form.get('bridge_name'), request.form.get('description'), public, request.form.get('tg')) + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('save_bridge') == 'edit': + public = False + if request.form.get('public_list') == 'True': + public = True + update_bridge_list(request.args.get('bridge'), request.form.get('description'), public, request.form.get('bridge_name'), request.form.get('tg')) + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('save_bridge') == 'delete': + bridge_delete(request.args.get('bridge')) + content = '''Redirecting in 3 seconds.
+ ''' + + + #Rules + elif request.args.get('save_rule'): + public_list = False + active = False + if request.form.get('active_dropdown') == 'True': + active = True + if request.args.get('save_rule') == 'new': + add_system_rule(request.form.get('bridge_dropdown'), request.form.get('system_text'), request.form.get('ts_dropdown'), request.form.get('tgid'), active, request.form.get('timer_time'), request.form.get('type_dropdown'), request.form.get('on'), request.form.get('off'), request.form.get('reset'), request.args.get('server'), public_list) + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('save_rule') == 'edit': + content = '''Redirecting in 3 seconds.
+ ''' + elif request.args.get('save_rule') == 'delete': + # print(request.args.get('bridge')) + # print(request.args.get('server')) + if request.args.get('system'): + delete_system_rule(request.args.get('bridge'), request.args.get('server'), request.args.get('system')) + else: + delete_system_bridge(request.args.get('bridge'), request.args.get('server')) + +## delete_system_rule(request.args.get('bridge'), request.args.get('server'), request.args.get('system')) + content = '''Redirecting in 3 seconds.
+ ''' + + elif request.args.get('add_rule'): +## svl = ServerList.query.all() + bl = BridgeList.query.all() #filter(bridge_name== request.form.get('username')).all() + all_o = OBP.query.filter_by(server=request.args.get('add_rule')).all() + all_m = MasterList.query.filter_by(server=request.args.get('add_rule')).all() + all_p = ProxyList.query.filter_by(server=request.args.get('add_rule')).all() + m_l = mmdvmPeer.query.filter_by(server=request.args.get('add_rule')).all() + x_l = xlxPeer.query.filter_by(server=request.args.get('add_rule')).all() +## print(sl) +## print(bl) +## svl_option = '' + bl_option = '' + sl_option = '' + for i in all_o: + sl_option = sl_option + '''''' + for i in all_m: + sl_option = sl_option + '''''' + for i in all_p: + sl_option = sl_option + '''''' + for i in m_l: + sl_option = sl_option + '''''' + for i in x_l: + sl_option = sl_option + '''''' + for i in bl: + bl_option = bl_option + '''''' + content = ''' ++ +''' + elif request.args.get('edit_rule') and request.args.get('bridge'): + br = BridgeRules.query.filter_by(server=request.args.get('edit_rule')).filter_by(bridge_name=request.args.get('bridge')).all() + print(br) + br_view = '''
| Delete SYSTEM Rule | +
|
+ + |
+
+ +''' + content = br_view + + elif request.args.get('edit_rule') == 'save' and request.args.get('bridge_edit'): + public_list = False + active = False + if request.form.get('active_dropdown') == 'True': + active = True + edit_system_rule(request.args.get('bridge_edit'), request.args.get('system'), request.form.get('ts_dropdown'), request.form.get('tgid'), active, request.form.get('timer_time'), request.form.get('type_dropdown'), request.form.get('on'), request.form.get('off'), request.form.get('reset'), request.args.get('server'), public_list) + content = '''
Redirecting in 3 seconds.
+ ''' + + elif request.args.get('add_bridge'): + s = ServerList.query.all() +## server_options = '' +## for i in s: +## server_options = server_options + '''\n''' + + content = ''' ++
+
+ + +''' + else: + all_b = BridgeList.query.all() + s = ServerList.query.all() + b_list = ''' +
| Add Bridge | + +
+ +
| Name | +Public | +Description | +TGID | + +
| ''' + str(i.bridge_name) + ''' + | ''' + str(i.public_list) + ''' | +''' + str(i.description) + ''' | +''' + str(i.tg) + ''' | + +
| Add a rule to server: ''' + str(i.name) + ''' | +
| Bridge Name | +- | +- | +
| ''' + str(x.bridge_name) + ''' | +Edit Bridge Rules | +Delete Bridge from this server | +
''' + content = b_list + r_list + '''''' + + return render_template('flask_user_layout.html', markup_content = Markup(content)) + + @app.route('/svr', methods=['POST']) + def auth(): + hblink_req = request.json + # print((hblink_req)) + if hblink_req['secret'] in shared_secrets(): + if 'login_id' in hblink_req and 'login_confirmed' not in hblink_req: + if type(hblink_req['login_id']) == int: + if authorized_peer(hblink_req['login_id'])[0]: + print(active_tgs) + if isinstance(authorized_peer(hblink_req['login_id'])[1], int) == True: + authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], gen_passphrase(hblink_req['login_id']), 'Attempt') +## active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] + response = jsonify( + allow=True, + mode='normal', + ) + elif authorized_peer(hblink_req['login_id'])[1] == '': + authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], 'Config Passphrase: ' + legacy_passphrase, 'Attempt') +## active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] + response = jsonify( + allow=True, + mode='legacy', + ) + elif authorized_peer(hblink_req['login_id'])[1] != '' or isinstance(authorized_peer(hblink_req['login_id'])[1], int) == False: + authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], authorized_peer(hblink_req['login_id'])[1], 'Attempt') +## active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] + # print(authorized_peer(hblink_req['login_id'])) + response = jsonify( + allow=True, + mode='override', + value=authorized_peer(hblink_req['login_id'])[1] + ) + try: + active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] +## print('Restart ' + hblink_req['login_server'] + ' please.') + except: +## active_tgs[hblink_req['login_server']] = {} + pass + elif authorized_peer(hblink_req['login_id'])[0] == False: +## print('log fail') + authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], 'Not Registered', '-', 'Failed') + response = jsonify( + allow=False) + elif not type(hblink_req['login_id']) == int: + user = hblink_req['login_id'] + u = User.query.filter_by(username=user).first() + + if not u: + msg = jsonify(auth=False, + reason='User not found') + response = make_response(msg, 401) + if u: + u_role = UserRoles.query.filter_by(user_id=u.id).first() + password = user_manager.verify_password(hblink_req['password'], u.password) + if u_role.role_id == 2: + role = 'user' + if u_role.role_id == 1: + role = 'admin' + if password: + response = jsonify(auth=True, role=role) + else: + msg = jsonify(auth=False, + reason='Incorrect password') + response = make_response(msg, 401) + elif 'login_id' in hblink_req and 'login_confirmed' in hblink_req: + if hblink_req['old_auth'] == True: + authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], 'CONFIG, NO UMS', 'Confirmed') + else: + authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], 'USER MANAGER', 'Confirmed') + response = jsonify( + logged=True + ) + elif 'burn_list' in hblink_req: # ['burn_list']: # == 'burn_list': + response = jsonify( + burn_list=get_burnlist() + ) + + elif 'get_config' in hblink_req: + if hblink_req['get_config']: + active_tgs[hblink_req['get_config']] = {} + print(active_tgs) + ## try: +## print(get_peer_configs(hblink_req['get_config'])) + response = jsonify( + config=server_get(hblink_req['get_config']), + peers=get_peer_configs(hblink_req['get_config']), + masters=masters_get(hblink_req['get_config']), + ## OBP=get_OBP(hblink_req['get_config']) + + ) + ## except: + ## message = jsonify(message='Config error') + ## response = make_response(message, 401) + elif 'get_rules' in hblink_req: + if hblink_req['get_rules']: # == 'burn_list': + + ## try: + response = jsonify( + rules=generate_rules(hblink_req['get_rules']), + ## OBP=get_OBP(hblink_req['get_config']) + + ) + ## except: + ## message = jsonify(message='Config error') + ## response = make_response(message, 401) + elif 'update_tg' in hblink_req: + if hblink_req['update_tg']: + print(hblink_req) +## print(hblink_req['data'][0]['SYSTEM']) + if 'on' == hblink_req['mode']: +## try: + if hblink_req['dmr_id'] == 0: + print('id 0') +## print(active_tgs) + for system in active_tgs[hblink_req['update_tg']].items(): + ## print(system) + ## print('sys') + if system[0] == hblink_req['data'][0]['SYSTEM']: + print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1']) +## print(hblink_req['data'][2]['tg']) + print('---------') + print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2']) + ## print(hblink_req['data'][1]['ts']) + if hblink_req['data'][1]['ts'] == 1: + #### print(active_tgs[hblink_req['update_tg']][system[0]][0]['1']) + + if active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'] == hblink_req['data'][2]['tg']: + pass + else: + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'].append(hblink_req['data'][2]['tg']) + #### active_tgs[hblink_req['update_tg']][system[0]][0]['1'].append(0) + if hblink_req['data'][1]['ts'] == 2: + if active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'] == hblink_req['data'][2]['tg']: + pass + #### print(active_tgs[hblink_req['update_tg']][system[0]][1]['2']) + else: + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'].append(hblink_req['data'][2]['tg']) + else: + try: + print('---------on------------') + print(hblink_req['data']) + print(active_tgs[hblink_req['update_tg']]) + print(hblink_req['data'][2]['ts2']) + print('-----------------------') + ## active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['SYSTEM'] = hblink_req['data'][0]['SYSTEM'] + #### active_tgs[hblink_req['update_tg']][hblink_req['dmr_id']].update({hblink_req['data'][0]['SYSTEM']: [{1:[hblink_req['data'][1]['ts1']]}, {2:[hblink_req['data'][2]['ts2']]}]}) #.update({[hblink_req['dmr_id']]:hblink_req['data']}) + if hblink_req['data'][1]['ts1'] not in active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1']: + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'].append(hblink_req['data'][1]['ts1']) + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['SYSTEM'] = hblink_req['data'][0]['SYSTEM'] + if hblink_req['data'][2]['ts2'] not in active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2']: + print('---0---') + print(hblink_req['data'][0]['SYSTEM']) + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['SYSTEM'] = hblink_req['data'][0]['SYSTEM'] + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'].append(hblink_req['data'][2]['ts2']) +## print('append') + #### active_tgs[hblink_req['update_tg']][system[0]][1]['2'].append(0) + ## print(hblink_req['data'][0]['SYSTEM']) + + ## print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']]) + ## print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['2']) + ## print(hblink_req['data'][1]['ts2']) + ## print(active_tgs[hblink_req['update_tg']]) + except: +## active_tgs[hblink_req['update_tg']] = {} + pass + +## except: +## pass + + + elif 'off' == hblink_req['mode']: + print('off') + for system in active_tgs[hblink_req['update_tg']].items(): + print(system) + if system[0] == hblink_req['data'][0]['SYSTEM']: + print('yes it is') +#### print(system[0]) +#### print(active_tgs[hblink_req['update_tg']][system[0]]) + if hblink_req['data'][1]['ts'] == 1: +#### print(active_tgs[hblink_req['update_tg']][system[0]][0]['1']) + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'].remove(hblink_req['data'][2]['tg']) +#### active_tgs[hblink_req['update_tg']][system[0]][0]['1'].append(0) + if hblink_req['data'][1]['ts'] == 2: +#### print(active_tgs[hblink_req['update_tg']][system[0]][1]['2']) + active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'].remove(hblink_req['data'][2]['tg']) +#### active_tgs[hblink_req['update_tg']][system[0]][1]['2'].append(0) + + + +## print() +## print(system) +## print(system[1][2]['SYSTEM']) +## print('off') +## print(hblink_req['data'][1]['ts']) +## print(hblink_req['data'][2]['tg']) + print(active_tgs) + response = 'got it' + else: + message = jsonify(message='Authentication error') + response = make_response(message, 401) + return response + + + + return app + + +if __name__ == '__main__': + app = create_app() + app.run(debug = True, port=hws_port, host=hws_host) diff --git a/web/config-SAMPLE.py b/web/config-SAMPLE.py new file mode 100644 index 0000000..db58108 --- /dev/null +++ b/web/config-SAMPLE.py @@ -0,0 +1,87 @@ + +''' +Settings for HBNet Web Server. +''' +# Database options +# Using SQLite is simple and easiest. Comment out this line and uncomment the MySQL +# line to use a MySQL/MariaDB server. +db_location = 'sqlite:///hbnet.sqlite' + +# Uncomment and change this line to use a MySQL DB. It is best to start with a fresh +# DB without data in it. + +#db_location = 'mysql+pymysql://DB_USERNAME:DB_PASSWORD@DB_HOST:MySQL_PORT/DB_NAME' + + +# Title of the HBNet Web Server +title = 'HBNet DMR server' +# Port to run server +hws_port = 8080 +# IP to run server on +hws_host = '127.0.0.1' +# Publicly accessible URL of the web server. THIS IS REQUIRED AND MUST BE CORRECT. +url = 'http://localhost:8080' +# Replace below with some random string such as an SHA256 +secret_key = 'SUPER SECRET LONG KEY' + +# Default state for newly created user accounts. Setting to False will require +# the approval of an admin user before the user can login. +default_account_state = True + +# Legacy passphrase used in hblink.cfg +#legacy_passphrase = 'passw0rd' + + +# Passphrase calculation config. If REMOTE_CONFIG is not used in your DMR server config +# (hblink.cfg), then the values in section [USER_MANAGER] MUST match the values below. +# If REMOTE_CONFIG is enabled, the DMR server (hblink) will automatically use the values below. +# These config options affect the generation of user passphrases. + +# Set to a value between 1 - 99. This value is used in the normal calculation. +append_int = 1 + +# Set to a value between 1 - 99. This value is used for compromised passphrases. +burn_int = 5 + +# Set to a value between 1 - 99 This value is used in the normal calculation. +extra_int_1 = 5 + +# Set to a value between 1 - 99 This value is used in the normal calculation. +extra_int_2 = 8 + +# Set to a length of about 10 characters. +extra_1 = 'TeSt' +extra_2 = 'DmR4' + +# Shorten generated passphrases +use_short_passphrase = True + +# Character length of shortened passphrase +shorten_length = 6 +# How often to pick character from long passphrase when shortening. +shorten_sample = 4 + +# Email settings +MAIL_SERVER = 'smtp.gmail.com' +MAIL_PORT = 465 +MAIL_USE_SSL = True +MAIL_USE_TLS = False +MAIL_USERNAME = 'app@gmail.com' +MAIL_PASSWORD = 'password' +MAIL_DEFAULT_SENDER = '"' + title + '"
h9s@
z3;y3M>i;Gu{YM(819M?+zW=aq0CD}}(;r0V2>lHeBjay!k+iV-WB=SOydc)U_YlDG
zk4IK^7S6U1fW`kz;eU-o|BLhx6BFkbmk<%<5#<*FVp;$q!Xs{BCB`EpE@EN9FDNKx
zA@WCr{H5I;X5-;);RaE#1%e3JXCP&N?=$1Q-%)-4-+l46g8)Is!!IVu&(HkF_A*NI
z-4gY`mM48nWm;O2{{%q#mg*$Iw;^rd>EhxDg}D7YF#iga{|C8$%>Oee|98{>DfY)~
zC76pZKn8XmI^NFzt@-~#@DB}|P%DVDJM6!6{huO#!19mt4Dk7nG2om8j$OXL&fP!5
z;x=>s5B~Wx-2M+*0960S$o~l6|G@Phxc)~7{EvkH6J7s->wkp6|48^h(e?isT)2OC
z?ht37(DMfR^6X1Ne4wYtw0xkZ3_=B2gIX6tDeQm|92a#{cMyn|<@O(nPobO_Fo@*=
z)>6S*!NeiI%k#6X{Sx>w0tl?EVCXZoHS3#5XB%*H;q4hKYW?`26+TOCMa!p7xk$zM
z2~!c()ad~OM=1Jho(@S-oJDrAIOWjg)lBX-A7NrkEG(r!)=#J`IWxrS)z%oOVmJ%&
zI9=3^Vo&$Oy5BZz1n%55DlCQeeXsT8*&UY%@MDc`dYe;OxiURpEXxQ+u>Sx3FK6}_
zrnrJj9HsIw*Z1Y|N0bEoOLo7Vw)twA%en&m%)<9EIjvE;Y6={0Xx9rNZsgVGnLh
z4Sgau3?!zN3bMY(=@|d6B|5dXb2H+i=uZ*lTanmMCK}{RtYn@YU6&$7~S4F)@BmYhLme?G`5YJs@f*-MWD!R&Stn
zDGQ1Vrv?h)&+$nG5>6Taq8R*f-R#)5810zm%^t};ao1w=yRN8f?uY@WsgFWXP7Z+b
zc{2&QlboJgh?%PZjZq!3R8nEblb~Xf7hEz%^6FmQg21u&BvqCTJmTd2r&@zBhZ6kh
zVBuURj2~5pNtFo$sIjc?n-K|TE1G)vb)b7m2mXD|NZeX#;B91Y?v3{k2$nWDxTr6@
zpg^StgwIG&oKysw;$;|EpF(FH&}#XG{ak`4FldpcEbIq@?6Mt^aN!s)yL~;ALI;z<
z_h+G52n^RMTRu;p4WhWk)_>d3m>4TG^V263rj1DJlVx{+WM(_&^>2cfr$n>2)H#Q|
zhFzk0!^?lj_PP*2m#p56L{{OYYNKek>3*TC%wcM!JUor4V!1slv71pucFci$CqK96
zW^Q}FoC!er&5>b%5jykWl3-+Z2-Hmf1dK6>NmD7%zL>sj%WtNXPBb|fN0IiUYRlHv
z+pRg}?9sQ%e0`Cahg+DV;y|wTL5gb-?}FmU(qtK}pV?qp$ww4OJCEc4$^H1_ag?xW
z5j{KgCLRYNV{Dx&s=B!)T|F*`V~>tCLz8Q&|-8sI#e{-yLd#8IVjXkPJwpD%CAZ
z3z{i?-4%6i8jL%Nny`)F>vnt3!jTj>Z7!C}4gsp=_fko>EvIH%K))AS5l8(4K@Jyy
zNFPOzJp
@N&78OX>f|bZz=L=D1GiN8BDUu6=PleK2gE?
zBDdZjN(%wuKQLRgiK*G9iv?ti6(yPx1=QY!1*>;hJsCpXC{fFj<^TY!
ur%}_1oy<^FiwMQ|mYmlslUJDFT#Bp p2@wTWT2Je)aS_$>KSZQmDuB#Q&mH&_mapGFD+DC?`&
zFGHM)TzV-LocD)9asiJQ#IvjsT!v*%sq{`0UCo4A)v+wg6`{@aWMP_52sjC*#s|VH
zEhI7e4k9)j|7q;GX|_3!NT%J#
Sepe8OaNtE^CsPTM#Cfle+;o%0Nt3wVS(T8HQ=|HlwJG8RdHxt> zU&h!EW|`zZsH?n~N)m>w8}MfSC_964IRNDU1hB8T3_JzB2{cMld2@roSe;g&ccigt z-s?d^?3kWjzQ(z%r12x6NOC{)w8_icd;f#{Ew^B{fZhF=oE4zZV)gk;!u-}krlq%A_gr=R-=axN0lVE<2YYCWBhS}c-qY5# zwC45JyCVY`kCi@4xs~!n+Gdv(5`b?i9UqoI-IN( 0RDYYdD+rkLg^7FN`(4R7 z=y?Fv#En>GjYAK`QTH%~dLRKP%ca!LrfsoO`=i-Y6gOCr7o54l5^j3 4%z3&4jotNFLcLxKowi2)sF%wyQ>j{X9n_+jTeoX1i^3ZOB!Jg&I4jXqp|)?# zg{-?yvMe2Mw6`Yyx~w;sn)m$2H1ON&tFG( gOu~knO%`(+~Ji{NzI{t=GO4%7o @8n#O2TYmv5jP3*h zPJp4AVa0-w)>!ouhkTMLKyK|OOYG!VN z09F4=MB&!ri>duqEvxB6YE+76>U?ySGo=VVx%iSxr8Wyb2uVByMdX$4PD~QXYmJ)O z9bbEEfjK(>r?c<2vj$%Rb0oYvh_OuVr|YjcsjD3}Pr)5}7^drt+y`FD2eFphQPlzb zk74LovXd;W+tXH!aZ7KPSw6oa`_89>f-ZHLa@i%8gSm9MGyb)eQAL^C_&q5(?BDfC z(c+#TJhD^li#W8UQc6Q1X2LP#h;+hyof%Z8UM?E1ZLWpxwR=&y16Z(UaHb+G?R9UW z0Zvm5KGVG&^OA!LPHgQ^{xllnTuZv?)A6dkr)>J+UBcpYezUfDpW%d0%zhD7LC9N+ zGq9w#;!?3VKI)B^N^`espsO`scduv#UJ-T6Ny|fUe#eArP-M5zB7CWXN5%46i8U+B z`>&kss4LfdD+WNpJLRyTvK|$KNMM^$ZN(hEj>{kvH>Ja5UU7Ju3Xfj)id^`5u7|$9 zWVULJ)~Y|&3(bwfcp81Oou3+FW=T@}Xj|$kF5!ZR-{0(Y*Cb7IEB%+;;`rNh-?}_< zXxJsjS#htk0g*ZTC=y$;R`iqiVr-H6u(IniWHS!N41ZGyV{#XDuhqxGW-VN#pt?h0 z)hgK1K@vhS&Ajw*d#wjCWGj~mLRY@({&9;O1eAkh>BHhGZ%0Y$JjSEk-HN@SH3^^b zuaJrWK0mu4ojGMIE6ic|Q&seZ%Cj!q{C-AMJT$D?x>L#ON$Gn6|D_74k=>XGtSN5W z$L0kljV3+eIi7r(qUV2AbAnuEJe-wAZJl~v;YAJv+4K9Zh8kV9(3ak%rVC8urM}tB z=J?uuFC%jN?5R?MT|oYNeZ%{cq#LWMhfoM{ZAtxcP<8F%8CbW3wj>ca%J}V;iNC}d zPae6oJyu6Vu;?)nfLXYHf=N^mujRKITUmu>$7wQ6tLl?cGD18C**ef6gD;!?X?V+A zchZBP#OOJM(pK%X=39iY+I-!bUcDCSoB|GWJig5n1Ia!~VGML-t$V;SI~H*w0!-v5 zu&_sLp6{97XBQ^lF430+U66duH`yY85>Lq)&ui9BSV)yyS-em(FB>Qa&!rw%X0LK| zv-yGyqwL!RAwBxHBWkUG^M3lh`VUt^h%6rWVZK5=fKk)Y4{@#pn-l1;d@ajz8!WOz zdZ0B6t&8n raKy-s`yUlCz@P0A2LMlQv z;Ls`sg^{JtQ+wwqvft%S^V`&Pa4WVF8>wm#NtDFFqTAXgR)_z#SOT*k{9leDc`2Am zd|kQi^JW;3y#-reCI3aKxz|em?>?hK`-%AvhCOE z;}kH3@4k)1LTqKsnlAV6voHEWo^7QtvPF6=7VYQ#wU1r`_OBeI%R}h=U4J_C^m`mn zR42-|OOB&5{X5Jwy611NNPw +`?B~o>Xg911B_X+bfC _qBQ^abJdgTKd>U-a2$YWZwK*VrX!(q!KA4l9DZ X2VFuXCs)yEx@jsXqtEj!CoGoSfO-E?6vF9Q o|9?I;j|nJOI)^9DF=ty#@&YN5Ed>vE)MYN zjV?P?3>8_}ef+7%NnLUFKvY*H)4WY<|LMLx4 a8DQqR zMnQ}vo?@=*ejrfFvSpJ-Uo!2TGw$BM$6rzjUH{0{Bp&n&JM`rnLOLqo6GbwWrK{g7 z!maR(CnS*ioe2j;9DV_XJtU^Bu-)`75B1h)OHE4ImL7#gPe7z|0n842YW{OGL4>H* zHOc2n5Ld}1MA Rv1HQl0Vuhogwu$+SVD65mMxA8NDWYkGSmBzz26 zSa8A{z}?K_V5KSd*L!Zgox8p)jg;GRjB0-)`buGsZ(vcn)^HQ+;HhY}DG %-JdY_dWN<>&BmvIEeAfAawceft}9NoHatE39E^aM33fjQ3#k)Utva4GzZf!vaW zKZt;1R(ZA|EulCq*-h6Dp)O8=eczQ>K$vT|uNIboQG>ZV!f(%aV#vGhly-KFqs2@d zb+y{88l217_+ex_I|D`C)9sN%RRazn8@?jUc2ngKn#h=rX4v}|9$@{tTWz#WzB` #a|+Y;1mC`j zR$l)&3?e4tlhV1waR F|A9^E5&jNQ~%;%^nuC=3C6*R0=)sqkT0f k}6mMdx|wpLeOgu4zFO1iO_W$ z4tT{l^fXlCFi}hp`+kKd|JEEY4UD8G^^C4?DI^w?pbje4ufUoi^p$*A4^^G1xTIoD zYdaEXKn(j %HU8eF@mQFo{gG(* zvI4(0B*-n?6p>l)@@;#?QkpRb{Bj)l(Dy3^qgzYjymHIRm)m;F-_k<;-VoBD#3Q%Z z(v;y4Nn^*on+(@LkaoH%>@L=|rV^{tSS_N#Cb3Dl44&5^hcEg?*(5d<6~b{&(){O1 zh^4KT;8z|$tIr}JrSg*preu$u&3DyFg7YW|jKt5;5)Q|xn;qr%yD7aHjnzG;Oz8>D zi}eQF;Ngr~#2Y_}l+33&AHW+p4!7z5ViNsktV90!-k`12^&x694k2MCbC*CEkJ+MS zgyVt|lCZs>dhWwQ+Wr&a)toHP#L~NfJjGiKtFrQV5;BX$3;XGdf&@M(2JgKsd|9aD z7H<5Uyw!5KHQ@NZ)^>*`Wq};?iQr+vAi%6C&+}PoeI_N2@j@?EWtIJYy+9DYVg8bT z^{|=cDF(%I$hlLGS6qXdAUwIN1H|tAAQm`_Y<^D&QJ844+}wR*&dcOJI+ZPMw7Ikp z?*98dDP|5xXKm0m-@uKk3@i$04JyFT<(>JZKZ*0LVaLpEBN>PV$pZVu(Y+r4G+Wbq z@i}^dXaJJ~t%sx5)Fy1#y=_JvujQnL53(|}ryA;{>fc;6< |$M6>!12Kx*Qnb zYT^vnKRqq-Y~zsBay+ZfaX(0RR@1nI0}Dl$g`U%#B6I%L6UK*?EnOHdtAtIlmHs$n zH#)!!Pmq}cuz1c&u`9zO+%z7pll4r)?0`c86wniPIL%wFpUxJJaZ{%MC|rE)x$yJ# z+1_=r=223yojI>fyTP-vhOzQgGko5PjQ%)Jd 6vdGH{cjFZdex6Z4S-7AaiqJ%K;x>^WXI3AB0>H??dg5 z?;Y(qSi3T4d|si#Gkc7FEx8|(z96}ZW%y?Gjj!ihmSNj5M9uXE;UDgWm6_C*cDXHq z#MQc0U3QE 8Js1-jb2R!RWWB2>P)77WUQ$mD_0d7MA~xui(!;U$?cgc-W#8 z4Y>kTe{fpKsH8R~p!@Q97ET&Z)c~T#g7)zJaat{Jt-KdH9$h!}h7V&IH5lJkP90o| zb<~q55$aUVFJ8t9^0MrbY^xk&qbrVoQZAilQU7Nn+m{-L2&Wiaom)bNR-2#fXGlKg zn9F={cj|3_Q(Bc};vYa`t9eCuq|z 7UXP7pMKoG)RSQm zOZdQD=d%)_yU)`RIj=jbjwPIU%`vDQSJ=Jj5n}H(01X+zA181YZ46!VDWx_g^XvWG z^-Y0I=~thA{tDcRXpmSXa8DkOIcZq!CNB2f#`_)xELz0-2hbovqngl+COJw3r|-t6 z9{08Oo?cCw^Jf@7jvG8U3a+M0Iz@(m1NNkD4gWKr%s|3sUsO}_m};97p x+HT>Ne8B7HLj>TTN5LfX77_ix|AOb# zWAe@<_`p twH-BVxu!3(*dmn5=cW;ATIU~DIDI0iU(zOwJttOU*W!(%fW&p zlwFnYpFh8Ok@o8@U1;I_7J@QVRk+Tg$3QstxZd+EJ(7)PyiK%qZ^ADva;Q|^y_NRA zGq>3pK$<|tBTDzBbAr>{op|OzJ3Uz&=Gd89&Gjp6^EM`xes4_JJMhr!qi=4l{q{Wv zBxAcKFi+gKK~5{oFZpNFs?4L0buP~^|9RvxpKVJy(_t+<>Ql&;sbQ*jz$%x buMXgt z)0fvQ^hvgUu{I&r+LLC$mxvcUMu*gmmwfbUhP=KcMb}+>@AQEjv((^|Yih%QBX(Uh zCH|+u!GnqwrS9_TdZ38;k3G^#CAbt3XHf0)(lR8i&M%|qB{COJJR3DFDJ)JZ64x7}Bj<7I1 MclFy+%*($b= mZ-g^=!C1p}tyf*$li Ip{oDee+wH&W*HiqNb%1i zBkBVb9P&O078Wms8y|7Dh~Anljck%-ruy&`#5G^BcQ1Vj%hem7w=W~r1c5MVn0cIh zbBNN0g9x2-Dy3TD8(Tm;75YE3o4Pk%gC|8LHSx-h{%V+hc&$)EX+lrN+u$(D0%n@M z3A+$%aXbw6J4ij`17UZc{?wJ7(cM>4jTJYx8*(V)8_uvviL7V3+37$s%6Hm)PDIb~ z6yK;{?Cnx0zfvn^ e(%!WnJj&GuE|8gSv_;sc%Q zld1NI;S$gg17oibKA|;zKZUpqw@BrMJ%M2I7cd?Plj$ptk TN0kx^Lmp+e!r(OTF0) zEj@x_&eKS=&mEJ>y~vF}?ui!YT?@Mj@}pSk{^n7$EkchZ)o)*GMSwI0@;C6W!b=E( zi5g)(Y$fc0@VH%OKmqp YRIWCX4Cfq7`6#0(>K~zoknf{!NI#_*JK47 zD>wPJS0+ZNkxP|CO~;8Jvnv_4wfG;@gP>;BzT9XT%!VC%0Xp4hQZV_B;O3b|*l3V8 zSkT^rA;LAFxB{SMH0WHXvPRkG);|Ek>8!;5A?KxPPj!&9h0Tns=5d8a=%>2>#B}Um z)!y5!wJ3qGom9JnFc67J`=~gqO}l8jtD42y2juZEOKRPIEj3-?FA*y^o5X2H4?4FM zbEPS5QfFba-V)4LPWtkz3HpD)!-7G8lLTlOwVY3RRUl7)t+~gMCdSYfgb}Og;z2oQ z?p2BfivT8EXO_=})AFD-8BI+pix8`o%#z?~ ?n=vk*t@}Nw5>`&%K3BDbiyW@g{F$$3x3GI}*>Mmj zXa=h!Bq(eOw7UW0G%3{ktFh3!M5u37$Fd^1*oX3O!h-XC=V!lA%k6gAXq##Aq*{Fi z*+~`X^tX5qs=V78rzkoGkoO997Pe2-fn<3@C1C~k#Lnf}3;XGs;tMbm9e&+{iOp^h zUYIAAL(TkGU7d*olYH6(^^)i6wBaLBe1R0vDW$P wD1{gWI@*> z2IGfbAtbB^Zv_OmTC)n7&(-?PH#9^!?-Ag 2ByI|$)TkpPvw*jrtE+(devm+MWE)b`d_$tmjQHPQaImaW~e z66**tTAsi-%SXl{N8K_mepjgFNuaLCGwb};ig_EPV -X>&JcTn;mf0 zKbXZK)aiPgrK 1H_a0H^ ^Xzl2Adfnu?;j&U0etPp9T!WLJ1=>U-FeHWD{wqX!DMiKo zHw(}@U3YWAQ^dY}v;dU4;;k9!4vCzsrsim>ltD(Jd>uL7#BM}P&kgxB^`gV-%D(gf z3745EcYek-9VFqItLE}5<$n^wNPPn(zr!c)DI792-qF04sl=c8UeXbaR)1nPUaVs! z>SCBH6f 7!&nB6>X%Z(LR#AAlnr$MYGF) zEWA MGu0e$;m< )?Hv&FfQf;o~u?3|t z$@}tKk>I(FA0lZxvqt!P2ZGM`6Bep|GBr;A_~9%l>npb8WFErG1Vz)#Lyo^;T*9$m z5OP?ys1sAG-?C6Sqnct-P^_Nvt!7(+tpC+q(QFE;dA}ZWbvBo2R5 &QO z?VtZ`-Ce!;d_$jaQ^GJrz<72K6Z@M 46M)P&Oi zfa*^aN 8| zxESZJ$_`3gHjm@3B^@bDi9Hj6Ix
iHlfbw#PDxwBR8&- `M|O-|#W5Uimv ^1@s(;@#icU@JI-)GOyLj21}b2+x~a9R#+R?F(SQD$F?KKX-YR&~5u zGp=3l1DzuRXH3Pf^nn_~V1RsWssm75{pK2vS$CzY{sPJXga51yE#6vIPou&g`9&Fb ziSJ3})wqJZ1&&^j9l<4|PQ^SctH39BE_7hhC@8cK@G5aHbOoKV+ucyIXzuxH^K$sC zvzcKckqo))996G; +9+7C#EB0K3o(-JAi4c!c+MN zB8zsHbipBK3psPtyU+NJd|bs3UAE4zs;+$j#=R NOZ= zvr%@GFYK#zQh_@$^^!^A=9w4pPU;DRl9cJc;I4-@XY)JtR;%Yu Y4fj?XK58+JXMb*54kGl4t|s z#Q~d9*xcM2bY%YP)j3+7<^Bq}2TgAp5BVQ?V&qALXoTBK56--B8=EgT+8q@!w9gg5 zxW(0?!^-|a7L=nE=hrogduEcq@GB$UoF8{L)OHnw*Ag&nb^oK| n_CQ6&|?hWWID~4)bxK{)*$hg2T6R#J5sxx#VPc JY_kKI|h9QTgU}W^f?&jdv#BntzLGPmFA=-Y_ix(;7b*E`|nrdieas zlpEcJQ_H;BAH`?deav%Gq#?PXZo2elPR@s7{)(ak{o|wG?hdH?W-@0iQw~*p1X0k* z8==!t8UGR&q^z;BP eEXuI7RH2ITIs^ 21a zIzJ7b*VL4(mWZ!pYJVK)q!UHkV~^_4Mtxa5{KB<9y @nfo8u9~0Sz$Oh7=3iSxZZU!|b@o)8qGGp#1Qk zsPR`IIPd2)AMWX+>W{vHg#lxRE$D@FuaB4enhqIH#kaY9bUt;bi}0LVZrO;dr?Um5 zW3;na-VSHGH{eH`d`_h}ROvfnv+jJ%ea_}k2-2vRx_ ~k9_NY8j(Xql8dx%wyr@>0Q1g=D&%O8b?_Su zej6okG5RL;O4`M<0i8+KP#`Q}J>!+_EUlO42RGv*s-r63m6)zET=LH6i6Z~9FHD>X z0i?-jK$bX&$E9U!HV_(sMeT#X*Y`t7+6P7E>CI{TH5sDr2LoBvN!Yj5#4y7fH=F7j za{|ME+{{C$vIkY%+SU5{bkd>oZtEb?Ix#N#*^S_jKpFIT>_FpYe)~JGfNR;+vh$wL zM@@m>tS}UA-hWtdO)#sI5}j!Iu&jXdPXq>rNhHtd>I*aGl8?Jb8Wv=}l728QxP?qt zeuMcY{XQYL0qCB&lQRR5+MoUMJHcRqwiaM8^42{V-j!S5y8dJ`YF&eMYTFbBcQ9^v z{uf<0l~}yg5G$Uq4ZyONfi*1DdYwn!^@eGqXnETmy}H8vy8AUia-j1&iDy;)E8MnQ z2q&2f?6=EVq1cwgaf;uP_ws_0MWc6FM6r6#e^~~`0H3l-%ZB4QyOo-DL-a$sob{s7 z24QXSyVY~=eDup&XV-(c=rV9-OG}FWs}Ev7T&rJk?mi4yg_W8M4RR&7-xEq&hrOo} zb2e!^{ )c;V?XAphm5aVRb-~&aIGr@!4u{EpkpvRkv`{<|pX>FWnQ? zrUNhta5N!&VU=BC-UBcE0zm&et05!dNq?@{Mq=ws!DesTudZF3VMP )(}+UX%y%1pxRf`NQR58JL#;*oQa<@1p4nW>vk@xCS+mgQI`)*>W78yl=vAD689 zg@^pI^jfE^h>tF->E^ ?~JT~ z3w*G2cE`~35|u>nB_EO5Rk=4;%qMp2lGk4J=}<;o9UIn412(q@^ V!3KzfT_CgT6d`7e4S&t -%hOR#it>p`DOi9K+E{j}@Bhh4qDbxASlc0tUvyq}KsW-;9X zlTvHc_{wRT#QONO|I_8PdzIjV(?0g?f>KAkXD~XlUm8%Wq-4Td&nsdvOJ?44q>es` zv}XX{dt+~O9D7R^X{7t$ynREbL>U1-hco|AZ>z<7i-zngs7D_J_oX`Fd9&-yHI@Oy z?!tVLPSe5pz<5sMr#sPws2ZMjL7Mz=(Vb4uLq5C$d)`sjJm=#hTh!?RY$85N-VdrV zf3Az7BQSO${@2aN5RsRDJx*XCt@$kIvD!TTf1nz&K;6{Xw*RBs{91gb+L6O^;c{Ti z? H$h`=&0611|c% =kLf9#UufMC6G>4p9F$(7^avGNv=`N8~B3-jKu?CAYq8M#41mhEg+ zh*SNlMnvrnDou25k{NBw2j{M_%5tZ#s1Z*$^Kd`i2d#HtA7??IK=RIC<_b>>6>aKp z9SH-Zig&H>H`j-nVQaqc4sk~WZvZk$_caTC@`>wGX!Zf#2cF`vo@7UP)VoY$>?P9i z=>e# m6v@_k%>Wc$ngoBCyMd*z+X zJ|1(r?*Y+I_kA{8s_MrBp$aX*0Ti$rSk1fFn Jbd=U%Cb)txCHU-WpdArB2bRz8 zUI-8KRadQ0_0j!q-jq2JzY>_OLpkxhuQW0Gc=V3TIp(jECSX^HS=u@u?PTYkAd}c7 z$I6>|arC0D7-3rXdfzhVu1jxpc~`TZJS+GH;?P<1v#=M2YZf&3y_uXI)f^=hLz1Sa z^H8n*+;qo9;@H7BxzO@lqhKsAH2A>;MSQ&uX_hoh3g3ZG1Xa3QUq335|B }oJB9c99 DFE= z^&ngS;Cg!s)Rr;a3Le3^T%^abuW+J@kiC?brK zcsONJYFKP_LFI2T4hTlO+UerZs@$~e=0eFS*N_lXmkUU2-?3s^9^s7fL+ zjkVzo%D`=(;fXr}4d3Gq4PI$9FdKJmuW-qUsB2v-QMl8a5ql!EMRECFI6q?D35Ux6 zo1MMMA8$c%V9rlQIQo5NH1aZ%c$;0~&yFH-cAv%v900m@&%Bl{>Hq_GQ8@pEAM_rq zMbB~1pTS09$cTlCE=zu-b$g$jJ>b`fWm_+&___TC*Ay{w{yMu2z{_{_5_^A2HD7ks zY_L*p^aC}Lhd*)*E#uuP&+KWhO~`n@%m*SiBEI0r*XQ**!+slXe}oqMf &*R#Tu1K45I~6+pW42lV0SaX)p!y89sX0vHvw6dl&5fx%6RZpydzYiP>;$ z{T_v%j~1N5_SIHdlKy>S&pQng9B0rndLdG1Ni&L)qF$~4w$93~_=?ro;A|itt2nI! zoAv(J>H0aCo%Jj=Ous3M&cOC1JJnwaU@~wf_JW>3-^QXqRei&?Nl T)3)e`B5eKCOy5mx>xm0{Q}x1w*ePWn&bOH@J7EY6)>2 zK1r%C%yHpv uSWrQiTUYO|uTxp^IMBbEn}!YCfTJH<4u;3|1eeMd z$AYsN#!nFiFfT ouXCC9~~={to~&M^Yor6Ys&__JEiqzs{d za$@Wxe|POmKufHuVn$>#eQw|r_E7dprtE6cS@b<* Bfzr z@Lc7V%AdfuWikKaqxStu3Q6EiB28Saa&T76G!_>z8tTQM1@9M&Kj{1Fncm-k-e)_| zA*h^K6Q5`N4%R{akVAqyhvobvanGPJ7O0li{l_Nr-*8ZQQUHU`U*10nK+q7Np)Q0k z!A=yB<^Gi0^y&SLa+qfDPzl63B*HfCpCmkJl!JTFCGDwJxLN*;si*QUMCo _ Qq4)T zTjO!fOKxC^CI6P#ps1vP#e;`bh?kE(#UBMJb}2>kr=Bf?0kv~Ou>Z}9qRb(Nc?1JT zXFNsn;3h{A+QWq5A9yS571L8nI*aPupsA}v;GhcuYH&7WmH) >UiH6bZ7#7G{gB%jYiT9_?7BRKvx{IPovbP}$xJ zT%`sWSi$g0MM|jj(k^iC^C6NOQgXI(;ft?gUGVfBSQfC8yBxP<+%Otgfx|UWW-!zt zq-pqPQ>)Z~rM1C#V^G4g_&RrBpLM~ev=8?ZF6j-ZN?)$J^_4Qp82_#CrT%z|6|UWG zOoX>Z`R}>#u2Iyn qFeaTd9C`3z=NDdtNj!)gYQilHax^8Mi PCiemm)+++UV^@u->>ow>hwHjO{ae<0%oU=&e4KvzhJwCQp#@U(Up+px4Iw zynmmT@E8q*FNrzGL1ppOFLRZ@MRnXC7>-?t6qkLPUW5{1*S`Z}ZiywseXYa<3fcJi zUmI=B?^~*u`M;(%IPl8##+mX=LzsIeqK^@8mEm&<&b5mphKGGJ>fc`?c&F zLJR;M?qE(Zukuvc(akdPQd_@YO)iX#O9DF2D)G=zvDGJ|h82Zq-)+UIWZd+6zd8;5 zQ0C+aBCM1R6O44Ifhoi`prp$dKPAD?n8sQJcWRPgJUfH t9ZLBnW7EqT~gd@l0$JX zs=2Yw4 8kPGkV7X3&FA!YK-vG}uRs#Vq7IncO)c$INiNgifnx}8(P^N``(iY*Yg zM^W?g6OU+DYDJqAX@}xchD^4{e6mUkzc+*yG0_XlgWL%zqn#|L<2kxYQQujykpjY_ z030RznC8>pN9%eV7f5AE2V9@6or(QQcRn*t$o!c>a|Mre WYgxlJ=gi3ulw1j^lOzQJt?b0Yu@+DDy=Iq9f~~ic~+qpXm(qOg%PJxT35;tdYc>fdgH&muxru@X);@Q zUk&B9v5@(0X3P-Ab(?q BrDoDGFRc5Zd;=s_;a6#%> zEz;*4;f!Pi9^6!Q#5#(v1&_3*X=`54^XZbiDSY5aizNYuiQ!C(f~Ngx1S3IwhT%ND zRv>*o(#B}Mr}eo2Qe1k>xY#(_^1oUDfwb;*qK4EvQ*yVjEN8Zi{cnMRvn!8}#dS3q z7_A}b?oA2{d0Yj>5Qr8j2L*-wx2e^EKziVShum*k#Y+(;nY8}-c&1i)4&&aIsq$w} z0hF#u!evx|pt#rj7Q`e#jbK92Ee5;qH%-R{KxSq`f5Lf`&WHjH{B9xZJJSZUI*0ap zm6FrvLQI&bRSqirtrR$?u?>bwiX1NJOZWq(z}RE#6z}&*Q-j}w$WlhvREArmRD^5| z6+JbS6qtaV5Fe)AjKfUq5QR!z4yT?E7P{TSD(tu=mZmcAjb|`OYB4WwgOeLoo_mTr z4cmw}>5X#{z;3Q8N*N*?%YHl=mnR|kc75l^s_Wja9;5~E3fUhG4f_P8aSGsm5mw|S zr# n5We)k%ifzJ>hixFm#brYn{rXYbkMoS6 zvEm4!nn;ecn|r{JF#)fKVj?;d+_vx_5`ZNin#Fw07fMbk@@J|{5IkL^I$nlx@4S0+ z?3=3xNchY|-Gcc89%ZObLr=KPZwZn&B$Qp%_GstJW8mhmioc&KDU{88 hbX{q$wqUbB+0R_}q&R@oxfZdWx{OCBidrc^2Rg z7Z*3^SZi%0Z>mqH_WfjfBGLqKDps}lj)VZiG0X{QR;z#4P-JX*EFI;_XWx|l%D#?p zgYotZMu8R-NV3Ah2jEmlxhb~S
g$Z}O{VV{HI_I+t0!FJkzEp@- i593fVGzy-b9Hd3w=rO_8x eYSm1e-?`K2dk^-e*lO)mI3%h00I87Q-<@y8mx*&Q zzGOK4hLTTk=vQ#H~BvrDQ=X@_qS2NSo#SmIVxptAb>q@oi hVz{a?~c<9`h2tlyy*9LQ1A*T+~h#{}A1jfw|z~t&n(C-5u zdS>7cd2-9v?|a4FVpa@d5i5oiF2zFeMrojsy6JSu6wX~$+8nCj_?QF{QrzK6>2hRv zu>{DSLzjS$21)3+sLNiMiL!JdJZo*;#yCU99DDo}g6pefbBms>>p8Kmh(C}tylvNl zl `$NqQ!kWE-pCO#9$P*F?FOaQo{<+ z*!KKb71H+(Dq1*iDO4(xg9iA{0KtG+{O03i8u4~}oXdERGyZ1v1p0X3>TL@F8j68q zV13N9lDeAMhjvo`=sl_LZphPbfBkx;!?C@kRl4Luxzs%f?SQgg&sw}A+!N_3DpGgd zxHW$;Q0vOPzfg%tyF+7|Jlr?dnNYtoNB8fl+Xr9<9ZLw0#B?e!t>0X`0aXjP){Jw5 zi7^D<;hE5vyO3E6GK6fu?5+9CyYBM DRw@yP66&F2BwPrUCU(N;bh@Ar7jw^X@zqk}pzm zY#5>iskX?0r$uP3S?>wVIgiMe o+Ku9B4BE=3qNKqY(swe8r=TG0%o`fn34lr#dJs5 z)Bc-7#3iR0&KGwG8Ki-!A7T_fe7~#-=Yko8(}e7YO{&DZSUs0S>OyUOGa^&w#xUaD zMm$^QOioxS|F9rIAS!%d;Rk|`7o?hLa=(ACw@M-`$k&G4mcFgJaYZ>-y?>lY3>){T zaF20Wb42_wH;Pc2(>B6adlC5S2{px=YHt9CB$^9tqya2q8tL3gxNyweM`>~3V_7>b zR#sA2oHGc~1QlvM!?9rrAqT0#RkrIWRK(6Mv`S$sh5lg|IcTD=5MWaQhv^lYdu#Z} zx5WTl{{#n?lm=v_GUGe &F;*5%XzSEd^oyCTxwusAl?_&?=NQunj;Zol1@<9> zG3OC`DeH%ub(w|Oh$y&%PXj8mzQ|d|w=~om)|{*}rsNo~_EVc%zI)tyM%w)Sgqtp{ zVtpkv#r!t10-RuiY=x$y0L`i}Efl3;lJTu@yBbo_gPZp~R`ioQcnCZpT+y}`Cgfgk zr0)ZF=gXH)Pj8zu9CGq#9*?3V9)8_j+nsYfF8Z7!y-4XcD5&T}B#qkS;LgeT{8u~} z3$ T)FKw&H2{iPgM2Mx#_RE5(l!-_dm7WFE)Eh;l z<#;LYs@l0p4P(N 2Zr-7KHp1sipiW5O+Pp@yum_c$vG7a2h3m#%@eJ>D=K&3 zPd>6tUE*g_^sVs@1u*-yBzjCLCK1bFl*JJu5;1qgTL274!DIsW+9c60@sBtW!=i>M zHradeH{DW-P}sD;Y!)icMaiCAYV~Q`2^62uwqP{orr4&Y&GYt$G6{7c%H^wREb5aG z0sdt=RRX?Y6lzy~0tzMm6#n~s!F9M7j=Pp5tWfn?D@&KOZ9`%*k!{&8X(Pp|aS_(9 zj`@YZKD^WmdC?AciGNrm?RoO3HT)xYRZ|h$@U|twcyb7RzuAhT5;Kqsg0ueU4@jtN z2_7eE9EQtIr~=QqoDYhG2!?-lA%-LIva9Q01unmo8ZJa0CSOCg3{m!*8@qgMPqh?3 zo)ue|qQx4B^>RIAM#~Rf%_%st3I2pVNT)3BnGll+XcXWwFX^;|%x(~^VFi8P8 0*BsIJ5l^%BTVHugHOhwKr7rK^aOz{ zcV!@`8_{SnJ4nWUFOH*`NGdxljg7%E$z|>N 9yA4QjxqTw@?Ngn8z<`z^Gr}6kiAd!7r3X zLD70Z#4>EsFL=<`#>4nimOOflGq_+3DiQf1qpn!`MNbuNI&XFyVZ(~M4`-wpOv1G? z^aR;hLy1bd@60@k?y+ L1o&6^@wpi z_q|Kyu2ve2gZ@KHC9;`#PT8-(C^MpOMh+^OgPBK#5O7n#a100)|w?6=L z?Ol|MhhZFujy%~f>)UVf{3{A|8MhrWIZDOAKHsYYBzybd#5t`uW+$AQ#AK-+dqw4h z&rzidp&+Q)v1oUEHwfCbPUMr&`F`U5^1?0gFmWYrrLmwFl%|J-g^nlrzWdFH(z;1Z zIcgm%$HLOY?&c 5fP=f$bf$T<_`R5)(l-Ey3Ztpz)0@Iim 8Ws`w)?UVycc+=X;k0N@$kwU!RZlbH*0lWiukI_3|Ke^@^g`lZ+G;c3EA8( z1lL>9?{LAO<;WE`- |?%LUN4* zf6fcf^xsckK76h&)PJ~f38(Z1qo&IuAVv``iqDtcFth3Hh3 q=Y_b-A1YpPfCR_ z;1jP{n#1caA3ct28JC<|n32oRFR2454c(}K_`FE =4FZp}=e>`u zTfxFmD+1zObFsp29D;XsK;d_b6fYRnk9 H9q6rN88;r)rI78+uaU zQ1H?5IC03P{ex_IWaQ4Tp&eO-N;z2tc)*NWw0U;IEZc@|$;p;^E>dFMWoF8X{Ux0M z<|W_wwR7}@*_Y}!?H4NOcoH$wjtj+Jwkj@p8X)O>hvRdx r+q>(?~uw8->5fQ54_<3%tJ51?)Ly*Yy^#EQ2+tpXVO)T4<=i zyNT#_9qFnKs4EK3dfn=|dTafQSmN0}4T9#$W6F12erUF 8Q<=#IjIKavVkKff>GlK2#rzb9^)>dus@t$Qf8Hs#I2Vtl0#ooXRyusM%8uDo1 zcNllkaj&0sYfesv+YjEY^imt*Fgb2*9EOmBb~v&503rqG3v#Vv) sZa6E=w7Ej@h zG+m ix^DodS0;s!vV5$R7Rpi1ua;^Bk+P)To<9PDNNp{9breHyF5&la zP{VdkqzX`k>P zNVerNM}T0?_0G zp4@F@nTxEcL~d;WGxOfP0(QTg2F!sjka|G>hwe`x_jboYneaJ$3DE3T7_r6 KuXvN4h#M81B6u@`elTm%Z$uaSkkydfocLra3= zeTW`{r%T$rzHR HzfN%jxQ`1k0lpAxJ(BS;Wkia$8F2uQ)%d|xb@9|1RmNNE9pMY(&XjXp#x$#$ * GpKcCZOK(3!d)4{kqR`ek?XxTjI6UuUb^zsH1W&~qwkAxEI z-MN@7`gJ2Mx=;l?w{MO~XjmDx1*GQc8XH4v_pB!5xzhdfX_+FN+AU1i Getm;eY7|@0QW^Lo^_?gA3lCFC zl`vpCBH7sd8ONJ?wAvd{JGRGyRRz#G)@GdaM2rN(75+<3QFlYd5)OQj(;sCseH}b) ze_sK9dlQ4AcZie+tuQM@p}4RBY8?vhNLX?R@qgL(KNM2+WS%Ah>JQup#Q;VJr_XRN z89i6k=`Wv`VZu^m`6_w}5CN=orpXoG{vvA;-C}qATw|^b{zv{6!8{tOG+W B2 zTwt4>;P^mLI;X{h-NHh%lyf&J=x5BCu+Uu?0wvfxA07U>9jM{LT?Lfn*%2z_Fr4 zHB(biQ@frQYnsIe0SMDOqJ_t$(Gbl>-%317ZjXP!G{U9luJd}*O#?*>NSll06e{^m z$W_}=ZJsFhR!M$(YGD-qnHznL|I~`!B$mu> dPa^l?Pn`6=2t)48@px1cUc5m5Vt55|;j^H~dGut-D8d$ChiO}RS@&^KQv)jk z{IqO@MTbv>VQ$Iba$cSKW`*c%DStj)2%nKQGdlraYM~Pko=6a1P0wDhP0!i$>@O2E zUo^`JKMfRFgE=%96}-4~5l(8s$%sk8R;6FmKs15JZBXx)-j+)D)!7_%Hrgye^q05X zJi7t6MX$W0-G3DzVe&5RH(G~%Q!Atzq^YUq*jVHk8cX=B$h|~IP;=f3QUZ<44qe&r zKRr@%>1Swi=~kx78&T8OkcBIW#l`T_dhOcRjzZfx@nkBv E4#V*IL?t%J%98KCGzK`osd~}nG~tN z12fhsi+*B;6x)t c?O=fqdKYD2Ka~<_CvkwfVB9V=Fs#3-l zoB6J#y3r=R@T1iEH~+GDYK|O@GSe~+zfC&XfP3hmUt2=e320j;B}HuNnVu@HZ8E%& z;*C$4O{Gs0-Wv>}D7G|4adIc_{U48(5(hqB9E28D@r2W>IPZ0x&pQEs)UB9^<4;Cm zy(K=D{5H_$+j3fCFnA@Yfh7(a%+_p2)mONCGZui+`gU>+?IjTly#O+HR*1*=Z->S2 zn+yz h$cc v-6i8{m;T6|TuJ-8mSg%eJj?)0|F$s(hR3=3;4@mT4)Dpw zT0;;YW?YwA{K<0UPY4m)BJ?f-SOdZ*VseMgsTI60C912T;V=}=mlASp-NTon)w$)W zhoKWd*Pr+9N_a^<3)8Ld7s{gXZ!+vQPC-Adph6`ZDLD8Q?8oln2VH_i(hDlFj+@|G zdP4;g5UbOPu`ed=+fV9z%nn-P3(TTm>TzZ^fi$JBTyjXk*?r^b2di3+3Pn#&^s9mC zBKt4dotcOao_pcb(qlw`*`xZH$jX-^fi0b!+x%p~r+VE