#!/usr/bin/env python3
"""
server.py - Purple Team Exercise Data Collection Server
Simple HTTP server for receiving files during security assessment
Handles file uploads from PowerShell clients with basic token authentication
"""

import os
import sys
import socket
import threading
import json
from datetime import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import base64

# Configuration
SERVER_HOST = '0.0.0.0'  # Listen on all interfaces
SERVER_PORT = 8443       # Non-standard port to avoid detection
AUTH_TOKEN = 'PurpleTeam2024'  # Change this for your exercise
UPLOAD_DIR = '/tmp/exfil-data'  # Directory to store received files

class FileUploadHandler(BaseHTTPRequestHandler):
    """HTTP request handler for file uploads"""
    
    def do_GET(self):
        """Handle GET requests - basic server info"""
        if self.path == '/status':
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            
            status = {
                'status': 'ready',
                'timestamp': datetime.now().isoformat(),
                'upload_dir': UPLOAD_DIR,
                'files_received': len(os.listdir(UPLOAD_DIR)) if os.path.exists(UPLOAD_DIR) else 0
            }
            self.wfile.write(json.dumps(status).encode())
        else:
            self.send_response(404)
            self.end_headers()
    
    def do_POST(self):
        """Handle POST requests - file uploads"""
        try:
            # Verify authentication token
            auth_header = self.headers.get('Authorization', '')
            if not auth_header.startswith('Bearer '):
                self.send_error(401, 'Missing or invalid authorization')
                return
            
            token = auth_header.split(' ')[1]
            if token != AUTH_TOKEN:
                self.send_error(403, 'Invalid authentication token')
                return
            
            # Get content length
            content_length = int(self.headers.get('Content-Length', 0))
            if content_length == 0:
                self.send_error(400, 'No content provided')
                return
            
            # Parse the upload path
            if self.path.startswith('/upload/'):
                filename = self.path[8:]  # Remove '/upload/' prefix
                if not filename:
                    self.send_error(400, 'No filename specified')
                    return
            else:
                self.send_error(404, 'Invalid upload path')
                return
            
            # Read file content
            file_data = self.rfile.read(content_length)
            
            # Ensure upload directory exists
            os.makedirs(UPLOAD_DIR, mode=0o750, exist_ok=True)
            
            # Create unique filename with timestamp to avoid collisions
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            client_ip = self.client_address[0].replace('.', '_')
            safe_filename = f"{timestamp}_{client_ip}_{filename}"
            
            # Save file
            file_path = os.path.join(UPLOAD_DIR, safe_filename)
            with open(file_path, 'wb') as f:
                f.write(file_data)
            
            # Set appropriate file permissions
            os.chmod(file_path, 0o640)
            
            # Log the upload
            log_entry = f"[{datetime.now().isoformat()}] Received: {safe_filename} ({len(file_data)} bytes) from {self.client_address[0]}"
            print(log_entry)
            
            # Log to file as well
            log_file = os.path.join(UPLOAD_DIR, 'upload_log.txt')
            with open(log_file, 'a') as f:
                f.write(log_entry + '\n')
            
            # Send success response
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            
            response = {
                'status': 'success',
                'filename': safe_filename,
                'size': len(file_data),
                'timestamp': datetime.now().isoformat()
            }
            self.wfile.write(json.dumps(response).encode())
            
        except Exception as e:
            print(f"Error handling upload: {str(e)}")
            self.send_error(500, f'Internal server error: {str(e)}')
    
    def log_message(self, format, *args):
        """Override to customize logging"""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print(f"[{timestamp}] {self.client_address[0]} - {format % args}")

class TCPFileServer:
    """Alternative TCP-based file server for direct socket connections"""
    
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.socket = None
    
    def start(self):
        """Start the TCP server"""
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((self.host, self.port))
        self.socket.listen(5)
        
        print(f"TCP File Server listening on {self.host}:{self.port}")
        
        try:
            while True:
                client_socket, address = self.socket.accept()
                thread = threading.Thread(target=self.handle_client, args=(client_socket, address))
                thread.daemon = True
                thread.start()
        except KeyboardInterrupt:
            print("Shutting down TCP server...")
        finally:
            if self.socket:
                self.socket.close()
    
    def handle_client(self, client_socket, address):
        """Handle individual TCP client connections"""
        try:
            print(f"TCP connection from {address[0]}:{address[1]}")
            
            # Simple protocol: expect JSON header with file info
            header_size = int.from_bytes(client_socket.recv(4), 'big')
            header_data = client_socket.recv(header_size)
            header = json.loads(header_data.decode())
            
            # Verify auth token
            if header.get('token') != AUTH_TOKEN:
                client_socket.send(b'AUTH_FAILED')
                return
            
            filename = header.get('filename', 'unknown_file')
            filesize = header.get('size', 0)
            
            # Send acknowledgment
            client_socket.send(b'READY')
            
            # Receive file data
            received_data = b''
            remaining = filesize
            
            while remaining > 0:
                chunk = client_socket.recv(min(remaining, 4096))
                if not chunk:
                    break
                received_data += chunk
                remaining -= len(chunk)
            
            # Save file
            os.makedirs(UPLOAD_DIR, mode=0o750, exist_ok=True)
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            client_ip = address[0].replace('.', '_')
            safe_filename = f"{timestamp}_{client_ip}_{filename}"
            
            file_path = os.path.join(UPLOAD_DIR, safe_filename)
            with open(file_path, 'wb') as f:
                f.write(received_data)
            
            os.chmod(file_path, 0o640)
            
            # Send success confirmation
            client_socket.send(b'SUCCESS')
            
            log_entry = f"[{datetime.now().isoformat()}] TCP Received: {safe_filename} ({len(received_data)} bytes) from {address[0]}"
            print(log_entry)
            
            # Log to file
            log_file = os.path.join(UPLOAD_DIR, 'upload_log.txt')
            with open(log_file, 'a') as f:
                f.write(log_entry + '\n')
                
        except Exception as e:
            print(f"Error handling TCP client {address}: {str(e)}")
            try:
                client_socket.send(b'ERROR')
            except:
                pass
        finally:
            client_socket.close()

def main():
    """Main server function"""
    print("Purple Team Exercise - File Collection Server")
    print("=" * 50)
    print(f"HTTP Server: http://{SERVER_HOST}:{SERVER_PORT}")
    print(f"TCP Server: tcp://{SERVER_HOST}:{SERVER_PORT + 1}")
    print(f"Auth Token: {AUTH_TOKEN}")
    print(f"Upload Directory: {UPLOAD_DIR}")
    print("=" * 50)
    
    # Create upload directory if it doesn't exist
    os.makedirs(UPLOAD_DIR, mode=0o750, exist_ok=True)
    
    # Start HTTP server in a separate thread
    try:
        httpd = HTTPServer((SERVER_HOST, SERVER_PORT), FileUploadHandler)
        http_thread = threading.Thread(target=httpd.serve_forever)
        http_thread.daemon = True
        http_thread.start()
        print(f"HTTP server started on port {SERVER_PORT}")
        
        # Start TCP server in main thread
        tcp_server = TCPFileServer(SERVER_HOST, SERVER_PORT + 1)
        tcp_server.start()
        
    except KeyboardInterrupt:
        print("\nShutting down servers...")
        httpd.shutdown()
        sys.exit(0)
    except Exception as e:
        print(f"Error starting servers: {str(e)}")
        sys.exit(1)

if __name__ == '__main__':
    main()