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}