From 925e23b0f9b9fca109292a455efc54466ca38cc8 Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Sat, 27 Nov 2010 14:22:47 +0100 Subject: [PATCH] Handle UserState and UserRemove (with a few exceptions!) --- client.go | 37 +++++++++---- message.go | 148 ++++++++++++++++++++++++++++++++++++++++++++++++--- server.go | 77 ++++++++++++++++++++++----- tlsserver.go | 1 + 4 files changed, 235 insertions(+), 28 deletions(-) diff --git a/client.go b/client.go index dede554..888a4b1 100644 --- a/client.go +++ b/client.go @@ -37,12 +37,21 @@ type Client struct { udp bool // Personal - UserId int - Session uint32 - Username string - Hash string - Tokens []string - Channel *Channel + UserId int + Session uint32 + Username string + Hash string + Tokens []string + Channel *Channel + SelfMute bool + SelfDeaf bool + Mute bool + Deaf bool + Suppress bool + PrioritySpeaker bool + Recording bool + PluginContext []byte + PluginIdentity string } // Something invalid happened on the wire. @@ -51,18 +60,28 @@ func (client *Client) Panic(reason string) { client.Disconnect() } -func (client *Client) Disconnect() { +// Internal disconnect function +func (client *Client) disconnect(kicked bool) { if !client.disconnected { client.disconnected = true close(client.udprecv) close(client.msgchan) client.conn.Close() - - client.server.RemoveClient(client) + client.server.RemoveClient(client, kicked) } } +// Disconnect a client (client disconnected) +func (client *Client) Disconnect() { + client.disconnect(false) +} + +// Disconnect a client (kick/ban) +func (client *Client) ForceDisconnect() { + client.disconnect(true) +} + // Read a protobuf message from a client func (client *Client) readProtoMessage() (msg *Message, err os.Error) { var length uint32 diff --git a/message.go b/message.go index 811ccca..6d63793 100644 --- a/message.go +++ b/message.go @@ -132,7 +132,48 @@ func (server *Server) handleChannelRemoveMessage(client *Client, msg *Message) { func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { } +// Handle a user remove packet. This can either be a client disconnecting, or a +// user kicking or kick-banning another player. func (server *Server) handleUserRemoveMessage(client *Client, msg *Message) { + userremove := &mumbleproto.UserRemove{} + err := proto.Unmarshal(msg.buf, userremove) + if err != nil { + client.Panic(err.String()) + } + + // Get the user to be removed. + user, ok := server.clients[*userremove.Session] + if !ok { + client.Panic("Invalid session in UserRemove message") + return + } + + ban := false + if userremove.Ban != nil { + ban = *userremove.Ban + } + + // Check user's permissions + perm := Permission(KickPermission) + if ban { + perm = Permission(BanPermission) + } + if user.UserId == 0 || !server.HasPermission(client, server.root, perm) { + client.sendPermissionDenied(client, server.root, perm) + return + } + + if ban { + log.Printf("handleUserRemove: Banning is not yet implemented.") + } + + userremove.Actor = proto.Uint32(uint32(client.Session)) + if err = server.broadcastProtoMessage(MessageUserRemove, userremove); err != nil { + log.Panic("Unable to broadcast UserRemove message") + return + } + + user.ForceDisconnect() } func (server *Server) handleUserStateMessage(client *Client, msg *Message) { @@ -143,13 +184,19 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { client.Panic(err.String()) } - if userstate.Session == nil { - log.Printf("UserState without session.") + actor, ok := server.clients[client.Session] + if !ok { + log.Printf("handleUserState: !") return } - - actor := server.clients[client.Session] - user := server.clients[*userstate.Session] + user := actor + if userstate.Session != nil { + user, ok = server.clients[*userstate.Session] + if !ok { + log.Printf("Invalid session in UserState message") + return + } + } log.Printf("actor = %v", actor) log.Printf("user = %v", user) @@ -206,7 +253,6 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { // Comment set/clear if userstate.Comment != nil { comment := *userstate.Comment - log.Printf("comment = %v", comment) // Clearing another user's comment. if user != actor { @@ -266,9 +312,99 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { if actor != user && (userstate.SelfDeaf != nil || userstate.SelfMute != nil || userstate.Texture != nil || userstate.PluginContext != nil || userstate.PluginIdentity != nil || userstate.Recording != nil) { + client.Panic("Invalid UserState") return } + log.Printf("handleUserState: In the out-figuring state") + + broadcast := false + if userstate.Texture != nil { + broadcast = true + } + + if userstate.SelfDeaf != nil { + user.SelfDeaf = *userstate.SelfDeaf + if user.SelfDeaf { + userstate.SelfDeaf = proto.Bool(true) + user.SelfMute = true + } + broadcast = true + } + + if userstate.SelfMute != nil { + user.SelfMute = *userstate.SelfMute + if !user.SelfMute { + userstate.SelfDeaf = proto.Bool(false) + user.SelfDeaf = false + } + } + + if userstate.PluginContext != nil { + user.PluginContext = userstate.PluginContext + } + + if userstate.PluginIdentity != nil { + user.PluginIdentity = *userstate.PluginIdentity + } + + if userstate.Comment != nil { + log.Printf("handleUserState: Comment unhandled") + broadcast = true + } + + if userstate.Mute != nil || userstate.Deaf != nil || userstate.Suppress != nil || userstate.PrioritySpeaker != nil { + if userstate.Deaf != nil { + user.Deaf = *userstate.Deaf + if user.Deaf { + userstate.Mute = proto.Bool(true) + } + } + if userstate.Mute != nil { + user.Mute = *userstate.Mute + if !user.Mute { + userstate.Deaf = proto.Bool(false) + user.Deaf = false + } + } + if userstate.Suppress != nil { + user.Suppress = *userstate.Suppress + } + if userstate.PrioritySpeaker != nil { + user.PrioritySpeaker = *userstate.PrioritySpeaker + } + broadcast = true + } + + if userstate.Recording != nil && *userstate.Recording != user.Recording { + user.Recording = *userstate.Recording + // fixme(mkrautz): Notify older clients of recording state change. + broadcast = true + } + + if userstate.UserId != nil { + log.Printf("handleUserState: SelfRegister unhandled") + userstate.UserId = nil + broadcast = true + } + + if userstate.ChannelId != nil { + channel, ok := server.channels[int(*userstate.ChannelId)] + if ok { + server.userEnterChannel(user, channel, userstate) + broadcast = true + } + } + + log.Printf("broadcast = %v", broadcast) + if broadcast { + log.Printf("sending broadcast!") + err := server.broadcastProtoMessage(MessageUserState, userstate) + if err != nil { + log.Printf("handleUserState: failed to broadcast userstate") + log.Panic("Unable to broadcast UserState") + } + } } func (server *Server) handleBanListMessage(client *Client, msg *Message) { diff --git a/server.go b/server.go index a9de636..d869af9 100644 --- a/server.go +++ b/server.go @@ -7,11 +7,13 @@ package main import ( "log" "crypto/tls" + "crypto/sha1" "os" "net" "bufio" "bytes" "encoding/binary" + "encoding/hex" "sync" "goprotobuf.googlecode.com/hg/proto" "mumbleproto" @@ -133,7 +135,7 @@ func (server *Server) NewClient(conn net.Conn) (err os.Error) { // Remove a disconnected client from the server's // internal representation. -func (server *Server) RemoveClient(client *Client) { +func (server *Server) RemoveClient(client *Client, kicked bool) { server.hmutex.Lock() if client.udpaddr != nil { host := client.udpaddr.IP.String() @@ -153,13 +155,20 @@ func (server *Server) RemoveClient(client *Client) { // Remove client from channel channel := client.Channel - channel.RemoveClient(client) + if channel != nil { + channel.RemoveClient(client) + } - err := server.broadcastProtoMessage(MessageUserRemove, &mumbleproto.UserRemove{ - Session: proto.Uint32(client.Session), - }) - if err != nil { - // server panic + // 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 { + err := server.broadcastProtoMessage(MessageUserRemove, &mumbleproto.UserRemove{ + Session: proto.Uint32(client.Session), + }) + if err != nil { + log.Panic("Unable to broadcast UserRemove message for disconnected client.") + } } } @@ -235,6 +244,21 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { client.Username = *auth.Username + // Extract certhash + tlsconn, ok := client.conn.(*tls.Conn) + if !ok { + client.Panic("Type assertion failed") + return + } + certs := tlsconn.PeerCertificates() + if len(certs) > 0 { + hash := sha1.New() + hash.Write(certs[0].Raw) + client.Hash = hex.EncodeToString(hash.Sum()) + } + + log.Printf("hash=%s", client.Hash) + // Setup the cryptstate for the client. client.crypt, err = cryptstate.New() if err != nil { @@ -289,9 +313,6 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { server.hclients[host] = append(server.hclients[host], client) server.hmutex.Unlock() - // Broadcast the the user entered a channel - server.root.AddClient(client) - if client.Username == "SuperUser" { client.UserId = 0 } @@ -304,8 +325,9 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { if client.UserId >= 0 { userstate.UserId = proto.Uint32(uint32(client.UserId)) } - if err := client.sendProtoMessage(MessageUserState, userstate); err != nil { - client.Panic(err.String()) + server.userEnterChannel(client, server.root, userstate) + if err := server.broadcastProtoMessage(MessageUserState, userstate); err != nil { + // Server panic? } server.sendUserList(client) @@ -410,8 +432,9 @@ func (server *Server) sendUserList(client *Client) { err := client.sendProtoMessage(MessageUserState, &mumbleproto.UserState{ Session: proto.Uint32(user.Session), Name: proto.String(user.Username), - ChannelId: proto.Uint32(0), + ChannelId: proto.Uint32(uint32(user.Channel.Id)), }) + log.Printf("ChanId = %v", user.Channel.Id) if err != nil { // Server panic? @@ -618,6 +641,34 @@ func (s *Server) ClearACLCache() { s.aclcache = NewACLCache() } +// Helper method for users entering new channels +func (server *Server) userEnterChannel(client *Client, channel *Channel, userstate *mumbleproto.UserState) { + if client.Channel == channel { + return + } + + oldchan := client.Channel + if oldchan != nil { + oldchan.RemoveClient(client) + } + channel.AddClient(client) + + server.ClearACLCache() + // fixme(mkrautz): Set LastChannel for user in datastore + // fixme(mkrautz): Remove channel if temporary + + canspeak := server.HasPermission(client, channel, SpeakPermission) + if canspeak == client.Suppress { + client.Suppress = !canspeak + userstate.Suppress = proto.Bool(client.Suppress) + } + + server.sendClientPermissions(client, channel) + if channel.parent != nil { + server.sendClientPermissions(client, channel.parent) + } +} + // The accept loop of the server. func (s *Server) ListenAndMurmur() { diff --git a/tlsserver.go b/tlsserver.go index 19b7349..1ac06da 100644 --- a/tlsserver.go +++ b/tlsserver.go @@ -72,6 +72,7 @@ func NewTLSListener(port int) (rl *tls.Listener) { config.Certificates = make([]tls.Certificate, 1) config.Certificates[0].Certificate = [][]byte{cert.Bytes} config.Certificates[0].PrivateKey = priv + config.AuthenticateClient = true l, err := net.ListenTCP("tcp", &net.TCPAddr{ net.ParseIP("0.0.0.0"),