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