main
44ceea7 · 1 month ago 7 commits
  1// Command tplinkctl logs in to a TP-Link router and runs encrypted API calls.
  2//
  3// Usage:
  4//
  5//	tplinkctl [flags] <path> [key=value ...]
  6//
  7// Examples:
  8//
  9//	export TPLINK_PASSWORD=secret
 10//	tplinkctl /admin/status?form=all operation=read
 11//	tplinkctl -host 192.168.0.1 /admin/wireless?form=guest operation=read
 12//
 13// The password is read from -password or the TPLINK_PASSWORD env var so it is
 14// never baked into the binary or shell history (use a leading space / env).
 15
 16//go:debug rsa1024min=0
 17
 18package main
 19
 20import (
 21	"encoding/json"
 22	"flag"
 23	"fmt"
 24	"net/url"
 25	"os"
 26	"strings"
 27	"golang.org/x/term"
 28)
 29
 30func main() {
 31	host := flag.String("host", env("TPLINK_HOST", "192.168.0.1"), "router host/IP")
 32	user := flag.String("user", env("TPLINK_USERNAME", "admin"), "login username")
 33	pass := flag.String("password", os.Getenv("TPLINK_PASSWORD"), "admin password (or set TPLINK_PASSWORD)")
 34	rgSec := flag.Bool("rgsec", os.Getenv("TPLINK_RG_SEC") == "1", "use SHA-256 credential hash (RG-SEC regions)")
 35	force := flag.Bool("force", os.Getenv("TPLINK_FORCE") == "1", "force login, evicting any existing session (confirm=true)")
 36	flag.Parse()
 37
 38	if *pass == "" {
 39		fmt.Fprint(os.Stderr, "Password: ")
 40		password, err := term.ReadPassword(int(os.Stdin.Fd()))
 41			if err != nil {
 42			fmt.Println("Error:", err)
 43			os.Exit(1)  	
 44		}
 45		*pass = string(password)
 46	}
 47
 48	args := flag.Args()
 49	if len(args) == 0 {
 50		// no path -> just prove login works and print the token
 51		args = []string{""}
 52	}
 53	path := args[0]
 54
 55	fields := url.Values{}
 56	for _, kv := range args[1:] {
 57		k, v, ok := strings.Cut(kv, "=")
 58		if !ok {
 59			fmt.Fprintf(os.Stderr, "error: bad key=value %q\n", kv)
 60			os.Exit(2)
 61		}
 62		fields.Set(k, v)
 63	}
 64	if path != "" && len(fields) == 0 {
 65		fields.Set("operation", "read") // sensible default for reads
 66	}
 67
 68	client := NewClient(Config{
 69		Host:     *host,
 70		Username: *user,
 71		Password: *pass,
 72		RGSec:    *rgSec,
 73		Force:    *force,
 74	})
 75
 76	if err := client.Login(); err != nil {
 77		fmt.Fprintf(os.Stderr, "login failed: %v\n", err)
 78		os.Exit(1)
 79	}
 80	fmt.Fprintf(os.Stderr, "login OK — stok=%s\n", client.Stok())
 81
 82	if path == "" {
 83		return
 84	}
 85
 86	data, err := client.Request(path, fields)
 87	if err != nil {
 88		fmt.Fprintf(os.Stderr, "request failed: %v\n", err)
 89		os.Exit(1)
 90	}
 91	fmt.Println(prettyJSON(data))
 92}
 93
 94func env(key, def string) string {
 95	if v := os.Getenv(key); v != "" {
 96		return v
 97	}
 98	return def
 99}
100
101func prettyJSON(raw json.RawMessage) string {
102	var v any
103	if err := json.Unmarshal(raw, &v); err != nil {
104		return string(raw)
105	}
106	out, err := json.MarshalIndent(v, "", "  ")
107	if err != nil {
108		return string(raw)
109	}
110	return string(out)
111}