Skip to content

Commit

Permalink
Add Votifier 2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
PassTheMayo committed Feb 11, 2024
1 parent d306e90 commit d86be25
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 1 deletion.
7 changes: 6 additions & 1 deletion config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,9 @@ bedrock_edition:
random: false
min: 0
max: 0
gamemode: survival
gamemode: survival
votifier:
enable: true
host: 0.0.0.0
port: 8192
token: abc123
6 changes: 6 additions & 0 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ type Config struct {
Gamemode string `yaml:"gamemode"`
} `yaml:"options"`
} `yaml:"bedrock_edition"`
Votifier struct {
Enable bool `yaml:"enable"`
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
Token string `yaml:"token"`
} `yaml:"votifier"`
}

// ReadFile reads the YAML file from the path.
Expand Down
15 changes: 15 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"main/src/java"
"main/src/query"
"main/src/util"
"main/src/vote"
)

var (
Expand Down Expand Up @@ -47,6 +48,14 @@ func init() {
log.Printf("Listening for query connections on %s:%d\n", conf.JavaEdition.Query.Host, conf.JavaEdition.Query.Port)
}

if conf.Votifier.Enable {
if err = vote.Listen(conf); err != nil {
log.Fatal(err)
}

log.Printf("Listening for Votifier on %s:%d\n", conf.Votifier.Host, conf.Votifier.Port)
}

util.SetSamplePlayers(conf.JavaEdition.Options.Players.Sample)
}

Expand All @@ -69,6 +78,12 @@ func main() {
go query.AcceptConnections()
}

if conf.Votifier.Enable {
defer vote.Close()

go vote.AcceptConnections()
}

s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt)
<-s
Expand Down
114 changes: 114 additions & 0 deletions src/vote/protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package vote

import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"io"
)

type voteMessage struct {
Payload string `json:"payload"`
Signature string `json:"signature"`
}

type votePayload struct {
ServiceName string `json:"serviceName"`
Username string `json:"username"`
Timestamp int64 `json:"timestamp"`
Challenge string `json:"challenge"`
UUID string `json:"uuid"`
}

type voteResponse struct {
Status string `json:"status"`
Error string `json:"error"`
}

func sendHandshake(w io.Writer, challenge string) error {
_, err := w.Write([]byte(fmt.Sprintf("VOTIFIER 2 %s\n", challenge)))

return err
}

func readPayload(r io.Reader, challenge string) error {
var (
identifier int16
messageLength int16
)

if err := binary.Read(r, binary.BigEndian, &identifier); err != nil {
return err
}

if identifier != 0x733A {
return fmt.Errorf("invalid identifier: %X", identifier)
}

if err := binary.Read(r, binary.BigEndian, &messageLength); err != nil {
return err
}

data := make([]byte, messageLength)

if _, err := r.Read(data); err != nil {
return err
}

var message voteMessage

if err := json.Unmarshal(data, &message); err != nil {
return err
}

var payload votePayload

if err := json.Unmarshal([]byte(message.Payload), &payload); err != nil {
return err
}

if payload.Challenge != challenge {
return fmt.Errorf("unexpected challenge: %s", payload.Challenge)
}

hash := hmac.New(sha256.New, []byte(conf.Votifier.Token))
hash.Write([]byte(message.Payload))

if base64.StdEncoding.EncodeToString(hash.Sum(nil)) != message.Signature {
return fmt.Errorf("invalid message signature (is the token wrong?): %s", message.Signature)
}

return nil
}

func writeResponse(w io.Writer) error {
data, err := json.Marshal(voteResponse{
Status: "ok",
})

if err != nil {
return err
}

_, err = w.Write(append(data, '\n'))

return err
}

func writeError(w io.Writer, message string) error {
data, err := json.Marshal(voteResponse{
Status: "error",
Error: message,
})

if err != nil {
return err
}

_, err = w.Write(append(data, '\n'))

return err
}
64 changes: 64 additions & 0 deletions src/vote/socket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package vote

import (
"fmt"
"net"

"main/src/config"
)

var (
socket net.Listener = nil
conf *config.Config = nil
)

// Listen creates a new TCP socket server using the address specified in the configuration file.
func Listen(c *config.Config) (err error) {
conf = c

socket, err = net.Listen("tcp", fmt.Sprintf("%s:%d", c.Votifier.Host, c.Votifier.Port))

return
}

// Close closes the socket server.
func Close() error {
return socket.Close()
}

// AcceptConnections should be started in a Goroutine and accepts new connections from the socket server.
func AcceptConnections() {
for {
conn, err := socket.Accept()

if err != nil {
continue
}

go handleConnection(conn)
}
}

func handleConnection(conn net.Conn) {
defer conn.Close()

challenge := generateChallenge()

if err := sendHandshake(conn, challenge); err != nil {
writeError(conn, err.Error())

return
}

if err := readPayload(conn, challenge); err != nil {
writeError(conn, err.Error())

return
}

if err := writeResponse(conn); err != nil {
writeError(conn, err.Error())

return
}
}
16 changes: 16 additions & 0 deletions src/vote/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package vote

import (
"crypto/rand"
"encoding/hex"
)

func generateChallenge() string {
data := make([]byte, 8)

if _, err := rand.Read(data); err != nil {
panic(err)
}

return hex.EncodeToString(data)
}

0 comments on commit d86be25

Please sign in to comment.