-
Notifications
You must be signed in to change notification settings - Fork 0
/
rcon.go
122 lines (99 loc) · 2.68 KB
/
rcon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package rcon
import (
"fmt"
"net"
"strings"
"time"
"github.com/Sch8ill/rcon/config"
)
type ConnectionState int
// an "enum" that represents the current connection state
const (
Connected ConnectionState = iota
Disconnected
Authenticated
)
type RconClient struct {
Address string
Password string
Timeout time.Duration
Conn net.Conn
ConnState ConnectionState
}
// creates a RCON client and establishes a connection
func Dial(addr string, password string, timeout time.Duration) (*RconClient, error) {
rconClient := NewClient(addr, password, timeout)
if err := rconClient.Connect(); err != nil {
return nil, err
}
if err := rconClient.Authenticate(); err != nil {
return nil, err
}
return rconClient, nil
}
// creates a new RCON client
func NewClient(addr string, password string, timeout time.Duration) *RconClient {
return &RconClient{
Address: addr,
Password: password,
Timeout: timeout,
ConnState: Disconnected,
}
}
// establishes the underlying TCP connenction of the RCON client
func (rc *RconClient) Connect() error {
if !strings.Contains(rc.Address, ":") {
rc.Address = rc.Address + ":" + fmt.Sprint(config.DefaultPort)
}
if rc.ConnState != Disconnected {
return errAlreadyConnected
}
conn, err := net.DialTimeout("tcp", rc.Address, rc.Timeout)
if err != nil {
return err
}
rc.ConnState = Connected
rc.Conn = conn
return nil
}
// authenticates the client using the clients password
func (rc *RconClient) Authenticate() error {
authPacket := newServerBoundPacket(SERVERDATA_AUTH, rc.Password)
rc.Conn.Write(authPacket.Bytes())
resPacket, err := newClientBoundPacket(rc.Conn)
if err != nil {
return err
}
if resPacket.ID == -1 {
// packet id -1 means authentication failed
return errAuthenticationFailed
}
rc.ConnState = Authenticated
return nil
}
// executes a command on the remote server
func (rc *RconClient) ExecuteCmd(cmd string) (string, error) {
// check if the client is connected and authenticated
if rc.ConnState != Authenticated {
return "", errWrongConenctionState
}
// construct the packet and write it to the socket
cmdPacket := newServerBoundPacket(SERVERDATA_EXECCOMMAND, cmd)
rc.Conn.Write(cmdPacket.Bytes())
// parse the received packet
resPacket, err := newClientBoundPacket(rc.Conn)
if err != nil {
return "", err
}
// the response packet has to have the same packet id as the request packet
if cmdPacket.ID != resPacket.ID {
return "", errWrongPacketID
}
return resPacket.getBody(), nil
}
// "closes" the RCON client by terminating the underlying TCP connection
// and setting the clients connection state to disconnected
func (rc *RconClient) Close() {
rc.Conn.Close()
rc.ConnState = Disconnected
}