-
Notifications
You must be signed in to change notification settings - Fork 13
/
rcon.go
207 lines (175 loc) · 6.11 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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Package rcon implements the communication protocol for communicating
// with RCON servers. Tested and working with Valve game servers.
package rcon
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"net"
"strings"
)
const (
PacketPaddingSize uint8 = 2 // Size of Packet's padding.
PacketHeaderSize uint8 = 8 // Size of Packet's header.
)
const (
TerminationSequence = "\x00" // Null empty ASCII string suffix.
)
// Packet type constants.
// https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Packet_Type
const (
Exec int32 = 2
Auth int32 = 3
AuthResponse int32 = 2
ResponseValue int32 = 0
)
// Rcon package errors.
var (
ErrInvalidWrite = fmt.Errorf("failed to write the payload correctly to remote connection")
ErrInvalidRead = fmt.Errorf("failed to read the response correctly from remote connection")
ErrInvalidChallenge = fmt.Errorf("server failed to mirror request challenge")
ErrUnauthorizedRequest = fmt.Errorf("client not authorized to remote server")
ErrFailedAuthorization = fmt.Errorf("failed to authorize to the remote server")
)
type Client struct {
Host string // The IP address of the remote server.
Port int // The Port the remote server's listening on.
Authorized bool // Has the client been authorized by the server?
Connection net.Conn // The TCP connection to the server.
}
type Header struct {
Size int32 // The size of the payload.
Challenge int32 // The challenge ths server should mirror.
Type int32 // The type of request being sent.
}
type Packet struct {
Header Header // Packet header.
Body string // Body of packet.
}
// Compile converts a packets header and body into its appropriate
// byte array payload, returning an error if the binary packages
// Write method fails to write the header bytes in their little
// endian byte order.
func (p Packet) Compile() (payload []byte, err error) {
var size int32 = p.Header.Size
var buffer bytes.Buffer
var padding [PacketPaddingSize]byte
if err = binary.Write(&buffer, binary.LittleEndian, &size); nil != err {
return
} else if err = binary.Write(&buffer, binary.LittleEndian, &p.Header.Challenge); nil != err {
return
} else if err = binary.Write(&buffer, binary.LittleEndian, &p.Header.Type); nil != err {
return
}
buffer.WriteString(p.Body)
buffer.Write(padding[:])
return buffer.Bytes(), nil
}
// NewPacket returns a pointer to a new Packet type.
func NewPacket(challenge, typ int32, body string) (packet *Packet) {
size := int32(len([]byte(body)) + int(PacketHeaderSize+PacketPaddingSize))
return &Packet{Header{size, challenge, typ}, body}
}
// Authorize calls Send with the appropriate command type and the provided
// password. The response packet is returned if authorization is successful
// or a potential error.
func (c *Client) Authorize(password string) (response *Packet, err error) {
if response, err = c.Send(Auth, password); nil == err {
if response.Header.Type == AuthResponse {
c.Authorized = true
} else {
err = ErrFailedAuthorization
response = nil
return
}
}
return
}
// Execute calls Send with the appropriate command type and the provided
// command. The response packet is returned if the command executed successfully
// or a potential error.
func (c *Client) Execute(command string) (response *Packet, err error) {
return c.Send(Exec, command)
}
// Sends accepts the commands type and its string to execute to the clients server,
// creating a packet with a random challenge id for the server to mirror,
// and compiling its payload bytes in the appropriate order. The response is
// decompiled from its bytes into a Packet type for return. An error is returned
// if send fails.
func (c *Client) Send(typ int32, command string) (response *Packet, err error) {
if typ != Auth && !c.Authorized {
err = ErrUnauthorizedRequest
return
}
// Create a random challenge for the server to mirror in its response.
var challenge int32
_ = binary.Read(rand.Reader, binary.LittleEndian, &challenge)
// Create the packet from the challenge, typ and command
// and compile it to its byte payload
packet := NewPacket(challenge, typ, command)
payload, err := packet.Compile()
var n int
if nil != err {
return
} else if n, err = c.Connection.Write(payload); nil != err {
return
} else if n != len(payload) {
err = ErrInvalidWrite
return
}
var header Header
if err = binary.Read(c.Connection, binary.LittleEndian, &header.Size); nil != err {
return
} else if err = binary.Read(c.Connection, binary.LittleEndian, &header.Challenge); nil != err {
return
} else if err = binary.Read(c.Connection, binary.LittleEndian, &header.Type); nil != err {
return
}
if packet.Header.Type == Auth && header.Type == ResponseValue {
// Discard, empty SERVERDATA_RESPONSE_VALUE from authorization.
_, _ = c.Connection.Read(make([]byte, header.Size-int32(PacketHeaderSize)))
// Reread the packet header.
if err = binary.Read(c.Connection, binary.LittleEndian, &header.Size); nil != err {
return
} else if err = binary.Read(c.Connection, binary.LittleEndian, &header.Challenge); nil != err {
return
} else if err = binary.Read(c.Connection, binary.LittleEndian, &header.Type); nil != err {
return
}
}
if header.Challenge != packet.Header.Challenge {
err = ErrInvalidChallenge
return
}
body := make([]byte, header.Size-int32(PacketHeaderSize))
n, err = c.Connection.Read(body)
for n < len(body) {
var nBytes int
nBytes, err = c.Connection.Read(body[n:])
if err != nil {
return
}
n += nBytes
}
if nil != err {
return
} else if n != len(body) {
err = ErrInvalidRead
return
}
response = new(Packet)
response.Header = header
response.Body = strings.TrimRight(string(body), TerminationSequence)
return
}
// NewClient creates a new Client type, creating the connection
// to the server specified by the host and port arguments. If
// the connection fails, an error is returned.
func NewClient(host string, port int) (client *Client, err error) {
client = new(Client)
client.Host = host
client.Port = port
client.Connection, err = net.Dial("tcp", fmt.Sprintf("%v:%v", client.Host, client.Port))
return
}