1
0
Fork 0
forked from External/grumble

Compare commits

...
Sign in to create a new pull request.

10 commits
deps ... master

Author SHA1 Message Date
8e9f2dd1bc working docker 2024-08-27 12:41:40 -07:00
CEF Server
1c1d133264 try to fix docker again x2 2024-08-27 19:22:46 +00:00
CEF Server
ca76023c02 try to fix docker again 2024-08-27 19:18:37 +00:00
29c96ebc5a Apply patch 2024-08-27 12:15:15 -07:00
CEF Server
a291cbbf1e Merge remote-tracking branch 'origin/master'
# Conflicts:
#	Dockerfile
2024-08-27 19:10:28 +00:00
CEF Server
31ff98b1e7 switch to redis pubsub for communication 2024-08-27 19:09:27 +00:00
6aabf2845d update dockerfile 2024-07-26 20:39:35 -07:00
1b4601a9c5 allow env to be passed 2024-07-26 06:01:07 -07:00
384ae29dd8 import changes 2024-07-24 17:01:20 -07:00
Jan Klass
3e7af1e0fe
misc(proj): Upgrade dependencies (#82) 2024-03-14 15:25:42 +01:00
8 changed files with 188 additions and 119 deletions

View file

@ -1,4 +1,4 @@
FROM golang:1.14-alpine as builder
FROM golang:1.22-alpine as builder
COPY . /go/src/mumble.info/grumble
@ -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 ./... && mkdir -p /go/bin/ && mv grumble /go/bin/grumble
FROM alpine:edge

View file

@ -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

View file

@ -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
})

View file

@ -15,16 +15,20 @@ import (
"encoding/hex"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"hash"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"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"
@ -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,11 @@ type Server struct {
// Logging
*log.Logger
// CEF
ergo net.Conn
redisContext context.Context
redis *redis.Client
}
type clientLogForwarder struct {
@ -137,6 +147,67 @@ func (lf clientLogForwarder) Write(incoming []byte) (int, error) {
return len(incoming), nil
}
func (server *Server) ergoMessage(ircChannel string, message ...string) {
server.redis.Publish(server.redisContext, "channel."+ircChannel, strings.Join(message, " "))
}
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(client.ircChannel, "VOICESTATE", client.Username, strconv.Itoa(muteState), strconv.Itoa(deafState))
}
func (server *Server) redisChannelSub() {
pubsub := server.redis.PSubscribe(server.redisContext, "channel.*")
defer pubsub.Close()
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 == channelName && client.Username == line[1] {
server.RemoveClient(client, true)
}
}
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)
@ -153,11 +224,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 +420,15 @@ func (server *Server) RemoveClient(client *Client, kicked bool) {
if channel != nil {
channel.RemoveClient(client)
}
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
// 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 +613,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 +663,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 +711,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 +722,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 +763,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 +789,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 +899,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 +1206,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 +1324,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 +1566,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 +1588,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)
}

8
cmd/grumble/util.go Normal file
View file

@ -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)))
}

View file

@ -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 {

13
go.mod
View file

@ -1,10 +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
)

70
go.sum
View file

@ -1,70 +1,28 @@
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/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
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.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=
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-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=
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-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=
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=