#!/usr/bin/env python3
"""
AUTOMATIC Bind9 DNS Server Setup Script
"""

import os
import sys
import subprocess
import configparser
from pathlib import Path

def run_command(cmd, check=True):
    """Execute a shell command and return result"""
    try:
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=check)
        return result.stdout, result.stderr, result.returncode
    except subprocess.CalledProcessError as e:
        print(f"Error executing command: {cmd}")
        print(f"Error: {e}")
        return None, str(e), e.returncode

def install_bind9():
    """Install and configure Bind9"""
    print("📦 Installing Bind9...")
    
    commands = [
        "apt update",
        "apt install -y bind9 bind9utils bind9-doc",
        "systemctl enable named",
        "systemctl start named"
    ]
    
    for cmd in commands:
        print(f"Running: {cmd}")
        stdout, stderr, returncode = run_command(cmd)
        if returncode != 0:
            print(f"Warning: Command may have failed: {cmd}")
            return False
    
    print("✅ Bind9 installed successfully")
    return True

def configure_bind_options():
    """Configure Bind9 options"""
    print("⚙️ Configuring Bind9 options...")
    
    options_content = """
options {
    directory "/var/cache/bind";
    recursion yes;
    allow-recursion { any; };
    allow-query { any; };
    allow-query-cache { any; };
    forwarders {
        8.8.8.8;
        8.8.4.4;
        1.1.1.1;
    };
    forward only;
    dnssec-validation auto;
    auth-nxdomain no;    # conform to RFC1035
    listen-on { any; };
    listen-on-v6 { any; };
};
"""
    
    try:
        with open('/etc/bind/named.conf.options', 'w') as f:
            f.write(options_content)
        print("✅ Bind9 options configured")
        return True
    except Exception as e:
        print(f"❌ Error configuring options: {e}")
        return False

def create_zone_file(domain, ip_address):
    """Create DNS zone file"""
    print(f"🌐 Creating zone file for {domain}...")
    
    zone_content = f"""; BIND data file for {domain}
$TTL    604800
@       IN      SOA     ns1.{domain}. admin.{domain}. (
                        2023123001 ; Serial
                        10800      ; Refresh
                        3600       ; Retry
                        604800     ; Expire
                        86400 )    ; Minimum TTL

; Name Servers
@       IN      NS      ns1.{domain}.
@       IN      NS      ns2.{domain}.

; A Records
@       IN      A       {ip_address}
ns1     IN      A       {ip_address}
ns2     IN      A       {ip_address}
www     IN      A       {ip_address}
*       IN      A       {ip_address}    ; Wildcard subdomain

; MX Records (Mail Exchange - optional)
;@      IN      MX      10 mail.{domain}.

; CNAME Records
;ftp    IN      CNAME   www.{domain}.
;mail   IN      CNAME   www.{domain}.
"""
    
    zone_filename = f"/etc/bind/db.{domain}"
    
    try:
        with open(zone_filename, 'w') as f:
            f.write(zone_content)
        print(f"✅ Zone file created: {zone_filename}")
        return zone_filename
    except Exception as e:
        print(f"❌ Error creating zone file: {e}")
        return None

def configure_bind_local(domain):
    """Configure Bind9 local configuration"""
    print("🔧 Configuring Bind9 local configuration...")
    
    local_content = f"""
// Forward zone for {domain}
zone "{domain}" {{
    type master;
    file "/etc/bind/db.{domain}";
    allow-transfer {{ any; }};
    allow-update {{ none; }};
}};

// Reverse zone (optional)
//zone "X.X.X.in-addr.arpa" {{
//    type master;
//    file "/etc/bind/db.reverse";
//}};
"""
    
    try:
        with open('/etc/bind/named.conf.local', 'w') as f:
            f.write(local_content)
        print("✅ Bind9 local configuration updated")
        return True
    except Exception as e:
        print(f"❌ Error configuring local: {e}")
        return False

def configure_resolv_conf(server_ip):
    """Configure local resolv.conf to use our DNS server"""
    print("🔧 Configuring resolv.conf...")
    
    resolv_content = f"""# Generated by DNS setup script
nameserver {server_ip}
nameserver 8.8.8.8
nameserver 1.1.1.1
options rotate
"""
    
    try:
        with open('/etc/resolv.conf', 'w') as f:
            f.write(resolv_content)
        print("✅ resolv.conf configured")
        return True
    except Exception as e:
        print(f"❌ Error configuring resolv.conf: {e}")
        return False

def test_bind_configuration():
    """Test Bind9 configuration"""
    print("🧪 Testing Bind9 configuration...")
    
    # Test configuration syntax
    stdout, stderr, returncode = run_command("named-checkconf")
    if returncode == 0:
        print("✅ named-checkconf passed")
    else:
        print("❌ named-checkconf failed")
        return False
    
    # Test zone files
    zone_files = [f for f in os.listdir('/etc/bind') if f.startswith('db.')]
    for zone_file in zone_files:
        domain = zone_file[3:]  # Remove 'db.' prefix
        stdout, stderr, returncode = run_command(f"named-checkzone {domain} /etc/bind/{zone_file}")
        if returncode == 0:
            print(f"✅ Zone check passed for {domain}")
        else:
            print(f"❌ Zone check failed for {domain}: {stderr}")
    
    return True

