commit 396c9d5b1639765399da1337596a63f49b3c7a4d Author: Michael Bang Date: Sat Apr 11 15:40:41 2015 +0200 Initial commit. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6c430c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Bang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3f8284 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +This is a very simple minecraft (and source engine?) RCON client. See [cli/main.go](cli/main.go) for an example on how to use it. + +# Shortcomings +- Long (split) responses aren't handled correctly. +- Probably a lot more. + + +# License +See the [LICENSE](LICENSE) file. + diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..8abfd7a --- /dev/null +++ b/cli/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/katnegermis/pocketmine-rcon" +) + +func main() { + if len(os.Args) < 2 { + fmt.Printf("Usage: ./rcon address password") + return + } + addr := os.Args[1] + pass := os.Args[2] + + conn, err := rcon.NewConnection(addr, pass) + if err != nil { + fmt.Println(err) + return + } + fmt.Printf("Successfully logged in at %s!\n", addr) + + prompt() + stdin := bufio.NewReader(os.Stdin) + input := "" + for { + if input, err = stdin.ReadString('\n'); err != nil { + fmt.Println(err) + return + } + input = strings.Trim(input[:len(input)-1], " ") + if input == ".exit" { + break + } + if len(input) == 0 { + prompt() + continue + } + r := conn.SendCommand(input) + fmt.Printf("Server:\n%s\n", r) + prompt() + } +} + +func prompt() { + fmt.Print("Enter command:\n>") +} diff --git a/connection.go b/connection.go new file mode 100644 index 0000000..30583f9 --- /dev/null +++ b/connection.go @@ -0,0 +1,118 @@ +package rcon + +import ( + "bytes" + "encoding/binary" + "errors" + "log" + "net" +) + +type Connection struct { + conn net.Conn + pass string + addr string +} + +var uniqueId int32 = 0 + +func NewConnection(addr, pass string) (*Connection, error) { + conn, err := net.Dial("tcp", addr) + if err != nil { + log.Fatal(err) + } + c := &Connection{conn: conn, pass: pass, addr: addr} + if err := c.auth(); err != nil { + return nil, err + } + return c, nil +} + +func (c *Connection) SendCommand(cmd string) string { + c.sendCommand(2, []byte(cmd)) + pkg := c.readPkg() + return string(pkg.Body) +} + +func (c *Connection) auth() error { + c.sendCommand(3, []byte(c.pass)) + pkg := c.readPkg() + if pkg.Type != 2 || pkg.Id != uniqueId { + return errors.New("Incorrect password.") + } + return nil +} + +func (c *Connection) sendCommand(typ int32, body []byte) { + size := int32(4 + 4 + len(body) + 2) + uniqueId += 1 + id := uniqueId + + wtr := binaryReadWriter{ByteOrder: binary.LittleEndian} + wtr.Write(size) + wtr.Write(id) + wtr.Write(typ) + wtr.Write(body) + wtr.Write([]byte{0x0, 0x0}) + if wtr.err != nil { + log.Fatal(wtr.err) + } + + c.conn.Write(wtr.buf.Bytes()) +} + +func (c *Connection) readPkg() Pkg { + const bufSize = 4096 + b := make([]byte, bufSize) + + // Doesn't handle split messages correctly. + read, err := c.conn.Read(b) + if err != nil { + log.Fatal(err) + } + + p := Pkg{} + rdr := binaryReadWriter{ByteOrder: binary.LittleEndian, + buf: bytes.NewBuffer(b)} + rdr.Read(&p.Size) + rdr.Read(&p.Id) + rdr.Read(&p.Type) + body := [bufSize - 12]byte{} + rdr.Read(&body) + if rdr.err != nil { + log.Fatal(rdr.err) + } + p.Body = body[:read-12] + return p +} + +type Pkg struct { + Size int32 + Id int32 + Type int32 + Body []byte + _null int16 +} + +type binaryReadWriter struct { + ByteOrder binary.ByteOrder + err error + buf *bytes.Buffer +} + +func (b *binaryReadWriter) Write(v interface{}) { + if b.err != nil { + return + } + if b.buf == nil { + b.buf = new(bytes.Buffer) + } + b.err = binary.Write(b.buf, b.ByteOrder, v) +} + +func (b *binaryReadWriter) Read(v interface{}) { + if b.err != nil || b.buf == nil { + return + } + binary.Read(b.buf, b.ByteOrder, v) +}