From 384ae29dd83b63817c28db834d90baeb1565e82d Mon Sep 17 00:00:00 2001 From: Failure Date: Wed, 24 Jul 2024 17:01:20 -0700 Subject: [PATCH 1/8] import changes --- cmd/grumble/client.go | 34 ++++---- cmd/grumble/message.go | 5 +- cmd/grumble/server.go | 164 ++++++++++++++++++++++++++++--------- cmd/grumble/util.go | 8 ++ cmd/grumble/voicetarget.go | 2 +- go.mod | 1 + go.sum | 13 +-- 7 files changed, 159 insertions(+), 68 deletions(-) create mode 100644 cmd/grumble/util.go diff --git a/cmd/grumble/client.go b/cmd/grumble/client.go index a2f535a..27cd54b 100644 --- a/cmd/grumble/client.go +++ b/cmd/grumble/client.go @@ -90,6 +90,9 @@ type Client struct { Recording bool PluginContext []byte PluginIdentity string + + // CEF + ircChannel string } // Debugf implements debug-level printing for Clients. @@ -187,7 +190,7 @@ func (client *Client) disconnect(kicked bool) { // // In case of a premature disconnect, close the channel so the // receiver routine can exit correctly. - if client.state == StateClientSentVersion || client.state == StateClientAuthenticated { + if (client.state == StateClientSentVersion || client.state == StateClientAuthenticated) && client.clientReady != nil { close(client.clientReady) } @@ -588,6 +591,20 @@ func (client *Client) sendChannelList() { client.sendChannelTree(client.server.RootChannel()) } +func (client *Client) sendWindowedChannel() *Channel { + channel, exists := client.server.WindowedChannels[client.ircChannel] + if !exists { + channel = NewChannel(client.server.nextChanId, client.ircChannel) + channel.temporary = true + + client.server.nextChanId++ + client.server.WindowedChannels[client.ircChannel] = channel + log.Print("Made new channel") + } + client.sendChannelTree(channel) + return channel +} + func (client *Client) sendChannelTree(channel *Channel) { chanstate := &mumbleproto.ChannelState{ ChannelId: proto.Uint32(uint32(channel.Id)), @@ -597,18 +614,6 @@ func (client *Client) sendChannelTree(channel *Channel) { chanstate.Parent = proto.Uint32(uint32(channel.parent.Id)) } - if channel.HasDescription() { - if client.Version >= 0x10202 { - chanstate.DescriptionHash = channel.DescriptionBlobHashBytes() - } else { - buf, err := blobStore.Get(channel.DescriptionBlob) - if err != nil { - panic("Blobstore error.") - } - chanstate.Description = proto.String(string(buf)) - } - } - if channel.IsTemporary() { chanstate.Temporary = proto.Bool(true) } @@ -626,9 +631,6 @@ func (client *Client) sendChannelTree(channel *Channel) { client.Panicf("%v", err) } - for _, subchannel := range channel.children { - client.sendChannelTree(subchannel) - } } // Try to do a crypto resync diff --git a/cmd/grumble/message.go b/cmd/grumble/message.go index 417faba..839dd57 100644 --- a/cmd/grumble/message.go +++ b/cmd/grumble/message.go @@ -313,7 +313,7 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { userstate.Session = proto.Uint32(client.Session()) userstate.ChannelId = proto.Uint32(uint32(channel.Id)) server.userEnterChannel(client, channel, userstate) - server.broadcastProtoMessage(userstate) + server.broadcastProtoMessageWithPredicate(userstate, func(cli *Client) bool { return cli.ircChannel == client.ircChannel }) } } else { // Edit existing channel. @@ -531,7 +531,7 @@ func (server *Server) handleUserRemoveMessage(client *Client, msg *Message) { } userremove.Actor = proto.Uint32(uint32(client.Session())) - if err = server.broadcastProtoMessage(userremove); err != nil { + if err = server.broadcastProtoMessageWithPredicate(userremove, func(cli *Client) bool { return cli.ircChannel == client.ircChannel }); err != nil { server.Panicf("Unable to broadcast UserRemove message") return } @@ -874,6 +874,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { server.ClearCaches() } + server.ergoStateBroadcast(target) err := server.broadcastProtoMessageWithPredicate(userstate, func(client *Client) bool { return client.Version >= 0x10203 }) diff --git a/cmd/grumble/server.go b/cmd/grumble/server.go index d46552d..425a209 100644 --- a/cmd/grumble/server.go +++ b/cmd/grumble/server.go @@ -15,11 +15,15 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/golang-jwt/jwt/v5" "hash" + "io" "log" "net" "net/http" + "os" "path/filepath" + "strconv" "strings" "sync" "time" @@ -38,7 +42,7 @@ import ( // The default port a Murmur server listens on const DefaultPort = 64738 -const DefaultWebPort = 443 +const DefaultWebPort = 8443 const UDPPacketSize = 1024 const LogOpsBeforeSync = 100 @@ -100,8 +104,9 @@ type Server struct { Opus bool // Channels - Channels map[int]*Channel - nextChanId int + Channels map[int]*Channel + WindowedChannels map[string]*Channel + nextChanId int // Users Users map[uint32]*User @@ -122,6 +127,9 @@ type Server struct { // Logging *log.Logger + + // CEF + ergo net.Conn } type clientLogForwarder struct { @@ -137,6 +145,62 @@ func (lf clientLogForwarder) Write(incoming []byte) (int, error) { return len(incoming), nil } +func (server *Server) ergoMessage(action string, ircChannel string, user string, extra ...string) { + if server.ergo == nil { + return + } + str := fmt.Sprintf("%s %s %s %s\n", action, ircChannel, user, strings.Join(extra, " ")) + server.ergo.Write([]byte(str)) +} + +func (server *Server) ergoStateBroadcast(client *Client) { + // Gooooooooo + muteState := b2i(client.SelfMute) + muteState |= b2i(client.Mute) << 1 + deafState := b2i(client.SelfDeaf) + deafState |= b2i(client.Deaf) << 1 + server.ergoMessage("VOICESTATE", client.ircChannel, client.Username, strconv.Itoa(muteState), strconv.Itoa(deafState)) +} + +func (server *Server) ergoConnection() { + conn, _ := net.Dial("tcp", "127.0.0.1:22843") + server.ergo = conn + reader := bufio.NewReader(conn) + println("Connection established with ergo") + for { + // read client request data + data, err := reader.ReadBytes(byte('\n')) + if err != nil { + if err != io.EOF { + fmt.Println("failed to read data, err:", err) + } + println("Reconnecting to ergo in 5s") + time.Sleep(5000) + go server.ergoConnection() + return + } + + line := strings.Split(string(data[:len(data)-1]), " ") + fmt.Printf("ergo: %+q\n", line) + switch line[0] { + case "KICK": + for _, client := range server.clients { + if client.ircChannel == line[1] && client.Username == line[2] { + server.RemoveClient(client, true) + } + } + case "POLL": + channel, ok := server.WindowedChannels[line[1]] + if ok { + for _, client := range channel.clients { + server.ergoStateBroadcast(client) + } + } + + } + } +} + // Allocate a new Murmur instance func NewServer(id int64) (s *Server, err error) { s = new(Server) @@ -153,11 +217,13 @@ func NewServer(id int64) (s *Server, err error) { s.nextUserId = 1 s.Channels = make(map[int]*Channel) + s.WindowedChannels = make(map[string]*Channel) s.Channels[0] = NewChannel(0, "Root") s.nextChanId = 1 s.Logger = log.New(logtarget.Default, fmt.Sprintf("[%v] ", s.Id), log.LstdFlags|log.Lmicroseconds) + go s.ergoConnection() return } @@ -347,14 +413,15 @@ func (server *Server) RemoveClient(client *Client, kicked bool) { if channel != nil { channel.RemoveClient(client) } + server.ergoMessage("PART", client.ircChannel, client.Username) // If the user was not kicked, broadcast a UserRemove message. // If the user is disconnect via a kick, the UserRemove message has already been sent // at this point. if !kicked && client.state > StateClientAuthenticated { - err := server.broadcastProtoMessage(&mumbleproto.UserRemove{ + err := server.broadcastProtoMessageWithPredicate(&mumbleproto.UserRemove{ Session: proto.Uint32(client.Session()), - }) + }, func(cli *Client) bool { return cli.ircChannel == client.ircChannel }) if err != nil { server.Panic("Unable to broadcast UserRemove message for disconnected client.") } @@ -539,11 +606,33 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { } } - if client.user == nil && server.hasServerPassword() { - if auth.Password == nil || !server.CheckServerPassword(*auth.Password) { - client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password") - return + if auth.Password == nil { + client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password") + return + } + + token, err := jwt.Parse(*auth.Password, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } + keyData, _ := os.ReadFile(filepath.Join(Args.DataDir, "jwtpub.pem")) + key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData) + return key, nil + }) + log.Printf("Auth failed: %s", err) + if err != nil { + client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Invalid server password") + return + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok { + client.ircChannel = claims["channel"].(string) + if client.Username != claims["account"].(string) { + client.RejectAuth(mumbleproto.Reject_InvalidUsername, "Username doesn't match") + } + } else { + client.RejectAuth(mumbleproto.Reject_WrongServerPW, "Something was wrong with your JWT") } // Setup the cryptstate for the client. @@ -567,7 +656,7 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { // Add codecs client.codecs = auth.CeltVersions - client.opus = auth.GetOpus() + client.opus = true // You get Opus. Too bad. client.state = StateClientAuthenticated server.clientAuthenticated <- client @@ -615,11 +704,10 @@ func (server *Server) finishAuthenticate(client *Client) { } } - // First, check whether we need to tell the other connected - // clients to switch to a codec so the new guy can actually speak. - server.updateCodecVersions(client) + // Opus gang only. + // server.updateCodecVersions(client) - client.sendChannelList() + channel := client.sendWindowedChannel() // Add the client to the host slice for its host address. host := client.tcpaddr.IP.String() @@ -627,14 +715,6 @@ func (server *Server) finishAuthenticate(client *Client) { server.hclients[host] = append(server.hclients[host], client) server.hmutex.Unlock() - channel := server.RootChannel() - if client.IsRegistered() { - lastChannel := server.Channels[client.user.LastChannelId] - if lastChannel != nil { - channel = lastChannel - } - } - userstate := &mumbleproto.UserState{ Session: proto.Uint32(client.Session()), Name: proto.String(client.ShownName()), @@ -676,7 +756,7 @@ func (server *Server) finishAuthenticate(client *Client) { } server.userEnterChannel(client, channel, userstate) - if err := server.broadcastProtoMessage(userstate); err != nil { + if err := server.broadcastProtoMessageWithPredicate(userstate, func(cli *Client) bool { return cli.ircChannel == client.ircChannel }); err != nil { // Server panic? } @@ -702,7 +782,7 @@ func (server *Server) finishAuthenticate(client *Client) { } err := client.sendMessage(&mumbleproto.ServerConfig{ - AllowHtml: proto.Bool(server.cfg.BoolValue("AllowHTML")), + AllowHtml: proto.Bool(false), MessageLength: proto.Uint32(server.cfg.Uint32Value("MaxTextMessageLength")), ImageMessageLength: proto.Uint32(server.cfg.Uint32Value("MaxImageMessageLength")), }) @@ -812,6 +892,10 @@ func (server *Server) updateCodecVersions(connecting *Client) { func (server *Server) sendUserList(client *Client) { for _, connectedClient := range server.clients { + if client.ircChannel != connectedClient.ircChannel { + continue + } + if connectedClient.state != StateClientReady { continue } @@ -1115,6 +1199,8 @@ func (server *Server) userEnterChannel(client *Client, channel *Channel, usersta if channel.parent != nil { server.sendClientPermissions(client, channel.parent) } + server.Printf("Broadcasting join: %s %s\n", client.ircChannel, client.Username) + server.ergoStateBroadcast(client) } // Register a client on the server. @@ -1231,12 +1317,10 @@ func (server *Server) RemoveChannel(channel *Channel) { parent := channel.parent delete(parent.children, channel.Id) delete(server.Channels, channel.Id) - chanremove := &mumbleproto.ChannelRemove{ - ChannelId: proto.Uint32(uint32(channel.Id)), - } - if err := server.broadcastProtoMessage(chanremove); err != nil { - server.Panicf("%v", err) - } + // Don't leak this + // if err := server.broadcastProtoMessage(chanremove); err != nil { + // server.Panicf("%v", err) + //} } // RemoveExpiredBans removes expired bans @@ -1475,19 +1559,19 @@ func (server *Server) Start() (err error) { if shouldListenWeb { // Create HTTP server and WebSocket "listener" webaddr := &net.TCPAddr{IP: net.ParseIP(host), Port: webport} - server.webtlscfg = &tls.Config{ - Certificates: []tls.Certificate{cert}, - ClientAuth: tls.NoClientCert, - NextProtos: []string{"http/1.1"}, - } + //server.webtlscfg = &tls.Config{ + // Certificates: []tls.Certificate{cert}, + // ClientAuth: tls.NoClientCert, + // NextProtos: []string{"http/1.1"}, + //} server.webwsl = web.NewListener(webaddr, server.Logger) mux := http.NewServeMux() mux.Handle("/", server.webwsl) server.webhttp = &http.Server{ - Addr: webaddr.String(), - Handler: mux, - TLSConfig: server.webtlscfg, - ErrorLog: server.Logger, + Addr: webaddr.String(), + Handler: mux, + //TLSConfig: server.webtlscfg, + ErrorLog: server.Logger, // Set sensible timeouts, in case no reverse proxy is in front of Grumble. // Non-conforming (or malicious) clients may otherwise block indefinitely and cause @@ -1497,7 +1581,7 @@ func (server *Server) Start() (err error) { IdleTimeout: 2 * time.Minute, } go func() { - err := server.webhttp.ListenAndServeTLS("", "") + err := server.webhttp.ListenAndServe() if err != http.ErrServerClosed { server.Fatalf("Fatal HTTP server error: %v", err) } diff --git a/cmd/grumble/util.go b/cmd/grumble/util.go new file mode 100644 index 0000000..75fd005 --- /dev/null +++ b/cmd/grumble/util.go @@ -0,0 +1,8 @@ +package main + +import "unsafe" + +// Stolen from https://dev.to/chigbeef_77/bool-int-but-stupid-in-go-3jb3 +func b2i(b bool) int { + return int(*(*byte)(unsafe.Pointer(&b))) +} diff --git a/cmd/grumble/voicetarget.go b/cmd/grumble/voicetarget.go index 4c8453e..9e2a306 100644 --- a/cmd/grumble/voicetarget.go +++ b/cmd/grumble/voicetarget.go @@ -126,7 +126,7 @@ func (vt *VoiceTarget) SendVoiceBroadcast(vb *VoiceBroadcast) { vt.fromChannelsCache = fromChannels } } - + // TODO: prevent cross-channel whispering kind := buf[0] & 0xe0 if len(fromChannels) > 0 { diff --git a/go.mod b/go.mod index f34f09a..b14f6fe 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module mumble.info/grumble go 1.14 require ( + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang/protobuf v1.5.4 github.com/gorilla/websocket v1.5.1 golang.org/x/crypto v0.21.0 diff --git a/go.sum b/go.sum index e1de580..d9901d3 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,14 @@ -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y= -golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= @@ -19,7 +16,6 @@ golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -33,8 +29,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -64,6 +58,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= From 1b4601a9c5b3f075fb6ad143cae6059fa3c43bb9 Mon Sep 17 00:00:00 2001 From: Failure Date: Fri, 26 Jul 2024 06:01:07 -0700 Subject: [PATCH 2/8] allow env to be passed --- cmd/grumble/server.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/grumble/server.go b/cmd/grumble/server.go index 425a209..4e1d95e 100644 --- a/cmd/grumble/server.go +++ b/cmd/grumble/server.go @@ -163,10 +163,14 @@ func (server *Server) ergoStateBroadcast(client *Client) { } func (server *Server) ergoConnection() { - conn, _ := net.Dial("tcp", "127.0.0.1:22843") + address := os.Getenv("GRUMBLE_ERGO_ADDR") + if len(address) == 0 { + address = "localhost" + } + conn, _ := net.Dial("tcp", net.JoinHostPort(address, "22843")) server.ergo = conn reader := bufio.NewReader(conn) - println("Connection established with ergo") + println("Connection established with ergo at ", address) for { // read client request data data, err := reader.ReadBytes(byte('\n')) From 6aabf2845d560e05c358984c7166c852e0edc3cf Mon Sep 17 00:00:00 2001 From: Failure Date: Fri, 26 Jul 2024 20:39:35 -0700 Subject: [PATCH 3/8] update dockerfile --- Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index d976431..7a8f474 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14-alpine as builder +FROM docker.io/golang:1.22-alpine as builder COPY . /go/src/mumble.info/grumble @@ -6,9 +6,7 @@ WORKDIR /go/src/mumble.info/grumble RUN apk add --no-cache git build-base -RUN go get -v -t ./... \ - && go build mumble.info/grumble/cmd/grumble \ - && go test -v ./... +RUN go get -v -t ./... && cd cmd/grumble && go build . && mkdir -p /go/bin/ && mv grumble /go/bin/grumble FROM alpine:edge From 31ff98b1e78689cd70f39fb5d593d0635efcb968 Mon Sep 17 00:00:00 2001 From: CEF Server Date: Tue, 27 Aug 2024 19:09:27 +0000 Subject: [PATCH 4/8] switch to redis pubsub for communication --- Dockerfile | 2 +- cmd/grumble/server.go | 77 ++++++++++++++++++++++--------------------- go.mod | 12 +++++-- go.sum | 57 ++++++-------------------------- 4 files changed, 61 insertions(+), 87 deletions(-) diff --git a/Dockerfile b/Dockerfile index d976431..841b3fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14-alpine as builder +FROM golang:1.22-alpine as builder COPY . /go/src/mumble.info/grumble diff --git a/cmd/grumble/server.go b/cmd/grumble/server.go index 4e1d95e..d5badf2 100644 --- a/cmd/grumble/server.go +++ b/cmd/grumble/server.go @@ -17,7 +17,6 @@ import ( "fmt" "github.com/golang-jwt/jwt/v5" "hash" - "io" "log" "net" "net/http" @@ -29,6 +28,7 @@ import ( "time" "github.com/golang/protobuf/proto" + "github.com/redis/go-redis/v9" "mumble.info/grumble/pkg/acl" "mumble.info/grumble/pkg/ban" "mumble.info/grumble/pkg/freezer" @@ -129,7 +129,9 @@ type Server struct { *log.Logger // CEF - ergo net.Conn + ergo net.Conn + redisContext context.Context + redis *redis.Client } type clientLogForwarder struct { @@ -145,12 +147,8 @@ func (lf clientLogForwarder) Write(incoming []byte) (int, error) { return len(incoming), nil } -func (server *Server) ergoMessage(action string, ircChannel string, user string, extra ...string) { - if server.ergo == nil { - return - } - str := fmt.Sprintf("%s %s %s %s\n", action, ircChannel, user, strings.Join(extra, " ")) - server.ergo.Write([]byte(str)) +func (server *Server) ergoMessage(ircChannel string, message ...string) { + server.redis.Publish(server.redisContext, "channel."+ircChannel, strings.Join(message, " ")) } func (server *Server) ergoStateBroadcast(client *Client) { @@ -159,52 +157,57 @@ func (server *Server) ergoStateBroadcast(client *Client) { muteState |= b2i(client.Mute) << 1 deafState := b2i(client.SelfDeaf) deafState |= b2i(client.Deaf) << 1 - server.ergoMessage("VOICESTATE", client.ircChannel, client.Username, strconv.Itoa(muteState), strconv.Itoa(deafState)) + server.ergoMessage(client.ircChannel, "VOICESTATE", client.Username, strconv.Itoa(muteState), strconv.Itoa(deafState)) } -func (server *Server) ergoConnection() { - address := os.Getenv("GRUMBLE_ERGO_ADDR") - if len(address) == 0 { - address = "localhost" - } - conn, _ := net.Dial("tcp", net.JoinHostPort(address, "22843")) - server.ergo = conn - reader := bufio.NewReader(conn) - println("Connection established with ergo at ", address) - for { - // read client request data - data, err := reader.ReadBytes(byte('\n')) - if err != nil { - if err != io.EOF { - fmt.Println("failed to read data, err:", err) - } - println("Reconnecting to ergo in 5s") - time.Sleep(5000) - go server.ergoConnection() - return - } +func (server *Server) redisChannelSub() { + pubsub := server.redis.PSubscribe(server.redisContext, "channel.*") + defer pubsub.Close() - line := strings.Split(string(data[:len(data)-1]), " ") - fmt.Printf("ergo: %+q\n", line) + ch := pubsub.Channel() + + for msg := range ch { + println("(Redis) ", msg.Channel, ": ", msg.Payload) + channelName := strings.SplitN(msg.Channel, ".", 2)[1] + line := strings.Split(msg.Payload, " ") + if len(line) == 0 { + println("Empty message dumped into ", msg.Channel) + continue + } switch line[0] { case "KICK": for _, client := range server.clients { - if client.ircChannel == line[1] && client.Username == line[2] { + if client.ircChannel == channelName && client.Username == line[1] { server.RemoveClient(client, true) } } - case "POLL": - channel, ok := server.WindowedChannels[line[1]] + case "VOICEPOLL": + channel, ok := server.WindowedChannels[channelName] if ok { for _, client := range channel.clients { server.ergoStateBroadcast(client) } } - } } } +func (server *Server) ergoConnection() { + server.redisContext = context.Background() + + address := os.Getenv("REDIS_ADDR") + if len(address) == 0 { + address = "redis://localhost/0?protocol=3" + } + redisOpts, err := redis.ParseURL(address) + if err != nil { + panic(err) + } + server.redis = redis.NewClient(redisOpts) + println("Redis connection established") + go server.redisChannelSub() +} + // Allocate a new Murmur instance func NewServer(id int64) (s *Server, err error) { s = new(Server) @@ -417,7 +420,7 @@ func (server *Server) RemoveClient(client *Client, kicked bool) { if channel != nil { channel.RemoveClient(client) } - server.ergoMessage("PART", client.ircChannel, client.Username) + server.ergoMessage(client.ircChannel, "VOICEPART", client.Username) // If the user was not kicked, broadcast a UserRemove message. // If the user is disconnect via a kick, the UserRemove message has already been sent diff --git a/go.mod b/go.mod index b14f6fe..f01a026 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,19 @@ module mumble.info/grumble -go 1.14 +go 1.22 require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang/protobuf v1.5.4 github.com/gorilla/websocket v1.5.1 + github.com/redis/go-redis/v9 v9.6.1 golang.org/x/crypto v0.21.0 - golang.org/x/net v0.22.0 // indirect +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index d9901d3..0d5181e 100644 --- a/go.sum +++ b/go.sum @@ -1,65 +1,28 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= From 29c96ebc5a6832dfcd689f08afb1afc03553278b Mon Sep 17 00:00:00 2001 From: Failure Date: Tue, 27 Aug 2024 12:15:15 -0700 Subject: [PATCH 5/8] Apply patch --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ed05f50..841b3fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14-alpine as builder +FROM golang:1.22-alpine as builder COPY . /go/src/mumble.info/grumble @@ -6,7 +6,9 @@ WORKDIR /go/src/mumble.info/grumble RUN apk add --no-cache git build-base -RUN go get -v -t ./... && cd cmd/grumble && go build . && mkdir -p /go/bin/ && mv grumble /go/bin/grumble +RUN go get -v -t ./... \ + && go build mumble.info/grumble/cmd/grumble \ + && go test -v ./... FROM alpine:edge From ca76023c02f9c7670b62f67945808a002de36c60 Mon Sep 17 00:00:00 2001 From: CEF Server Date: Tue, 27 Aug 2024 19:18:37 +0000 Subject: [PATCH 6/8] try to fix docker again --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 841b3fc..91813ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add --no-cache git build-base RUN go get -v -t ./... \ && go build mumble.info/grumble/cmd/grumble \ - && go test -v ./... + && go test -v ./... && cd cmd/grumble && mkdir -p /go/bin/ && mv grumble /go/bin/grumble FROM alpine:edge From 1c1d133264aa2db1dbf95b1b45ab867459aa029f Mon Sep 17 00:00:00 2001 From: CEF Server Date: Tue, 27 Aug 2024 19:22:46 +0000 Subject: [PATCH 7/8] try to fix docker again x2 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 91813ab..a32f42c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add --no-cache git build-base RUN go get -v -t ./... \ && go build mumble.info/grumble/cmd/grumble \ - && go test -v ./... && cd cmd/grumble && mkdir -p /go/bin/ && mv grumble /go/bin/grumble + && go test -v ./... && mkdir -p /go/bin/ && mv /go/src/mumble.info/grumble/cmd/grumble/grumble /go/bin/grumble FROM alpine:edge From 8e9f2dd1bc3686aa3fbab0fb1467a9cb5e9639de Mon Sep 17 00:00:00 2001 From: Failure Date: Tue, 27 Aug 2024 12:41:40 -0700 Subject: [PATCH 8/8] working docker --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a32f42c..c264aba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add --no-cache git build-base RUN go get -v -t ./... \ && go build mumble.info/grumble/cmd/grumble \ - && go test -v ./... && mkdir -p /go/bin/ && mv /go/src/mumble.info/grumble/cmd/grumble/grumble /go/bin/grumble + && go test -v ./... && mkdir -p /go/bin/ && mv grumble /go/bin/grumble FROM alpine:edge