def restart_bind_service():
    """Restart Bind9 service"""
    print("🔄 Restarting Bind9 service...")
    
    commands = [
        "systemctl stop named",
        "systemctl start named",
        "systemctl status named --no-pager -l"
    ]
    
    for cmd in commands:
        stdout, stderr, returncode = run_command(cmd, check=False)
        if returncode != 0 and "stop" not in cmd:
            print(f"⚠️ Command may have failed: {cmd}")
    
    print("✅ Bind9 service restarted")
    return True

def test_dns_resolution(domain, server_ip):
    """Test DNS resolution"""
    print("🔍 Testing DNS resolution...")
    
    test_commands = [
        f"dig @{server_ip} {domain}",
        f"dig @{server_ip} www.{domain}",
        f"dig @{server_ip} ns1.{domain}",
        f"nslookup {domain} {server_ip}"
    ]
    
    for cmd in test_commands:
        print(f"\nTesting: {cmd}")
        stdout, stderr, returncode = run_command(cmd, check=False)
        if stdout:
            print(stdout[:200] + "..." if len(stdout) > 200 else stdout)
        if returncode != 0:
            print(f"⚠️ Test may have failed: {cmd}")
    
    return True

def configure_firewall():
    """Configure firewall for DNS"""
    print("🔥 Configuring firewall for DNS...")
    
    commands = [
        "ufw allow 53/tcp",    # DNS TCP
        "ufw allow 53/udp",    # DNS UDP
        "ufw allow 953/tcp",   # Bind9 control channel
        "ufw --force enable"   # Enable firewall
    ]
    
    for cmd in commands:
        stdout, stderr, returncode = run_command(cmd, check=False)
        if returncode != 0:
            print(f"⚠️ Firewall command may have failed: {cmd}")
    
    print("✅ Firewall configured for DNS")
    return True

def get_public_ip():
    """Get public IP address"""
    try:
        import requests
        response = requests.get('https://api.ipify.org', timeout=10)
        return response.text.strip()
    except:
        try:
            # Try internal methods
            stdout, stderr, returncode = run_command("hostname -I | awk '{print $1}'")
            if stdout:
                return stdout.strip()
        except:
            pass
        return "YOUR_SERVER_IP"

def main():
    """Main function"""
    print("🚀 Automatic Bind9 DNS Server Setup")
    print("=" * 50)
    
    # Check if running as root
    if os.geteuid() != 0:
        print("❌ This script must be run as root!")
        print("Please run: sudo python3 script.py")
        sys.exit(1)
    
    try:
        # Get user input
        domain = input("Enter your domain name (e.g., example.com): ").strip()
        if not domain:
            print("❌ Domain name is required!")
            sys.exit(1)
        
        ip_address = input(f"Enter server IP address (press Enter for auto-detection): ").strip()
        if not ip_address:
            ip_address = get_public_ip()
            print(f"Auto-detected IP: {ip_address}")
        
        # Confirm setup
        print(f"\nWill setup Bind9 for:")
        print(f"Domain: {domain}")
        print(f"IP Address: {ip_address}")
        confirm = input("Continue? (y/N): ").strip().lower()
        if confirm != 'y':
            print("Setup cancelled.")
            sys.exit(0)
        
        # Execute setup steps
        if not install_bind9():
            sys.exit(1)
        
        if not configure_bind_options():
            sys.exit(1)
        
        zone_file = create_zone_file(domain, ip_address)
        if not zone_file:
            sys.exit(1)
        
        if not configure_bind_local(domain):
            sys.exit(1)
        
        if not test_bind_configuration():
            sys.exit(1)
        
        if not restart_bind_service():
            sys.exit(1)
        
        if not configure_resolv_conf(ip_address):
            print("⚠️ resolv.conf configuration failed, continuing...")
        
        if not configure_firewall():
            print("⚠️ Firewall configuration failed, continuing...")
        
        test_dns_resolution(domain, ip_address)
        
        print("\n🎉 Bind9 DNS Server Setup Completed Successfully!")
        print("=" * 50)
        print(f"Domain: {domain}")
        print(f"IP Address: {ip_address}")
        print(f"Name Servers: ns1.{domain}, ns2.{domain}")
        print(f"Zone File: /etc/bind/db.{domain}")
        print("\n📋 Next steps:")
        print("1. Update your domain registrar's nameservers to point to this server")
        print("2. Test external DNS resolution")
        print("3. Consider setting up secondary DNS for redundancy")
        print("\n🔧 Management commands:")
        print("sudo systemctl status named")
        print("sudo journalctl -u named -f")
        print("sudo named-checkconf")
        print(f"sudo named-checkzone {domain} /etc/bind/db.{domain}")
        
    except KeyboardInterrupt:
        print("\n❌ Setup cancelled by user.")
        sys.exit(1)
    except Exception as e:
        print(f"❌ Unexpected error: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()