#!/usr/bin/env python3
"""Read-only TURN listener, TLS and cloud-network diagnostics; never reads relay secrets."""
import importlib.util
import argparse
import json
from pathlib import Path
import socket
import subprocess
import time

ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location('deployment_client', ROOT / 'scripts/deploy.py')
client = importlib.util.module_from_spec(spec)
spec.loader.exec_module(client)
config = client.configuration(ROOT / '.deploy/config.json')
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--trace-ingress', action='store_true', help='Count incoming TCP probe headers without capturing payloads')
parser.add_argument('--fetch-backups', action='store_true', help='Download and checksum-verify setup baseline files')
args = parser.parse_args()
code = r'''
import hashlib,json,shutil,socket,ssl,struct,subprocess,urllib.request
from pathlib import Path
def cmd(args):
 p=subprocess.run(args,capture_output=True,text=True,timeout=15)
 return {'status':p.returncode,'output':p.stdout[:16000]}
result={}
if TRACE_INGRESS:
 print('TRACE_READY',flush=True)
 capture=subprocess.run(['timeout','15','tcpdump','-n','-q','-i','enX0','-c','8','tcp port 3478'],
  capture_output=True,text=True,timeout=20)
 headers=[line for line in capture.stdout.splitlines() if line.strip()]
 result['external_tcp_probe_packets_seen']=len(headers)
 result['probe_packet_headers']=headers
 result['input_firewall']=cmd(['iptables','-L','INPUT','-n','-v','--line-numbers'])
 result['input_rules']=cmd(['iptables','-S','INPUT'])
result.update({'service':cmd(['systemctl','show','coturn','-p','ActiveState','-p','SubState','-p','MainPID','-p','User']),
 'renewal_timer':cmd(['systemctl','is-active','bondvia-turn-renew.timer']),
 'listeners':cmd(['ss','-lntu']), 'firewall':cmd(['ufw','status']), 'tcpdump_available':bool(shutil.which('tcpdump'))})
for transport in ['udp','tcp','tls']:
 s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM if transport=='udp' else socket.SOCK_STREAM)
 s.settimeout(5)
 try:
  if transport=='tls': s=ssl.create_default_context().wrap_socket(s,server_hostname='connectlyvideochat.com')
  endpoint=('172.31.33.153',5349 if transport=='tls' else 3478)
  if transport=='udp':
   message=struct.pack('!HHI12s',3,8,0x2112a442,b'localprobe12')+struct.pack('!HH4s',0x19,4,b'\x11\0\0\0')
   s.sendto(message,endpoint); response,_=s.recvfrom(4096)
   result[transport+'_internal_auth_challenge']=struct.unpack('!H',response[:2])[0]==0x113
  else:
   s.connect(endpoint); result[transport+'_internal_connection']=True
 except (OSError,ValueError) as error: result[transport+'_internal_error']=type(error).__name__+': '+str(error)
 finally:s.close()
try:
 token=urllib.request.urlopen(urllib.request.Request('http://169.254.169.254/latest/api/token',
  method='PUT',headers={'X-aws-ec2-metadata-token-ttl-seconds':'60'}),timeout=3).read().decode()
 def metadata(path):
  return urllib.request.urlopen(urllib.request.Request('http://169.254.169.254/latest/meta-data/'+path,
   headers={'X-aws-ec2-metadata-token':token}),timeout=3).read().decode()
 mac=metadata('network/interfaces/macs/').splitlines()[0]
 result['cloud_network']={'instance_id':metadata('instance-id'),'region':metadata('placement/region'),
  'security_group_ids':metadata('network/interfaces/macs/'+mac+'security-group-ids').splitlines()}
except (OSError,ValueError):result['cloud_metadata_available']=False
work=Path('/www/backup/bondvia-deploy/round2-turn')
result['backup_checksums']={name:hashlib.sha256((work/name).read_bytes()).hexdigest()
 for name in ['firewall-before.txt','turnserver.conf.before','tooling-release']}
print(json.dumps(result,indent=2))
'''.replace('TRACE_INGRESS', repr(args.trace_ingress))
process = subprocess.Popen(['ssh', '-p', str(config['port']), *client.ssh_options(config),
                         f'{config["user"]}@{config["host"]}', 'python3 -'],
                        stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
process.stdin.write(code)
process.stdin.close()
if args.trace_ingress:
    if process.stdout.readline().strip() != 'TRACE_READY':
        raise RuntimeError('Server packet-header counter did not start')
    time.sleep(1)
    try:
        with socket.create_connection((config['host'], 3478), timeout=5):
            pass
    except OSError:
        pass
output = process.stdout.read()
error = process.stderr.read()
if process.wait() != 0:
    raise RuntimeError('Read-only TURN inspection failed')
data = json.loads(output)
if args.fetch_backups:
    folder = ROOT / '.deploy/turn-backups'
    folder.mkdir(parents=True, exist_ok=True, mode=0o700)
    allowed = {'firewall-before.txt', 'turnserver.conf.before', 'tooling-release'}
    if set(data['backup_checksums']) != allowed:
        raise ValueError('Unexpected TURN backup set')
    for name, expected in data['backup_checksums'].items():
        target = folder / name
        client.copy(config, '/www/backup/bondvia-deploy/round2-turn/' + name, target, download=True)
        target.chmod(0o600)
        if client.digest(target) != expected:
            raise ValueError('TURN baseline backup checksum mismatch')
    data['offserver_baseline_backups_verified'] = True
path = ROOT / '.deploy/turn-network-inspection.json'
path.write_text(json.dumps(data, indent=2) + '\n')
path.chmod(0o600)
print(json.dumps(data, indent=2))
