mirror of
https://github.com/mumble-voip/grumble.git
synced 2025-12-19 21:59:59 -08:00
838 lines
23 KiB
Go
838 lines
23 KiB
Go
// Copyright (c) 2010 The Grumble Authors
|
|
// The use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE-file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"log"
|
|
"mumbleproto"
|
|
"goprotobuf.googlecode.com/hg/proto"
|
|
"net"
|
|
"crypto/sha1"
|
|
"cryptstate"
|
|
"fmt"
|
|
)
|
|
|
|
// These are the different kinds of messages
|
|
// that are defined for the Mumble protocol
|
|
const (
|
|
MessageVersion = iota
|
|
MessageUDPTunnel
|
|
MessageAuthenticate
|
|
MessagePing
|
|
MessageReject
|
|
MessageServerSync
|
|
MessageChannelRemove
|
|
MessageChannelState
|
|
MessageUserRemove
|
|
MessageUserState
|
|
MessageBanList
|
|
MessageTextMessage
|
|
MessagePermissionDenied
|
|
MessageACL
|
|
MessageQueryUsers
|
|
MessageCryptSetup
|
|
MessageContextActionAdd
|
|
MessageContextAction
|
|
MessageUserList
|
|
MessageVoiceTarget
|
|
MessagePermissionQuery
|
|
MessageCodecVersion
|
|
MessageUserStats
|
|
MessageRequestBlob
|
|
MessageServerConfig
|
|
)
|
|
|
|
const (
|
|
UDPMessageVoiceCELTAlpha = iota
|
|
UDPMessagePing
|
|
UDPMessageVoiceSpeex
|
|
UDPMessageVoiceCELTBeta
|
|
)
|
|
|
|
type Message struct {
|
|
buf []byte
|
|
|
|
// Kind denotes a message kind for TCP packets. This field
|
|
// is ignored for UDP packets.
|
|
kind uint16
|
|
|
|
// For UDP datagrams one of these fields have to be filled out.
|
|
// If there is no connection established, address must be used.
|
|
// If the datagram comes from an already-connected client, the
|
|
// client field should point to that client.
|
|
client *Client
|
|
address net.Addr
|
|
}
|
|
|
|
type VoiceBroadcast struct {
|
|
// The client who is performing the broadcast
|
|
client *Client
|
|
// The VoiceTarget identifier.
|
|
target byte
|
|
// The voice packet itself.
|
|
buf []byte
|
|
}
|
|
|
|
func (server *Server) handleCryptSetup(client *Client, msg *Message) {
|
|
cs := &mumbleproto.CryptSetup{}
|
|
err := proto.Unmarshal(msg.buf, cs)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
return
|
|
}
|
|
|
|
// No client nonce. This means the client
|
|
// is requesting that we re-sync our nonces.
|
|
if len(cs.ClientNonce) == 0 {
|
|
log.Printf("Requested crypt-nonce resync")
|
|
cs.ClientNonce = make([]byte, cryptstate.AESBlockSize)
|
|
if copy(cs.ClientNonce, client.crypt.EncryptIV[0:]) != cryptstate.AESBlockSize {
|
|
return
|
|
}
|
|
client.sendProtoMessage(MessageCryptSetup, cs)
|
|
} else {
|
|
log.Printf("Received client nonce")
|
|
if len(cs.ClientNonce) != cryptstate.AESBlockSize {
|
|
return
|
|
}
|
|
|
|
client.crypt.Resync += 1
|
|
if copy(client.crypt.DecryptIV[0:], cs.ClientNonce) != cryptstate.AESBlockSize {
|
|
return
|
|
}
|
|
log.Printf("Crypt re-sync successful")
|
|
}
|
|
}
|
|
|
|
func (server *Server) handlePingMessage(client *Client, msg *Message) {
|
|
ping := &mumbleproto.Ping{}
|
|
err := proto.Unmarshal(msg.buf, ping)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
return
|
|
}
|
|
|
|
// Phony response for ping messages. We don't keep stats
|
|
// for this yet.
|
|
client.sendProtoMessage(MessagePing, &mumbleproto.Ping{
|
|
Timestamp: ping.Timestamp,
|
|
Good: proto.Uint32(uint32(client.crypt.Good)),
|
|
Late: proto.Uint32(uint32(client.crypt.Late)),
|
|
Lost: proto.Uint32(uint32(client.crypt.Lost)),
|
|
Resync: proto.Uint32(uint32(client.crypt.Resync)),
|
|
})
|
|
}
|
|
|
|
func (server *Server) handleChannelAddMessage(client *Client, msg *Message) {
|
|
}
|
|
|
|
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 {
|
|
// fixme(mkrautz): Implement banning.
|
|
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()
|
|
}
|
|
|
|
// Handle user state changes
|
|
func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
|
|
userstate := &mumbleproto.UserState{}
|
|
err := proto.Unmarshal(msg.buf, userstate)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
}
|
|
|
|
actor, ok := server.clients[client.Session]
|
|
if !ok {
|
|
log.Panic("Client not found in server's client map.")
|
|
return
|
|
}
|
|
user := actor
|
|
if userstate.Session != nil {
|
|
user, ok = server.clients[*userstate.Session]
|
|
if !ok {
|
|
client.Panic("Invalid session in UserState message")
|
|
return
|
|
}
|
|
}
|
|
|
|
userstate.Session = proto.Uint32(user.Session)
|
|
userstate.Actor = proto.Uint32(actor.Session)
|
|
|
|
// Does it have a channel ID?
|
|
if userstate.ChannelId != nil {
|
|
// Destination channel
|
|
dstChan, ok := server.channels[int(*userstate.ChannelId)]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// If the user and the actor aren't the same, check whether the actor has MovePermission on
|
|
// the user's curent channel.
|
|
if actor != user && !server.HasPermission(actor, user.Channel, MovePermission) {
|
|
client.sendPermissionDenied(actor, user.Channel, MovePermission)
|
|
return
|
|
}
|
|
|
|
// Check whether the actor has MovePermission on dstChan. Check whether user has EnterPermission
|
|
// on dstChan.
|
|
if !server.HasPermission(actor, dstChan, MovePermission) && !server.HasPermission(user, dstChan, EnterPermission) {
|
|
client.sendPermissionDenied(user, dstChan, EnterPermission)
|
|
return
|
|
}
|
|
|
|
// fixme(mkrautz): Check whether the channel is full.
|
|
}
|
|
|
|
if userstate.Mute != nil || userstate.Deaf != nil || userstate.Suppress != nil || userstate.PrioritySpeaker != nil {
|
|
// Disallow for SuperUser
|
|
if user.UserId == 0 {
|
|
client.sendPermissionDeniedType("SuperUser")
|
|
return
|
|
}
|
|
|
|
// Check whether the actor has 'mutedeafen' permission on user's channel.
|
|
if !server.HasPermission(actor, user.Channel, MuteDeafenPermission) {
|
|
client.sendPermissionDenied(actor, user.Channel, MuteDeafenPermission)
|
|
return
|
|
}
|
|
|
|
// Check if this was a suppress operation. Only the server can suppress users.
|
|
if userstate.Suppress != nil {
|
|
client.sendPermissionDenied(actor, user.Channel, MuteDeafenPermission)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Comment set/clear
|
|
if userstate.Comment != nil {
|
|
comment := *userstate.Comment
|
|
|
|
// Clearing another user's comment.
|
|
if user != actor {
|
|
// Check if actor has 'move' permissions on the root channel. It is needed
|
|
// to clear another user's comment.
|
|
if !server.HasPermission(actor, server.root, MovePermission) {
|
|
client.sendPermissionDenied(actor, server.root, MovePermission)
|
|
return
|
|
}
|
|
|
|
// Only allow empty text.
|
|
if len(comment) > 0 {
|
|
client.Panic("Cannot clear another user's comment")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check if the text is allowed.
|
|
|
|
// Only set the comment if it is different from the current
|
|
// user comment.
|
|
if comment == user.Comment {
|
|
userstate.Comment = nil
|
|
}
|
|
}
|
|
|
|
// Texture change
|
|
if userstate.Texture != nil {
|
|
// Check the length of the texture
|
|
}
|
|
|
|
// Registration
|
|
if userstate.UserId != nil {
|
|
// If user == actor, check for SelfRegisterPermission on root channel.
|
|
// If user != actor, check for RegisterPermission permission on root channel.
|
|
permCheck := Permission(NonePermission)
|
|
uid := *userstate.UserId
|
|
if user == actor {
|
|
permCheck = SelfRegisterPermission
|
|
} else {
|
|
permCheck = RegisterPermission
|
|
}
|
|
if uid >= 0 || !server.HasPermission(actor, server.root, SelfRegisterPermission) {
|
|
client.sendPermissionDenied(actor, server.root, permCheck)
|
|
return
|
|
}
|
|
|
|
// We can't register a user with an empty hash.
|
|
if len(user.Hash) == 0 {
|
|
client.sendPermissionDeniedTypeUser("MissingCertificate", user)
|
|
}
|
|
}
|
|
|
|
// Prevent self-targetting state changes to be applied to other users
|
|
// That is, if actor != user, then:
|
|
// Discard message if it has any of the following things set:
|
|
// - SelfDeaf
|
|
// - SelfMute
|
|
// - Texture
|
|
// - PluginContext
|
|
// - PluginIdentity
|
|
// - Recording
|
|
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
|
|
}
|
|
|
|
broadcast := false
|
|
|
|
if userstate.Texture != nil {
|
|
user.Texture = userstate.Texture
|
|
if len(user.Texture) >= 128 {
|
|
hash := sha1.New()
|
|
hash.Write(user.Texture)
|
|
user.TextureHash = hash.Sum()
|
|
} else {
|
|
user.TextureHash = []byte{}
|
|
}
|
|
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 {
|
|
user.Comment = *userstate.Comment
|
|
if len(user.Comment) >= 128 {
|
|
hash := sha1.New()
|
|
hash.Write([]byte(user.Comment))
|
|
user.CommentHash = hash.Sum()
|
|
} else {
|
|
user.CommentHash = []byte{}
|
|
}
|
|
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
|
|
|
|
txtmsg := &mumbleproto.TextMessage{}
|
|
txtmsg.TreeId = append(txtmsg.TreeId, uint32(0))
|
|
if user.Recording {
|
|
txtmsg.Message = proto.String(fmt.Sprintf("User '%s' started recording", user.Username))
|
|
} else {
|
|
txtmsg.Message = proto.String(fmt.Sprintf("User '%s' stopped recording", user.Username))
|
|
}
|
|
|
|
server.broadcastProtoMessageWithPredicate(MessageTextMessage, txtmsg, func(client *Client) bool {
|
|
return client.Version < 0x10203
|
|
})
|
|
|
|
broadcast = true
|
|
}
|
|
|
|
if userstate.UserId != nil {
|
|
// fixme(mkrautz): Registration is currently unhandled.
|
|
log.Printf("handleUserState: (Self)Register not implemented yet!")
|
|
userstate.UserId = nil
|
|
}
|
|
|
|
if userstate.ChannelId != nil {
|
|
channel, ok := server.channels[int(*userstate.ChannelId)]
|
|
if ok {
|
|
server.userEnterChannel(user, channel, userstate)
|
|
broadcast = true
|
|
}
|
|
}
|
|
|
|
if broadcast {
|
|
// This variable denotes the length of a zlib-encoded "old-style" texture.
|
|
// Mumble and Murmur used qCompress and qUncompress from Qt to compress
|
|
// textures that were sent over the wire. We can use this to determine
|
|
// whether a texture is a "new style" or an "old style" texture.
|
|
texlen := uint32(0)
|
|
if user.Texture != nil && len(user.Texture) > 4 {
|
|
texlen = uint32(user.Texture[0])<<24 | uint32(user.Texture[1])<<16 | uint32(user.Texture[2])<<8 | uint32(user.Texture[3])
|
|
}
|
|
if userstate.Texture != nil && len(user.Texture) > 4 && texlen != 600*60*4 {
|
|
// The sent texture is a new-style texture. Strip it from the message
|
|
// we send to pre-1.2.2 clients.
|
|
userstate.Texture = nil
|
|
err := server.broadcastProtoMessageWithPredicate(MessageUserState, userstate, func(client *Client) bool {
|
|
return client.Version < 0x10202
|
|
})
|
|
if err != nil {
|
|
log.Panic("Unable to broadcast UserState")
|
|
}
|
|
// Re-add it to the message, so that 1.2.2+ clients *do* get the new-style texture.
|
|
userstate.Texture = user.Texture
|
|
} else {
|
|
// Old style texture. We can send the message as-is.
|
|
err := server.broadcastProtoMessageWithPredicate(MessageUserState, userstate, func(client *Client) bool {
|
|
return client.Version < 0x10202
|
|
})
|
|
if err != nil {
|
|
log.Panic("Unable to broadcast UserState")
|
|
}
|
|
}
|
|
|
|
// If a texture hash is set on user, we transmit that instead of
|
|
// the texture itself. This allows the client to intelligently fetch
|
|
// the blobs that it does not already have in its local storage.
|
|
if userstate != nil && len(user.TextureHash) > 0 {
|
|
userstate.Texture = nil
|
|
userstate.TextureHash = user.TextureHash
|
|
}
|
|
// Ditto for comments.
|
|
if userstate.Comment != nil && len(user.CommentHash) > 0 {
|
|
userstate.Comment = nil
|
|
userstate.CommentHash = user.CommentHash
|
|
}
|
|
|
|
err := server.broadcastProtoMessageWithPredicate(MessageUserState, userstate, func(client *Client) bool {
|
|
return client.Version >= 0x10203
|
|
})
|
|
if err != nil {
|
|
log.Panic("Unable to broadcast UserState")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (server *Server) handleBanListMessage(client *Client, msg *Message) {
|
|
}
|
|
|
|
// Broadcast text messages
|
|
func (server *Server) handleTextMessage(client *Client, msg *Message) {
|
|
txtmsg := &mumbleproto.TextMessage{}
|
|
err := proto.Unmarshal(msg.buf, txtmsg)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
return
|
|
}
|
|
|
|
// fixme(mkrautz): Check text message length.
|
|
// fixme(mkrautz): Sanitize text as well.
|
|
|
|
users := make(map[uint32]*Client)
|
|
|
|
// Tree
|
|
for _, chanid := range txtmsg.TreeId {
|
|
if channel, ok := server.channels[int(chanid)]; ok {
|
|
if !server.HasPermission(client, channel, TextMessagePermission) {
|
|
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
|
}
|
|
for _, user := range channel.clients {
|
|
users[user.Session] = user
|
|
}
|
|
}
|
|
}
|
|
|
|
// Direct-to-channel
|
|
for _, chanid := range txtmsg.ChannelId {
|
|
if channel, ok := server.channels[int(chanid)]; ok {
|
|
if !server.HasPermission(client, channel, TextMessagePermission) {
|
|
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
|
return
|
|
}
|
|
for _, user := range channel.clients {
|
|
users[user.Session] = user
|
|
}
|
|
}
|
|
}
|
|
|
|
// Direct-to-users
|
|
for _, session := range txtmsg.Session {
|
|
if user, ok := server.clients[session]; ok {
|
|
if !server.HasPermission(client, user.Channel, TextMessagePermission) {
|
|
client.sendPermissionDenied(client, user.Channel, TextMessagePermission)
|
|
return
|
|
}
|
|
users[session] = user
|
|
}
|
|
}
|
|
|
|
// Remove ourselves
|
|
users[client.Session] = nil, false
|
|
|
|
for _, user := range users {
|
|
user.sendProtoMessage(MessageTextMessage, &mumbleproto.TextMessage{
|
|
Actor: proto.Uint32(client.Session),
|
|
Message: txtmsg.Message,
|
|
})
|
|
}
|
|
}
|
|
|
|
// ACL set/query
|
|
func (server *Server) handleAclMessage(client *Client, msg *Message) {
|
|
acl := &mumbleproto.ACL{}
|
|
err := proto.Unmarshal(msg.buf, acl)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
}
|
|
|
|
// Look up the channel this ACL message operates on.
|
|
channel, ok := server.channels[int(*acl.ChannelId)]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Does the user have permission to update or look at ACLs?
|
|
if !server.HasPermission(client, channel, WritePermission) && !(channel.parent != nil && server.HasPermission(client, channel.parent, WritePermission)) {
|
|
client.sendPermissionDenied(client, channel, WritePermission)
|
|
return
|
|
}
|
|
|
|
reply := &mumbleproto.ACL{}
|
|
reply.ChannelId = proto.Uint32(uint32(channel.Id))
|
|
|
|
channels := []*Channel{}
|
|
users := map[int]bool{}
|
|
|
|
// Query the current ACL state for the channel
|
|
if acl.Query != nil && *acl.Query != false {
|
|
reply.InheritAcls = proto.Bool(channel.InheritACL)
|
|
// Walk the channel tree to get all relevant channels.
|
|
// (Stop if we reach a channel that doesn't have the InheritACL flag set)
|
|
iter := channel
|
|
for iter != nil {
|
|
channels = append([]*Channel{iter}, channels...)
|
|
if iter == channel || iter.InheritACL {
|
|
iter = iter.parent
|
|
} else {
|
|
iter = nil
|
|
}
|
|
}
|
|
|
|
// Construct the protobuf ChanACL objects corresponding to the ACLs defined
|
|
// in our channel list.
|
|
reply.Acls = []*mumbleproto.ACL_ChanACL{}
|
|
for _, iter := range channels {
|
|
for _, chanacl := range iter.ACL {
|
|
if iter == channel || chanacl.ApplySubs {
|
|
mpacl := &mumbleproto.ACL_ChanACL{}
|
|
mpacl.Inherited = proto.Bool(iter != channel)
|
|
mpacl.ApplyHere = proto.Bool(chanacl.ApplyHere)
|
|
mpacl.ApplySubs = proto.Bool(chanacl.ApplySubs)
|
|
if chanacl.UserId >= 0 {
|
|
mpacl.UserId = proto.Uint32(uint32(chanacl.UserId))
|
|
users[chanacl.UserId] = true
|
|
} else {
|
|
mpacl.Group = proto.String(chanacl.Group)
|
|
}
|
|
mpacl.Grant = proto.Uint32(uint32(chanacl.Allow))
|
|
mpacl.Deny = proto.Uint32(uint32(chanacl.Deny))
|
|
reply.Acls = append(reply.Acls, mpacl)
|
|
}
|
|
}
|
|
}
|
|
|
|
parent := channel.parent
|
|
allnames := channel.GroupNames()
|
|
|
|
// Construct the protobuf ChanGroups that we send back to the client.
|
|
// Also constructs a usermap that is a set user ids from the channel's groups.
|
|
reply.Groups = []*mumbleproto.ACL_ChanGroup{}
|
|
for name, _ := range allnames {
|
|
group := channel.Groups[name]
|
|
pgroup, ok := parent.Groups[name]
|
|
if !ok {
|
|
pgroup = nil
|
|
}
|
|
|
|
mpgroup := &mumbleproto.ACL_ChanGroup{}
|
|
mpgroup.Name = proto.String(name)
|
|
|
|
mpgroup.Inherit = proto.Bool(true)
|
|
if group != nil {
|
|
mpgroup.Inherit = proto.Bool(group.Inherit)
|
|
}
|
|
|
|
mpgroup.Inheritable = proto.Bool(true)
|
|
if group != nil {
|
|
mpgroup.Inheritable = proto.Bool(group.Inheritable)
|
|
}
|
|
|
|
mpgroup.Inherited = proto.Bool(pgroup != nil && pgroup.Inheritable)
|
|
|
|
// Add the set of user ids that this group affects to the user map.
|
|
// This is used later on in this function to send the client a QueryUsers
|
|
// message that maps user ids to usernames.
|
|
if group != nil {
|
|
toadd := map[int]bool{}
|
|
for uid, _ := range group.Add {
|
|
users[uid] = true
|
|
toadd[uid] = true
|
|
}
|
|
for uid, _ := range group.Remove {
|
|
users[uid] = true
|
|
toadd[uid] = false, false
|
|
}
|
|
for uid, _ := range toadd {
|
|
mpgroup.Add = append(mpgroup.Add, uint32(uid))
|
|
}
|
|
}
|
|
if pgroup != nil {
|
|
for uid, _ := range pgroup.Members() {
|
|
users[uid] = true
|
|
mpgroup.InheritedMembers = append(mpgroup.InheritedMembers, uint32(uid))
|
|
}
|
|
}
|
|
|
|
reply.Groups = append(reply.Groups, mpgroup)
|
|
}
|
|
|
|
if err := client.sendProtoMessage(MessageACL, reply); err != nil {
|
|
client.Panic(err.String())
|
|
}
|
|
|
|
// Map the user ids in the user map to usernames of users.
|
|
// fixme(mkrautz): This requires a persistent datastore, because it retrieves registered users.
|
|
queryusers := &mumbleproto.QueryUsers{}
|
|
for uid, _ := range users {
|
|
queryusers.Ids = append(queryusers.Ids, uint32(uid))
|
|
queryusers.Names = append(queryusers.Names, "Unknown")
|
|
}
|
|
if len(queryusers.Ids) > 0 {
|
|
client.sendProtoMessage(MessageQueryUsers, reply)
|
|
}
|
|
|
|
// Set new groups and ACLs
|
|
} else {
|
|
|
|
// Get old temporary members
|
|
oldtmp := map[string]map[int]bool{}
|
|
for name, grp := range channel.Groups {
|
|
oldtmp[name] = grp.Temporary
|
|
}
|
|
|
|
// Clear current ACLs and groups
|
|
channel.ACL = []*ChannelACL{}
|
|
channel.Groups = map[string]*Group{}
|
|
|
|
// Add the received groups to the channel.
|
|
channel.InheritACL = *acl.InheritAcls
|
|
for _, pbgrp := range acl.Groups {
|
|
changroup := NewGroup(channel, *pbgrp.Name)
|
|
|
|
changroup.Inherit = *pbgrp.Inherit
|
|
changroup.Inheritable = *pbgrp.Inheritable
|
|
for _, uid := range pbgrp.Add {
|
|
changroup.Add[int(uid)] = true
|
|
}
|
|
for _, uid := range pbgrp.Remove {
|
|
changroup.Remove[int(uid)] = true
|
|
}
|
|
if temp, ok := oldtmp[*pbgrp.Name]; ok {
|
|
changroup.Temporary = temp
|
|
}
|
|
|
|
channel.Groups[changroup.Name] = changroup
|
|
}
|
|
// Add the received ACLs to the channel.
|
|
for _, pbacl := range acl.Acls {
|
|
chanacl := NewChannelACL(channel)
|
|
|
|
chanacl.ApplyHere = *pbacl.ApplyHere
|
|
chanacl.ApplySubs = *pbacl.ApplySubs
|
|
if pbacl.UserId != nil {
|
|
chanacl.UserId = int(*pbacl.UserId)
|
|
} else {
|
|
chanacl.Group = *pbacl.Group
|
|
}
|
|
chanacl.Deny = Permission(*pbacl.Deny & AllPermissions)
|
|
chanacl.Allow = Permission(*pbacl.Grant & AllPermissions)
|
|
|
|
channel.ACL = append(channel.ACL, chanacl)
|
|
}
|
|
|
|
// Clear the server's ACL cache
|
|
server.ClearACLCache()
|
|
|
|
// Regular user?
|
|
if (!server.HasPermission(client, channel, WritePermission) && client.UserId >= 0) || len(client.Hash) > 0 {
|
|
chanacl := NewChannelACL(channel)
|
|
|
|
chanacl.ApplyHere = true
|
|
chanacl.ApplySubs = false
|
|
if client.UserId >= 0 {
|
|
chanacl.UserId = client.UserId
|
|
} else {
|
|
chanacl.Group = "$" + client.Hash
|
|
}
|
|
chanacl.UserId = client.UserId
|
|
chanacl.Deny = Permission(NonePermission)
|
|
chanacl.Allow = Permission(WritePermission | TraversePermission)
|
|
|
|
channel.ACL = append(channel.ACL, chanacl)
|
|
|
|
server.ClearACLCache()
|
|
}
|
|
|
|
// fixme(mkrautz): Sync channel to datastore
|
|
}
|
|
}
|
|
|
|
// User query
|
|
func (server *Server) handleQueryUsers(client *Client, msg *Message) {
|
|
}
|
|
|
|
// User stats message. Shown in the Mumble client when a
|
|
// user right clicks a user and selects 'User Information'.
|
|
func (server *Server) handleUserStatsMessage(client *Client, msg *Message) {
|
|
stats := &mumbleproto.UserStats{}
|
|
err := proto.Unmarshal(msg.buf, stats)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
}
|
|
|
|
log.Printf("UserStats")
|
|
}
|
|
|
|
// Permission query
|
|
func (server *Server) handlePermissionQuery(client *Client, msg *Message) {
|
|
query := &mumbleproto.PermissionQuery{}
|
|
err := proto.Unmarshal(msg.buf, query)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
}
|
|
|
|
if query.ChannelId == nil {
|
|
return
|
|
}
|
|
|
|
channel := server.channels[int(*query.ChannelId)]
|
|
server.sendClientPermissions(client, channel)
|
|
}
|
|
|
|
// Request big blobs from the server
|
|
func (server *Server) handleRequestBlob(client *Client, msg *Message) {
|
|
blobreq := &mumbleproto.RequestBlob{}
|
|
err := proto.Unmarshal(msg.buf, blobreq)
|
|
if err != nil {
|
|
client.Panic(err.String())
|
|
return
|
|
}
|
|
|
|
userstate := &mumbleproto.UserState{}
|
|
|
|
// Request for user textures
|
|
if len(blobreq.SessionTexture) > 0 {
|
|
for _, sid := range blobreq.SessionTexture {
|
|
if user, ok := server.clients[sid]; ok {
|
|
if len(user.Texture) > 0 {
|
|
userstate.Reset()
|
|
userstate.Session = proto.Uint32(uint32(user.Session))
|
|
userstate.Texture = user.Texture
|
|
if err := client.sendProtoMessage(MessageUserState, userstate); err != nil {
|
|
client.Panic(err.String())
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Request for user comments
|
|
if len(blobreq.SessionComment) > 0 {
|
|
for _, sid := range blobreq.SessionComment {
|
|
if user, ok := server.clients[sid]; ok {
|
|
if len(user.Comment) > 0 {
|
|
userstate.Reset()
|
|
userstate.Session = proto.Uint32(uint32(user.Session))
|
|
userstate.Comment = proto.String(user.Comment)
|
|
if err := client.sendProtoMessage(MessageUserState, userstate); err != nil {
|
|
client.Panic(err.String())
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Request for channel descriptions
|
|
if len(blobreq.ChannelDescription) > 0 {
|
|
log.Printf("No support for ChannelDescriptions in BlobRequest.")
|
|
}
|
|
}
|