diff --git a/go.mod b/go.mod index bed7af3..90c8f74 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ectrc/snow go 1.21.3 require ( + github.com/beevik/etree v1.3.0 github.com/bwmarrin/discordgo v0.27.1 github.com/goccy/go-json v0.10.2 github.com/gofiber/contrib/websocket v1.3.0 diff --git a/go.sum b/go.sum index 9c410d2..4d8a81e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU= +github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/handlers/socket.go b/handlers/socket.go index 8cfd9a9..384d21c 100644 --- a/handlers/socket.go +++ b/handlers/socket.go @@ -1,6 +1,7 @@ package handlers import ( + "github.com/beevik/etree" "github.com/ectrc/snow/aid" "github.com/ectrc/snow/person" "github.com/gofiber/contrib/websocket" @@ -16,13 +17,39 @@ var ( type Socket struct { ID string + JID string Type SocketType Connection *websocket.Conn Person *person.Person } +type MessageToWrite struct { + Socket *Socket + Message []byte +} + +func (s *Socket) Write(message []byte) { + socketWriteQueue <- MessageToWrite{ + Socket: s, + Message: message, + } +} + +func (s *Socket) WriteTree(message *etree.Document) { + bytes, err := message.WriteToBytes() + if err != nil { + return + } + + socketWriteQueue <- MessageToWrite{ + Socket: s, + Message: bytes, + } +} + var ( - handles = map[SocketType]func(string) { + socketWriteQueue = make(chan MessageToWrite, 1000) + socketHandlers = map[SocketType]func(string) { SocketTypeXmpp: handlePresenceSocket, } @@ -56,19 +83,35 @@ func WebsocketConnection(c *websocket.Conn) { Type: protocol, Connection: c, }) - defer close(uuid) + defer func() { + socket, ok := sockets.Get(uuid) + if !ok { + return + } + socket.Connection.Close() + sockets.Delete(uuid) + aid.Print("(xmpp) connection closed", uuid) + }() - if handle, ok := handles[protocol]; ok { + if handle, ok := socketHandlers[protocol]; ok { handle(uuid) } } -func close(id string) { - socket, ok := sockets.Get(id) - if !ok { - return - } - socket.Connection.Close() - sockets.Delete(id) - aid.Print("(xmpp) connection closed", id) +func init() { + go func() { + for { + if aid.Config != nil { + break + } + } + + aid.Print("(socket) write queue started") + + for { + message := <-socketWriteQueue + aid.Print("(socket) message sent", message.Socket.ID, string(message.Message)) + message.Socket.Connection.WriteMessage(websocket.TextMessage, message.Message) + } + }() } \ No newline at end of file diff --git a/handlers/xmpp.go b/handlers/xmpp.go index 9946f5f..6bf51dd 100644 --- a/handlers/xmpp.go +++ b/handlers/xmpp.go @@ -1,6 +1,22 @@ package handlers -import "github.com/ectrc/snow/aid" +import ( + "fmt" + "strings" + "time" + + "github.com/beevik/etree" + "github.com/ectrc/snow/aid" + p "github.com/ectrc/snow/person" +) + +var ( + socketXmppMessageHandlers = map[string]func(*Socket, *etree.Document) error { + "open": presenceSocketOpenEvent, + "iq": presenceSocketIqRootEvent, + "close": presenceSocketCloseEvent, + } +) func handlePresenceSocket(id string) { aid.Print("(xmpp) connection opened", id) @@ -10,11 +26,138 @@ func handlePresenceSocket(id string) { } for { - _, msg, err := socket.Connection.ReadMessage() + _, message, err := socket.Connection.ReadMessage() if err != nil { - aid.Print("(xmpp) error reading message", err) break } - aid.Print("(xmpp) message received", string(msg)) + + parsed := etree.NewDocument() + if err := parsed.ReadFromBytes(message); err != nil { + return + } + + aid.Print("(xmpp) message received", string(message)) + + if handler, ok := socketXmppMessageHandlers[parsed.Root().Tag]; ok { + if err := handler(socket, parsed); err != nil { + aid.Print("(xmpp) connection closed", id, err.Error()) + return + } + } } +} + +func presenceSocketOpenEvent(socket *Socket, tree *etree.Document) error { + document := etree.NewDocument() + open := document.CreateElement("open") + open.Attr = append(open.Attr, etree.Attr{Key: "xmlns", Value: "urn:ietf:params:xml:ns:xmpp-framing"}) + open.Attr = append(open.Attr, etree.Attr{Key: "from", Value: "prod.ol.epicgames.com"}) + open.Attr = append(open.Attr, etree.Attr{Key: "version", Value: "1.0"}) + open.Attr = append(open.Attr, etree.Attr{Key: "id", Value: socket.ID}) + + socket.WriteTree(document) + return nil +} + +func presenceSocketCloseEvent(socket *Socket, tree *etree.Document) error { + return fmt.Errorf("safe exit") +} + +func presenceSocketIqRootEvent(socket *Socket, tree *etree.Document) error { + redirect := map[string]func(*Socket, *etree.Document) error{ + "set": presenceSocketIqSetEvent, + "get": presenceSocketIqGetEvent, + } + + if handler, ok := redirect[tree.Root().SelectAttr("type").Value]; ok { + if err := handler(socket, tree); err != nil { + return err + } + } + + return nil +} + +func presenceSocketIqSetEvent(socket *Socket, tree *etree.Document) error { + token := tree.Root().SelectElement("query").SelectElement("password") + if token == nil || token.Text() == "" { + return fmt.Errorf("invalid token") + } + real := strings.ReplaceAll(token.Text(), "eg1~", "") + + claims, err := aid.JWTVerify(real) + if err != nil { + return fmt.Errorf("invalid token") + } + + if claims["snow_id"] == nil { + return fmt.Errorf("invalid token") + } + + snowId, ok := claims["snow_id"].(string) + if !ok { + return fmt.Errorf("invalid token") + } + + person := p.Find(snowId) + if person == nil { + return fmt.Errorf("invalid token") + } + + socket.Person = person + socket.JID = person.ID + "@prod.ol.epicgames.com/" + tree.Root().SelectElement("query").SelectElement("resource").Text() + + document := etree.NewDocument() + iq := document.CreateElement("iq") + iq.Attr = append(iq.Attr, etree.Attr{Key: "type", Value: "result"}) + iq.Attr = append(iq.Attr, etree.Attr{Key: "id", Value: "_xmpp_auth1"}) + iq.Attr = append(iq.Attr, etree.Attr{Key: "from", Value: "prod.ol.epicgames.com"}) + + socket.WriteTree(document) + return nil +} + +func presenceSocketIqGetEvent(socket *Socket, tree *etree.Document) error { + document := etree.NewDocument() + iq := document.CreateElement("iq") + iq.Attr = append(iq.Attr, etree.Attr{Key: "type", Value: "result"}) + iq.Attr = append(iq.Attr, etree.Attr{Key: "id", Value: tree.Root().SelectAttr("id").Value}) + iq.Attr = append(iq.Attr, etree.Attr{Key: "to", Value: tree.Root().SelectAttr("from").Value}) + iq.Attr = append(iq.Attr, etree.Attr{Key: "from", Value: "prod.ol.epicgames.com"}) + ping := iq.CreateElement("ping") + ping.Attr = append(ping.Attr, etree.Attr{Key: "xmlns", Value: "urn:xmpp:ping"}) + + socket.WriteTree(document) + return nil +} + +func init() { + go func() { + for { + if aid.Config != nil { + break + } + } + + timer := time.NewTicker(30 * time.Second) + for { + <-timer.C + sockets.Range(func(key string, socket *Socket) bool { + if socket.Type != SocketTypeXmpp || socket.Person == nil { + return true + } + + document := etree.NewDocument() + iq := document.CreateElement("iq") + iq.Attr = append(iq.Attr, etree.Attr{Key: "id", Value: "_xmpp_auth1"}) + iq.Attr = append(iq.Attr, etree.Attr{Key: "type", Value: "get"}) + iq.Attr = append(iq.Attr, etree.Attr{Key: "from", Value: "prod.ol.epicgames.com"}) + ping := iq.CreateElement("ping") + ping.Attr = append(iq.Attr, etree.Attr{Key: "xmlns", Value: "urn:xmpp:ping"}) + + socket.WriteTree(document) + return true + }) + } + }() } \ No newline at end of file