master
go 109 lines 2.37 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package tor
4
5 import (
6 "bytes"
7 "errors"
8 "fmt"
9 "strings"
10
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/socket"
12 )
13
14 // https://spec.torproject.org/control-spec/index.html
15 // https://github.com/torproject/stem/blob/master/stem/control.py
16
17 const (
18 cmdAuthenticate = "AUTHENTICATE"
19 cmdQuit = "QUIT"
20 cmdGetInfo = "GETINFO"
21 )
22
23 type controlConn interface {
24 connect() error
25 disconnect()
26
27 getInfo(...string) ([]byte, error)
28 }
29
30 func newControlConn(conf Config) controlConn {
31 return &torControlClient{
32 password: conf.Password,
33 conn: socket.New(socket.Config{
34 Address: conf.Address,
35 Timeout: conf.Timeout.Duration(),
36 })}
37 }
38
39 type torControlClient struct {
40 password string
41 conn socket.Client
42 }
43
44 func (c *torControlClient) connect() error {
45 if err := c.conn.Connect(); err != nil {
46 return err
47 }
48
49 return c.authenticate()
50 }
51
52 func (c *torControlClient) authenticate() error {
53 // https://spec.torproject.org/control-spec/commands.html#authenticate
54
55 cmd := cmdAuthenticate
56 if c.password != "" {
57 cmd = fmt.Sprintf("%s \"%s\"", cmdAuthenticate, c.password)
58 }
59
60 var s string
61 err := c.conn.Command(cmd+"\n", func(bs []byte) (bool, error) {
62 s = string(bs)
63 return false, nil
64 })
65 if err != nil {
66 return fmt.Errorf("authentication failed: %v", err)
67 }
68 if !strings.HasPrefix(s, "250") {
69 return fmt.Errorf("authentication failed: %s", s)
70 }
71 return nil
72 }
73
74 func (c *torControlClient) disconnect() {
75 // https://spec.torproject.org/control-spec/commands.html#quit
76
77 _ = c.conn.Command(cmdQuit+"\n", func(bs []byte) (bool, error) { return false, nil })
78 _ = c.conn.Disconnect()
79 }
80
81 func (c *torControlClient) getInfo(keywords ...string) ([]byte, error) {
82 // https://spec.torproject.org/control-spec/commands.html#getinfo
83
84 if len(keywords) == 0 {
85 return nil, errors.New("no keywords specified")
86 }
87 cmd := fmt.Sprintf("%s %s", cmdGetInfo, strings.Join(keywords, " "))
88
89 var buf bytes.Buffer
90
91 if err := c.conn.Command(cmd+"\n", func(bs []byte) (bool, error) {
92 s := string(bs)
93
94 switch {
95 case strings.HasPrefix(s, "250-"):
96 buf.WriteString(strings.TrimPrefix(s, "250-"))
97 buf.WriteByte('\n')
98 return true, nil
99 case strings.HasPrefix(s, "250 "):
100 return false, nil
101 default:
102 return false, errors.New(s)
103 }
104 }); err != nil {
105 return nil, fmt.Errorf("command '%s' failed: %v", cmd, err)
106 }
107
108 return buf.Bytes(), nil
109 }