-
Notifications
You must be signed in to change notification settings - Fork 4
/
mcgorcon.go
141 lines (124 loc) · 3.78 KB
/
mcgorcon.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
// Package mcgorcon is a Minecraft RCON Client written in Go.
// It is designed to be easy to use and integrate into your own applications.
package mcgorcon
import (
"bytes"
"encoding/binary"
"fmt"
"errors"
"io"
"net"
"time"
)
type packetType int32
// Client is a representation of an RCON client.
type Client struct {
password string
connection net.Conn
}
// header is the header of a Minecraft RCON packet.
type header struct {
Size int32
RequestID int32
PacketType packetType
}
const packetTypeCommand packetType = 2
const packetTypeAuth packetType = 3
const requestIDBadLogin int32 = -1
// Dial up the server and establish a RCON conneciton.
func Dial(host string, port int, pass string) (Client, error) {
// Combine the host and port to form the address.
address := host + ":" + fmt.Sprint(port)
// Actually establish the conneciton.
conn, err := net.DialTimeout("tcp", address, 10*time.Second)
if err != nil {
return Client{}, err
}
// Create the client object, since the connection has been established.
c := Client{password: pass, connection: conn}
// TODO - server validation to make sure we're talking to a real RCON server.
// For now, just return the client and assume it's a real server.
return c, nil
}
// SendCommand sends a command to the server and returns the result (often nothing).
func (c *Client) SendCommand(command string) (string, error) {
// Because I'm lazy, just authenticate with every command.
err := c.authenticate()
if err != nil {
return "", err
}
// Send the packet.
head, payload, err := c.sendPacket(packetTypeCommand, []byte(command))
if err != nil {
return "", err
}
// Auth was bad, throw error.
if head.RequestID == requestIDBadLogin {
return "", errors.New("Bad auth, could not send command.")
}
return string(payload), nil
}
// authenticate authenticates the user with the server.
func (c *Client) authenticate() error {
// Send the packet.
head, _, err := c.sendPacket(packetTypeAuth, []byte(c.password))
if err != nil {
return err
}
// If the credentials were bad, throw error.
if head.RequestID == requestIDBadLogin {
return errors.New("Bad auth, could not authenticate.")
}
return nil
}
// sendPacket sends the binary packet representation to the server and returns the response.
func (c *Client) sendPacket(t packetType, p []byte) (header, []byte, error) {
// Generate the binary packet.
packet, err := packetise(t, p)
if err != nil {
return header{}, nil, err
}
// Send the packet over the wire.
_, err = c.connection.Write(packet)
if err != nil {
return header{}, nil, err
}
// Receive and decode the response.
head, payload, err := depacketise(c.connection)
if err != nil {
return header{}, nil, err
}
return head, payload, nil
}
// packetise encodes the packet type and payload into a binary representation to send over the wire.
func packetise(t packetType, p []byte) ([]byte, error) {
// Generate a random request ID.
pad := [2]byte{}
length := int32(len(p) + 10)
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, length)
binary.Write(&buf, binary.LittleEndian, int32(0))
binary.Write(&buf, binary.LittleEndian, t)
binary.Write(&buf, binary.LittleEndian, p)
binary.Write(&buf, binary.LittleEndian, pad)
// Notchian server doesn't like big packets :(
if buf.Len() >= 1460 {
return nil, errors.New("Packet too big when packetising.")
}
// Return the bytes.
return buf.Bytes(), nil
}
// depacketise decodes the binary packet into a native Go struct.
func depacketise(r io.Reader) (header, []byte, error) {
head := header{}
err := binary.Read(r, binary.LittleEndian, &head)
if err != nil {
return header{}, nil, err
}
payload := make([]byte, head.Size-8)
_, err = io.ReadFull(r, payload)
if err != nil {
return header{}, nil, err
}
return head, payload[:len(payload)-2], nil
}