• Tor Flipper

    From Yamn Remailer@noreply@mixmin.net to alt.privacy.anon-server on Sun Sep 27 10:04:17 2026
    From Newsgroup: alt.privacy.anon-server

    // tor-flipper.go
    //
    // Rotates the Tor exit node on a fixed or random interval, prints the current // exit IP, and optionally runs a command after each rotation.
    //
    // --------------------------------------------------------------------------- // BEFORE YOU RUN THIS PROGRAM
    // --------------------------------------------------------------------------- //
    // This tool talks to the Tor control port (default: 9051) and to the Tor SOCKS // proxy (default: 9050). The SOCKS proxy is opened by Tor automatically on port
    // 9050, even if you do NOT write "SocksPort 9050" in your torrc. You only need // to configure the control port and authentication explicitly.
    //
    // 1. Install Tor.
    // - Debian/Ubuntu: sudo apt install tor
    // - Fedora/RHEL: sudo dnf install tor
    // - macOS (brew): brew install tor
    // - Windows: install the Tor Expert Bundle (recommended for a
    // headless setup).
    //
    // 2. Edit the Tor configuration file (usually /etc/tor/torrc on Linux,
    // /usr/local/etc/tor/torrc on macOS with brew, or torrc next to the Tor
    // Expert Bundle on Windows) and make sure the following line is present
    // and NOT commented out:
    //
    // ControlPort 9051
    //
    // NOTE: You do NOT need to add "SocksPort 9050" explicitly. Tor opens a
    // SOCKS proxy on 127.0.0.1:9050 by default. Only set "SocksPort 0" if you // want to run Tor purely as a relay without any local application access. //
    // 3. Enable authentication on the control port. You have two options:
    //
    // a) Password authentication (what this program expects):
    // Generate a hashed password with Tor itself:
    //
    // tor --hash-password "yourPlaintextPassword"
    //
    // This prints a line like:
    //
    // 16:8A1F...ABCD
    //
    // Add that line to torrc, e.g.:
    //
    // HashedControlPassword 16:8A1F...ABCD
    //
    // IMPORTANT: When you start this program, you must pass the ORIGINAL // PLAINTEXT password ("yourPlaintextPassword") via -password, NOT the // hashed value above.
    //
    // b) Cookie authentication (not used by this program's default flow):
    // Tor writes a cookie file (CookieAuthentication 1). This tool does // not support that path out of the box; use option (a).
    //
    // 4. (Recommended) Add DNS-leak diagnostics to torrc:
    //
    // TestSocks 1 # Logs a NOTICE for safe (hostname) SOCKS requests
    // # and a WARNING for requests that leak DNS (IP
    // # addresses sent to the SOCKS port).
    // SafeSocks 1 # BLOCKS connections that would leak DNS. Strongly
    // # recommended, but may break applications that do
    // # their own DNS resolution. For this Go program it is // # safe, because it passes hostnames to Tor.
    //
    // 5. Make sure the control port is only reachable by you. By default it
    // listens on 127.0.0.1:9051, which is fine for local use.
    //
    // 6. Reload or restart Tor so the torrc changes take effect:
    //
    // sudo systemctl restart tor # systemd
    // sudo service tor restart # sysvinit
    //
    // 7. Quick sanity check that the control port is up:
    //
    // nc -vz 127.0.0.1 9051
    //
    // And that the SOCKS port answers (works without SocksPort in torrc):
    //
    // curl --socks5 127.0.0.1:9050 https://check.torproject.org/api/ip
    //
    // --------------------------------------------------------------------------- // IMPORTANT: TOR BROWSER vs. SYSTEM TOR / EXPERT BUNDLE
    // --------------------------------------------------------------------------- //
    // The default ports used by this program are:
    //
    // -proxy socks5://127.0.0.1:9050 (SOCKS proxy)
    // -port 9051 (control port)
    //
    // These defaults match a standalone Tor installation (system Tor, Tor Expert // Bundle, or a manually started tor process). They do NOT match the Tor Browser.
    //
    // The Tor Browser deliberately uses different ports to avoid conflicts with an // already running system Tor:
    //
    // Tor Browser SOCKS proxy: 127.0.0.1:9150
    // Tor Browser control port: 127.0.0.1:9151
    //
    // If you want to use this program together with the Tor Browser, you must:
    //
    // 1. Start the Tor Browser and wait until it has fully bootstrapped.
    // 2. Enable the control port in the Tor Browser's torrc (it is disabled by // default). This is possible but not officially supported and can break // on updates. The recommended approach is to use a standalone Tor daemon. // 3. Pass the alternate ports to this program, for example:
    //
    // ./tor-flipper -seconds 60 -password "..." \
    // -port 9151 -proxy socks5://127.0.0.1:9150
    //
    // 4. Be aware that the Tor Browser uses cookie authentication by default
    // (CookieAuthentication 1), not a plaintext password. This program only // implements password authentication (AUTHENTICATE "password"). You would // have to either switch the Tor Browser to HashedControlPassword or use // a standalone Tor instance.
    //
    // Because of these complications, it is strongly recommended to use a
    // standalone Tor daemon (system Tor or Tor Expert Bundle) with this tool.

    package main

    import (
    "bufio"
    "context"
    "flag"
    "fmt"
    "io"
    "math/rand"
    "net"
    "net/http"
    "net/url"
    "os"
    "os/exec"
    "os/signal"
    "path/filepath"
    "runtime"
    "runtime/debug"
    "strings"
    "syscall"
    "time"

    "golang.org/x/net/proxy"
    )

    func getCurrentIP(proxyAddr string, checkURL string) string {
    proxyURL, err := url.Parse(proxyAddr)
    if err != nil {
    return fmt.Sprintf("[!] Failed to parse proxy URL: %v", err)
    }
    dialer, err := proxy.FromURL(proxyURL, proxy.Direct)
    if err != nil {
    return fmt.Sprintf("[!] Failed to create proxy dialer: %v", err)
    }
    transport := &http.Transport{
    DisableKeepAlives: true,
    }
    if cd, ok := dialer.(proxy.ContextDialer); ok {
    transport.DialContext = cd.DialContext
    } else {
    transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
    return dialer.Dial(network, addr)
    }
    }
    defer transport.CloseIdleConnections()
    client := &http.Client{
    Transport: transport,
    Timeout: 15 * time.Second,
    }
    req, err := http.NewRequest("GET", checkURL, nil)
    if err != nil {
    return fmt.Sprintf("[!] Failed to create request: %v", err)
    }
    req.Header.Set("User-Agent", "curl/8.0")
    resp, err := client.Do(req)
    if err != nil {
    return fmt.Sprintf("[!] Failed to fetch IP: %v", err)
    }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    if err != nil {
    return fmt.Sprintf("[!] Failed to read response: %v", err)
    }
    return strings.TrimSpace(string(body))
    }

    func sendTorSignal(host string, port int, password string, signal string) error {
    addr := fmt.Sprintf("%s:%d", host, port)
    conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
    if err != nil {
    return fmt.Errorf("failed to connect to Tor control port %s: %v", addr, err)
    }
    defer conn.Close()

    reader := bufio.NewReader(conn)
    authCmd := fmt.Sprintf("AUTHENTICATE \"%s\"\r\n", password)
    if _, err = conn.Write([]byte(authCmd)); err != nil {
    return fmt.Errorf("failed to send AUTHENTICATE: %v", err)
    }
    resp, err := reader.ReadString('\n')
    if err != nil {
    return fmt.Errorf("failed to read AUTHENTICATE response: %v", err)
    }
    if !strings.HasPrefix(resp, "250") {
    return fmt.Errorf("authentication failed: %s", strings.TrimSpace(resp))
    }

    sigCmd := fmt.Sprintf("SIGNAL %s\r\n", signal)
    if _, err = conn.Write([]byte(sigCmd)); err != nil {
    return fmt.Errorf("failed to send SIGNAL: %v", err)
    }
    resp, err = reader.ReadString('\n')
    if err != nil {
    return fmt.Errorf("failed to read SIGNAL response: %v", err)
    }
    if !strings.HasPrefix(resp, "250") {
    return fmt.Errorf("signal command failed: %s", strings.TrimSpace(resp))
    }

    _, _ = conn.Write([]byte("QUIT\r\n"))
    return nil
    }

    func splitArgs(s string) []string {
    var args []string
    var cur strings.Builder
    inSingle := false
    inDouble := false
    escaped := false
    started := false

    for _, r := range s {
    if escaped {
    cur.WriteRune(r)
    escaped = false
    started = true
    continue
    }
    if r == '\\' && !inSingle {
    escaped = true
    started = true
    continue
    }
    if r == '\'' && !inDouble {
    inSingle = !inSingle
    started = true
    continue
    }
    if r == '"' && !inSingle {
    inDouble = !inDouble
    started = true
    continue
    }
    if (r == ' ' || r == '\t') && !inSingle && !inDouble {
    if started {
    args = append(args, cur.String())
    cur.Reset()
    started = false
    }
    continue
    }
    cur.WriteRune(r)
    started = true
    }
    if started {
    args = append(args, cur.String())
    }
    return args
    }

    func needsShell(s string) bool {
    for _, r := range s {
    switch r {
    case '|', '&', ';', '<', '>', '(', ')', '$', '`', '\n', '*', '?', '~':
    return true
    }
    }
    return false
    }

    func runCommand(command string) error {
    var cmd *exec.Cmd

    if runtime.GOOS == "windows" {
    cmd = exec.Command("cmd.exe", "/C", command)
    } else if needsShell(command) {
    cmd = exec.Command("sh", "-c", command)
    } else {
    args := splitArgs(command)
    if len(args) == 0 {
    return fmt.Errorf("empty command")
    }
    cmd = exec.Command(args[0], args[1:]...)
    }

    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Stdin = os.Stdin

    fmt.Printf("[>] Executing: %s\n", command)
    return cmd.Run()
    }

    func printUsageTo(w io.Writer, prog string) {
    fmt.Fprintf(w, "Usage: %s [OPTIONS] MODE\n", prog)
    fmt.Fprintf(w, "\n")
    fmt.Fprintf(w, "Rotate the Tor exit node at a fixed or random interval, print the\n")
    fmt.Fprintf(w, "current exit IP, and optionally run a command after each rotation.\n")
    fmt.Fprintf(w, "\n")
    fmt.Fprintf(w, "Mode (exactly one is required):\n")
    fmt.Fprintf(w, " -seconds N rotate every N seconds (fixed interval)\n")
    fmt.Fprintf(w, " -min N -max M rotate at a random interval between N and M seconds\n")
    fmt.Fprintf(w, "\n")
    fmt.Fprintf(w, "Options:\n")
    fmt.Fprintf(w, " -port PORT Tor control port (default: 9051)\n")
    fmt.Fprintf(w, " -password PW plaintext password for the Tor control port (required)\n")
    fmt.Fprintf(w, " -proxy URL SOCKS5 proxy address for Tor\n")
    fmt.Fprintf(w, " (default: socks5://127.0.0.1:9050)\n")
    fmt.Fprintf(w, " -u URL URL used to check the current exit IP\n")
    fmt.Fprintf(w, " (default: http://ip-api.com)\n")
    fmt.Fprintf(w, " -exec CMD command (with optional arguments) to run after each\n")
    fmt.Fprintf(w, " rotation. Quote the whole command, e.g.\n")
    fmt.Fprintf(w, " -exec \"date +%%s\" or -exec \"curl -s URL\"\n")
    fmt.Fprintf(w, " -version print version information and exit\n")
    fmt.Fprintf(w, " -h, -help show this help and exit\n")
    fmt.Fprintf(w, "\n")
    fmt.Fprintf(w, "Examples:\n")
    fmt.Fprintf(w, " %s -seconds 60 -password secret\n", prog)
    fmt.Fprintf(w, " %s -min 30 -max 120 -password secret\n", prog)
    fmt.Fprintf(w, " %s -seconds 300 -password secret -exec \"./reload-app.sh\"\n", prog)
    fmt.Fprintf(w, " %s -seconds 300 -password secret -exec \"systemctl restart myapp\"\n", prog)
    fmt.Fprintf(w, "\n")
    fmt.Fprintf(w, "Notes:\n")
    fmt.Fprintf(w, " The password must be the plaintext password, not the hashed value\n")
    fmt.Fprintf(w, " from the torrc. The Tor control port must be enabled with a\n")
    fmt.Fprintf(w, " HashedControlPassword.\n")
    fmt.Fprintf(w, "\n")
    fmt.Fprintf(w, " Simple commands with arguments are executed directly (no shell).\n")
    fmt.Fprintf(w, " Shell metacharacters (|, &, ;, <, >, $, `, *, ?) trigger a\n")
    fmt.Fprintf(w, " shell fallback so pipes and redirects still work.\n") }

    func printVersion(w io.Writer, prog string) {
    version := "unknown"
    commit := "unknown"
    date := "unknown"
    dirty := ""

    if bi, ok := debug.ReadBuildInfo(); ok {
    if bi.Main.Version != "" && bi.Main.Version != "(devel)" {
    version = bi.Main.Version
    }
    for _, s := range bi.Settings {
    switch s.Key {
    case "vcs.revision":
    commit = s.Value
    case "vcs.time":
    date = s.Value
    case "vcs.modified":
    if s.Value == "true" {
    dirty = "-dirty"
    }
    }
    }
    }

    fmt.Fprintf(w, "%s %s%s\n", prog, version, dirty)
    fmt.Fprintf(w, "commit: %s\n", commit)
    fmt.Fprintf(w, "built: %s\n", date)
    fmt.Fprintf(w, "go: %s\n", runtime.Version())
    fmt.Fprintf(w, "os/arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
    }

    func main() {
    seconds := flag.Int("seconds", 0, "Fixed interval in seconds to rotate Tor exit node")
    minSec := flag.Int("min", 0, "Minimum interval in seconds for random rotation")
    maxSec := flag.Int("max", 0, "Maximum interval in seconds for random rotation")
    port := flag.Int("port", 9051, "Tor control port (default: 9051)")
    password := flag.String("password", "", "Plaintext password for Tor control port authentication (required)")
    proxyAddr := flag.String("proxy", "socks5://127.0.0.1:9050", "SOCKS5 proxy address for Tor")
    checkURL := flag.String("u", "http://ip-api.com", "URL to check the current IP")
    execCmd := flag.String("exec", "", "Cross-platform command/script to execute after circuit change")
    showVersion := flag.Bool("version", false, "print version information and exit")

    prog := filepath.Base(os.Args[0])
    flag.CommandLine.Init(prog, flag.ContinueOnError)
    flag.CommandLine.SetOutput(os.Stderr)
    flag.Usage = func() {}

    if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
    if err == flag.ErrHelp {
    printUsageTo(os.Stdout, prog)
    os.Exit(0)
    }
    fmt.Fprintf(os.Stderr, "%s: %v\n", prog, err)
    printUsageTo(os.Stderr, prog)
    os.Exit(2)
    }

    if *showVersion {
    printVersion(os.Stdout, prog)
    os.Exit(0)
    }

    useRandom := (*minSec > 0 && *maxSec > 0)
    useFixed := (*seconds > 0)

    if !useRandom && !useFixed {
    fmt.Fprintln(os.Stderr, "[!] You must provide either -seconds OR both -min and -max")
    printUsageTo(os.Stderr, prog)
    os.Exit(1)
    }
    if useRandom && *minSec >= *maxSec {
    fmt.Fprintln(os.Stderr, "[!] -min must be strictly less than -max")
    os.Exit(1)
    }
    if *password == "" {
    fmt.Fprintln(os.Stderr, "[!] -password is required (must be PLAINTEXT, not the hashed value!)")
    printUsageTo(os.Stderr, prog)
    os.Exit(1)
    }

    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    fmt.Printf("[+] Authenticated to Tor control port on port %d\n", *port)
    if useRandom {
    fmt.Printf("[+] Tor exit node rotator started (random interval: %ds - %ds)\n", *minSec, *maxSec)
    } else {
    fmt.Printf("[+] Tor exit node rotator started (fixed interval: %ds)\n", *seconds)
    }
    fmt.Printf("[+] Using proxy: %s\n", *proxyAddr)
    fmt.Printf("[+] Checking IP via: %s\n", *checkURL)
    if *execCmd != "" {
    fmt.Printf("[+] Auto-Exec enabled: %s\n", *execCmd)
    }
    fmt.Println()
    fmt.Println("[i] Press Ctrl+C to stop gracefully.")

    loop:
    for {
    var waitTime int
    if useRandom {
    waitTime = rand.Intn(*maxSec-*minSec+1) + *minSec
    } else {
    waitTime = *seconds
    }

    err := sendTorSignal("127.0.0.1", *port, *password, "NEWNYM")
    if err != nil {
    fmt.Printf("[!] Error: %v\n", err)
    } else {
    fmt.Println("[+] Sent signal to rotate Tor circuit.")
    }

    select {
    case <-ctx.Done():
    break loop
    case <-time.After(5 * time.Second):
    }

    newIP := getCurrentIP(*proxyAddr, *checkURL)
    fmt.Printf("[+] Current Tor exit IP: %s\n", newIP)

    if *execCmd != "" {
    if err := runCommand(*execCmd); err != nil {
    fmt.Printf("[!] Exec failed: %v\n", err)
    } else {
    fmt.Println("[+] Exec finished successfully.")
    }
    }

    fmt.Printf("[i] Waiting %d seconds before next rotation...\n\n", waitTime)

    select {
    case <-ctx.Done():
    break loop
    case <-time.After(time.Duration(waitTime) * time.Second):
    }
    }

    fmt.Println()
    fmt.Println("[+] Shutdown signal received. Exiting gracefully. Goodbye.")
    }
    --- Synchronet 3.22a-Linux NewsLink 1.2