From 4903558e4a69692d0860ad478aa6f58d88927ac5 Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Tue, 23 Nov 2010 00:37:24 +0100 Subject: [PATCH] Initial ACL and group support. --- Makefile | 4 +- acl.go | 240 ++++++++++++++++++++++++++++++++++++ channel.go | 19 ++- client.go | 50 +++++++- group.go | 356 +++++++++++++++++++++++++++++++++++++++++++++++++++++ message.go | 326 ++++++++++++++++++++++++++++++++++++++++++++++-- server.go | 86 ++++++++++--- 7 files changed, 1047 insertions(+), 34 deletions(-) create mode 100644 acl.go create mode 100644 group.go diff --git a/Makefile b/Makefile index d6083da..b2bdfa4 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,9 @@ GOFILES = \ tlsserver.go \ server.go \ client.go \ - channel.go + channel.go \ + acl.go \ + group.go .PHONY: grumble grumble: pkg diff --git a/acl.go b/acl.go new file mode 100644 index 0000000..9ca83ca --- /dev/null +++ b/acl.go @@ -0,0 +1,240 @@ +// 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 + +const ( + // Per-channel permissions + NonePermission = 0x0 + WritePermission = 0x1 + TraversePermission = 0x2 + EnterPermission = 0x4 + SpeakPermission = 0x8 + MuteDeafenPermission = 0x10 + MovePermission = 0x20 + MakeChannelPermission = 0x40 + LinkChannelPermission = 0x80 + WhisperPermission = 0x100 + TextMessagePermission = 0x200 + TempChannelPermission = 0x400 + + // Root channel only + KickPermission = 0x10000 + BanPermission = 0x20000 + RegisterPermission = 0x40000 + SelfRegisterPermission = 0x80000 + + // Extra flags + CachedPermission = 0x8000000 + AllPermissions = 0xf07ff +) + +type Permission uint32 + +// Check whether the given flags are set on perm +func (perm Permission) IsSet(check Permission) bool { + return perm&check == check +} + +// Check whether the Permission is marked as cached +// (i.e. that it was read from an ACLCache) +func (perm Permission) IsCached() bool { + return perm.IsSet(CachedPermission) +} + +// Clear a flag in the Permission +func (perm Permission) ClearFlag(flag Permission) { + perm &= ^flag +} + +// Clear the cache bit in the Permission +func (perm Permission) ClearCacheBit() { + perm.ClearFlag(CachedPermission) +} + +// A channel-to-permission mapping used in the ACLCache +type ChannelCache map[int]Permission +// The ACLCache maps a user id to a ChannelCache map. +// The ChannelCache map maps a channel to its permissions. +type ACLCache map[uint32]ChannelCache + +// Creates a new ACLCache +func NewACLCache() ACLCache { + return make(map[uint32]ChannelCache) +} + +// Store a client's permissions for a particular channel. When the permissions are stored, +// the permission will have the CachedPermission flag added to it. +func (cache ACLCache) StorePermission(client *Client, channel *Channel, perm Permission) { + chancache, ok := cache[client.Session] + if !ok { + chancache = make(map[int]Permission) + cache[client.Session] = chancache + } + chancache[channel.Id] = perm | CachedPermission +} + +// Get a client's permissions for a partcular channel. NonePermission will be returned +// on error. To determine whether the returned value was retrieved from the cache, the +// caller must call IsCached() on the returned permission. +func (cache ACLCache) GetPermission(client *Client, channel *Channel) (perm Permission) { + chancache, ok := cache[client.Session] + perm = Permission(NonePermission) + if !ok { + return + } + perm, ok = chancache[channel.Id] + if !ok { + perm = Permission(NonePermission) + return + } + return +} + +// An ACL as defined on a channel. +// An ACL can be defined for either a user or a group. +type ChannelACL struct { + // The channel that the ChannelACL is defined on. + Channel *Channel + + // The user id that this ACL applied to. If this + // field is -1, the ACL is a group ACL. + UserId int + // The group that this ACL applies to. + Group string + + // The ApplyHere flag determines whether the ACL + // should apply to the current channel. + ApplyHere bool + // The ApplySubs flag determines whethr the ACL + // should apply to subchannels. + ApplySubs bool + + // The allowed permission flags. + Allow Permission + // The allowed permission flags. The Deny flags override + // permissions set in Allow. + Deny Permission +} + +// Returns true if the ACL is defined on a user +// (as opposed to a group) +func (acl ChannelACL) IsUserACL() bool { + return acl.UserId != -1 +} + +// Returns true if the ACL is defined on a channel +// (as opposed to a user) +func (acl ChannelACL) IsChannelACL() bool { + return !acl.IsUserACL() +} + +// Create a new ACL for channel. Does not add it to the channel's +// ACL list. This must be done manually. +func NewChannelACL(channel *Channel) *ChannelACL { + return &ChannelACL{ + Channel: channel, + UserId: -1, + } +} + +// Check whether client has permission perm on channel. Perm *must* be a single permission, +// and not a combination of permissions. +func (server *Server) HasPermission(client *Client, channel *Channel, perm Permission) bool { + // SuperUser can't speak or whisper, but everything else is OK + if client.UserId == 0 { + if perm == SpeakPermission || perm == WhisperPermission { + return false + } + return true + } + + // First, try to look in the server's ACLCache. + granted := Permission(NonePermission) + cached := server.aclcache.GetPermission(client, channel) + if cached.IsCached() { + granted = cached + // The +write permission implies all permissions except for +speak and +whisper. + // For more information regarding this check, please see the comment regarding a simmilar + // check at the bottom of this function. + if perm != SpeakPermission && perm != WhisperPermission { + return (granted & (perm | WritePermission)) != NonePermission + } else { + return (granted & perm) != NonePermission + } + } + + // Default permissions + def := Permission(TraversePermission | EnterPermission | SpeakPermission | WhisperPermission | TextMessagePermission) + granted = def + + channels := []*Channel{} + iter := channel + for iter != nil { + channels = append([]*Channel{iter}, channels...) + iter = iter.parent + } + + traverse := true + write := false + + for _, iter := range channels { + // If the channel does not inherit any ACLs, use the default permissions. + if !iter.InheritACL { + granted = def + } + // Iterate through ACLs that are defined on iter. Note: this does not include + // ACLs that iter has inherited from a parent (unless there is also a group on + // iter with the same name, that changes the permissions a bit!) + for _, acl := range iter.ACL { + // Determine whether the ACL applies to client. If it is + // a user ACL and the user id of the ACL matches client, we're good to go. + // + // If it's a group ACL, we have to parse and interpret the group string in the + // current context to determine membership. For that we use GroupMemberCheck. + matchUser := acl.IsUserACL() && acl.UserId == client.UserId + matchGroup := GroupMemberCheck(channel, iter, acl.Group, client) + if matchUser || matchGroup { + if acl.Allow.IsSet(TraversePermission) { + traverse = true + } + if acl.Deny.IsSet(TraversePermission) { + traverse = false + } + if acl.Allow.IsSet(WritePermission) { + write = true + } + if acl.Deny.IsSet(WritePermission) { + write = false + } + if (channel == iter && acl.ApplyHere) || (channel != iter && acl.ApplySubs) { + granted |= acl.Allow + granted &= ^acl.Deny + } + } + // If traverse is not set and the user doesn't have write permissions + // on the channel, the user will not have any permissions. + // This is because -traverse removes all permissions, and +write grants + // all permissions. + if !traverse && !write { + granted = NonePermission + break + } + } + } + + // Cache the result + server.aclcache.StorePermission(client, channel, granted) + + // The +write permission implies all permissions except for +speak and +whisper. + // This means that if the user has WritePermission, we should return true for all + // permissions exccept SpeakPermission and WhisperPermission. + if perm != SpeakPermission && perm != WhisperPermission { + return (granted & (perm | WritePermission)) != NonePermission + } else { + return (granted & perm) != NonePermission + } + + return false +} diff --git a/channel.go b/channel.go index 034ba42..3172f7a 100644 --- a/channel.go +++ b/channel.go @@ -1,3 +1,7 @@ +// 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 // A Mumble channel @@ -8,10 +12,17 @@ type Channel struct { Temporary bool Position int - clients map[uint32]*Client + clients map[uint32]*Client - parent *Channel - children map[int]*Channel + parent *Channel + children map[int]*Channel + + // ACL + ACL []*ChannelACL + InheritACL bool + + // Groups + Groups map[string]*Group } func NewChannel(id int, name string) (channel *Channel) { @@ -20,6 +31,8 @@ func NewChannel(id int, name string) (channel *Channel) { channel.Name = name channel.clients = make(map[uint32]*Client) channel.children = make(map[int]*Channel) + channel.ACL = []*ChannelACL{} + channel.Groups = map[string]*Group{} return } diff --git a/client.go b/client.go index de78d15..dede554 100644 --- a/client.go +++ b/client.go @@ -37,14 +37,17 @@ type Client struct { udp bool // Personal + UserId int Session uint32 Username string + Hash string Tokens []string Channel *Channel } // Something invalid happened on the wire. func (client *Client) Panic(reason string) { + log.Printf("Client panic: %s", reason) client.Disconnect() } @@ -54,6 +57,8 @@ func (client *Client) Disconnect() { close(client.udprecv) close(client.msgchan) + client.conn.Close() + client.server.RemoveClient(client) } } @@ -108,6 +113,49 @@ func (c *Client) sendProtoMessage(kind uint16, msg interface{}) (err os.Error) { return } +// Send permission denied by type +func (c *Client) sendPermissionDeniedType(kind string) { + val, ok := mumbleproto.PermissionDenied_DenyType_value[kind] + if ok { + d, err := proto.Marshal(&mumbleproto.PermissionDenied{ + Type: mumbleproto.NewPermissionDenied_DenyType(val), + }) + if err != nil { + c.Panic(err.String()) + return + } + c.msgchan <- &Message{ + buf: d, + kind: MessagePermissionDenied, + } + } else { + log.Printf("Unknown permission denied type.") + } +} + +// Send permission denied by who, what, where +func (c *Client) sendPermissionDenied(who *Client, where *Channel, what Permission) { + d, err := proto.Marshal(&mumbleproto.PermissionDenied{ + Permission: proto.Uint32(uint32(what)), + ChannelId: proto.Uint32(uint32(where.Id)), + Session: proto.Uint32(who.Session), + Type: mumbleproto.NewPermissionDenied_DenyType(mumbleproto.PermissionDenied_Permission), + }) + if err != nil { + c.Panic(err.String()) + } + c.msgchan <- &Message{ + buf: d, + kind: MessagePermissionDenied, + } +} + +// Send permission denied fallback +func (c *Client) sendPermissionDeniedFallback(kind string, version uint32, text string) { + // fixme(mkrautz): Do fallback kind of stuff... + c.sendPermissionDeniedType(kind) +} + // UDP receiver. func (client *Client) udpreceiver() { for buf := range client.udprecv { @@ -152,7 +200,7 @@ func (client *Client) udpreceiver() { buf: outbuf[0 : 1+outgoing.Size()], target: target, } - // Server loopback + // Server loopback } else { client.sendUdp(&Message{ buf: outbuf[0 : 1+outgoing.Size()], diff --git a/group.go b/group.go new file mode 100644 index 0000000..0051713 --- /dev/null +++ b/group.go @@ -0,0 +1,356 @@ +// 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" + "strings" + "strconv" +) + +type Group struct { + // The channel that this group resides in + Channel *Channel + + // The name of this group + Name string + + // The inherit flag means that this group will inherit group + // members from its parent. + Inherit bool + + // The inheritable flag means that subchannels can + // inherit the members of this group. + Inheritable bool + + // Group adds permissions to these users + Add map[int]bool + // Group removes permissions from these users + Remove map[int]bool + // Temporary add (authenticators) + Temporary map[int]bool +} + +// Create a new group for channel with name. Does not add it to the channels +// group list. +func NewGroup(channel *Channel, name string) *Group { + grp := &Group{} + grp.Channel = channel + grp.Name = name + grp.Add = make(map[int]bool) + grp.Remove = make(map[int]bool) + grp.Temporary = make(map[int]bool) + return grp +} + +// Check whether the Add set contains id. +func (group *Group) AddContains(id int) (ok bool) { + _, ok = group.Add[id] + return +} + +// Check whether the Remove set contains id. +func (group *Group) RemoveContains(id int) (ok bool) { + _, ok = group.Remove[id] + return +} + +// Check whether the Temporary set contains id. +func (group *Group) TemporaryContains(id int) (ok bool) { + _, ok = group.Temporary[id] + return +} + +// Get the set of user id's from the group. This includes group +// members that have been inherited from an ancestor. +func (group *Group) Members() map[int]bool { + groups := []*Group{} + members := map[int]bool{} + + // The channel that the group is defined on. + channel := group.Channel + + // Walk a group's channel tree, starting with the channel the group + // is defined on, followed by its parent channels. + iter := group.Channel + for iter != nil { + curgroup := iter.Groups[group.Name] + if curgroup != nil { + // If the group is not inheritable, and we're looking at an + // ancestor group, we've looked in all the groups we should. + if iter != channel && !curgroup.Inheritable { + break + } + // Add the group to the list of groups to be considered + groups = append([]*Group{curgroup}, groups...) + // If this group does not inherit from groups in its ancestors, stop looking + // for more ancestor groups. + if !curgroup.Inherit { + break + } + } + iter = iter.parent + } + + for _, curgroup := range groups { + for uid, _ := range curgroup.Add { + members[uid] = true + } + for uid, _ := range curgroup.Remove { + members[uid] = false, false + } + } + + return members +} + +// Checks whether a user is a member of the group as defined on channel. +// The channel current is the channel that group membership is currently being evaluated for. +// The channel aclchan is the channel that the group is defined on. This means that current inherits +// the group from an acl in aclchan. +// +// The channel aclchan will always be either equal to current, or be an ancestor. +func GroupMemberCheck(current *Channel, aclchan *Channel, name string, client *Client) bool { + invert := false + token := false + hash := false + + // Returns the 'correct' return value considering the value + // of the invert flag. + retvalify := func(in bool) bool { + if invert { + return !in + } + return in + } + + member := false + channel := current + + for { + // Empty group name are not valid. + if len(name) == 0 { + return false + } + // Invert + if name[0] == '!' { + invert = true + name = name[1:] + continue + } + // Evaluate in ACL context (not current channel) + if name[0] == '~' { + channel = aclchan + name = name[1:] + continue + } + // Token + if name[0] == '#' { + token = true + name = name[1:] + continue + } + // Hash + if name[0] == '$' { + hash = true + name = name[1:] + continue + } + break + } + + // The user is part of this group if the remaining name is part of + // his access token list. + if token { + log.Printf("GroupMemberCheck: Implement token matching") + member = false // fixme(mkrautz) + // The user is part of this group if the remaining name matches his + // cert hash. + } else if hash { + log.Printf("GroupMemberCheck: Implement hash matching") + member = false // fixme(mkrautz) + // None + } else if name == "none" { + member = false + // Everyone + } else if name == "all" { + member = true + // The user is part of the auth group is he is authenticated. That is, + // his UserId is >= 0. + } else if name == "auth" { + member = client.UserId >= 0 + // The user is part of the strong group if he is authenticated to the server + // via a strong certificate (i.e. non-self-signed). + } else if name == "strong" { + log.Printf("GroupMemberCheck: Implement strong certificate matching") + member = false // fixme(mkrautz) + // Is the user in the currently evaluated channel? + } else if name == "in" { + member = client.Channel == channel + // Is the user not in the currently evaluated channel? + } else if name == "out" { + member = client.Channel != channel + // fixme(mkrautz): The sub group implementation below hasn't been thoroughly + // tested yet. It might be a bit buggy! + } else if name == "sub" { + // Strip away the "sub," part of the name + name = name[4:] + + mindesc := 1 + maxdesc := 1000 + minpath := 0 + + // Parse the groupname to extract the values we should use + // for minpath (first argument), mindesc (second argument), + // and maxdesc (third argument). + args := strings.Split(name, ",", 3) + nargs := len(args) + if nargs == 3 { + if len(args[2]) > 0 { + if result, err := strconv.Atoi(args[2]); err == nil { + maxdesc = result + } + } + } + if nargs >= 2 { + if len(args[1]) > 0 { + if result, err := strconv.Atoi(args[1]); err == nil { + mindesc = result + } + } + } + if nargs >= 1 { + if len(args[0]) > 0 { + if result, err := strconv.Atoi(args[0]); err == nil { + minpath = result + } + } + } + + // Build a chain of channels, starting from the client's current channel. + playerChain := []*Channel{} + iter := client.Channel + for iter != nil { + playerChain = append([]*Channel{iter}, playerChain...) + iter = iter.parent + } + // Build a chain of channels, starting from the channel current. This is + // the channel that group membership is checked against, notwithstanding + // the ~ group operator. + groupChain := []*Channel{} + iter = current + for iter != nil { + groupChain = append([]*Channel{iter}, groupChain...) + iter = iter.parent + } + + // Helper function that finds the given channel in the channels slice. + // Returns -1 if the given channel was not found in the slice. + indexOf := func(channels []*Channel, channel *Channel) int { + for i, iter := range channels { + if iter == channel { + return i + } + } + return -1 + } + + // Find the index of channel that the group is currently being evaluated on. + // This can be either aclchan or current depending on the ~ group operator. + cofs := indexOf(groupChain, channel) + if cofs == -1 { + log.Printf("Invalid chain") + return false + } + + // Add the first parameter of our sub group to cofs to get our 'base' channel. + cofs += minpath + // Check that the minpath parameter that was given is a valid index for groupChain. + if cofs >= len(groupChain) { + return retvalify(false) + } else if cofs < 0 { + cofs = 0 + } + + // If our 'base' channel is not in the playerChain, the group does not apply to the client. + if indexOf(playerChain, groupChain[cofs]) == -1 { + return retvalify(false) + } + + // Down here, we're certain that the playerChain includes the base channel + // *somewhere*. We must now determine if the path depth makes the user a + // member of the group. + mindepth := cofs + mindesc + maxdepth := cofs + maxdesc + pdepth := len(playerChain) - 1 + member = pdepth >= mindepth && pdepth <= maxdepth + + // Non-magic groups + } else { + groups := []*Group{} + + iter := channel + for iter != nil { + if group, ok := iter.Groups[name]; ok { + // Skip non-inheritable groups if we're in parents + // of our evaluated channel. + if iter != channel && !group.Inheritable { + break + } + // Prepend group + groups = append([]*Group{group}, groups...) + // If this group does not inherit from groups in its ancestors, stop looking + // for more ancestor groups. + if !group.Inherit { + break + } + } + iter = iter.parent + } + + for _, group := range groups { + if group.AddContains(client.UserId) || group.TemporaryContains(client.UserId) || group.TemporaryContains(-int(client.Session)) { + member = true + } + if group.RemoveContains(client.UserId) { + member = false + } + } + } + + return retvalify(member) +} + +// Get the list of group names in a particular channel. +// This function walks the through the channel and all its +// parent channels to figure out all groups that affect +// the channel while considering group inheritance. +func (channel *Channel) GroupNames() map[string]bool { + names := map[string]bool{} + + // Construct a list of channels. Fartherst away ancestors + // are put in front of the list, allowing us to linearly + // iterate the list to determine inheritance. + channels := []*Channel{} + iter := channel + for iter != nil { + channels = append([]*Channel{iter}, channels...) + iter = iter.parent + } + + // Walk through all channels and groups in them. + for _, iter := range channels { + for _, group := range iter.Groups { + // A non-inheritable group in parent. Discard it. + if channel != iter && !group.Inheritable { + names[group.Name] = false, false + // An inheritable group. Add it to the list. + } else { + names[group.Name] = true + } + } + } + return names +} diff --git a/message.go b/message.go index 19f1b07..811ccca 100644 --- a/message.go +++ b/message.go @@ -1,4 +1,3 @@ -// Grumble - an implementation of Murmur in 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. @@ -161,24 +160,47 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { // Has a channel ID if userstate.ChannelId != nil { // Destination channel - dstChan := server.channels[int(*userstate.ChannelId)] - log.Printf("dstChan = %v", dstChan) + 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 the 'move' permission // on the user's channel to move. + if actor != user && !server.HasPermission(actor, user.Channel, MovePermission) { + client.sendPermissionDenied(actor, user.Channel, MovePermission) + return + } // Check whether the actor has 'move' permissions on dstChan. Check whether user has 'enter' // permissions on dstChan. + if !server.HasPermission(actor, dstChan, MovePermission) && !server.HasPermission(user, dstChan, EnterPermission) { + client.sendPermissionDenied(user, dstChan, EnterPermission) + return + } // Check whether the channel is full. + // fixme(mkrautz): See above. } 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 @@ -190,8 +212,15 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { 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 { + return + } } // Check if the text is allowed. @@ -209,9 +238,20 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { if userstate.UserId != nil { // If user == actor, check for 'selfregister' permission on root channel. // If user != actor, check for 'register' 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 + } - // Check if the UserId in the message is >= 0. A registration attempt - // must use a negative UserId. + // If user's hash is empty, deny... + // fixme(mkrautz) } // Prevent self-targetting state changes to be applied to other users @@ -226,7 +266,7 @@ 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) { - return + return } } @@ -234,6 +274,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { 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) @@ -242,15 +283,50 @@ func (server *Server) handleTextMessage(client *Client, msg *Message) { return } - users := []*Client{} - for i := 0; i < len(txtmsg.Session); i++ { - user, ok := server.clients[txtmsg.Session[i]] - if !ok { - log.Panic("Could not look up client by session") + // 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 + } } - users = append(users, 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), @@ -259,7 +335,214 @@ func (server *Server) handleTextMessage(client *Client, msg *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 @@ -274,5 +557,22 @@ func (server *Server) handleUserStatsMessage(client *Client, msg *Message) { if err != nil { client.Panic(err.String()) } - log.Printf("UserStatsMessage") + + 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) } diff --git a/server.go b/server.go index 069aa9e..a9de636 100644 --- a/server.go +++ b/server.go @@ -63,9 +63,12 @@ type Server struct { PreferAlphaCodec bool // Channels - chanid int - root *Channel - channels map[int]*Channel + chanid int + root *Channel + channels map[int]*Channel + + // ACL cache + aclcache ACLCache } // Allocate a new Murmur instance @@ -93,6 +96,8 @@ func NewServer(addr string, port int) (s *Server, err os.Error) { subChan := s.NewChannel("SubChannel") s.root.AddChild(subChan) + s.aclcache = NewACLCache() + go s.handler() return @@ -117,6 +122,8 @@ func (server *Server) NewClient(conn net.Conn) (err os.Error) { client.msgchan = make(chan *Message) client.udprecv = make(chan []byte) + client.UserId = -1 + go client.receiver() go client.udpreceiver() go client.sender() @@ -149,7 +156,7 @@ func (server *Server) RemoveClient(client *Client) { channel.RemoveClient(client) err := server.broadcastProtoMessage(MessageUserRemove, &mumbleproto.UserRemove{ - Session: proto.Uint32(client.Session), + Session: proto.Uint32(client.Session), }) if err != nil { // server panic @@ -166,7 +173,7 @@ func (server *Server) NewChannel(name string) (channel *Channel) { // Remove a channel from the server. func (server *Server) RemoveChanel(channel *Channel) { - if (channel.Id == 0) { + if channel.Id == 0 { log.Printf("Attempted to remove root channel.") return } @@ -284,22 +291,41 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { // Broadcast the the user entered a channel server.root.AddClient(client) - err = server.broadcastProtoMessage(MessageUserState, &mumbleproto.UserState{ + + if client.Username == "SuperUser" { + client.UserId = 0 + } + + userstate := &mumbleproto.UserState{ Session: proto.Uint32(client.Session), Name: proto.String(client.Username), ChannelId: proto.Uint32(0), - }) - if err != nil { + } + if client.UserId >= 0 { + userstate.UserId = proto.Uint32(uint32(client.UserId)) + } + if err := client.sendProtoMessage(MessageUserState, userstate); err != nil { client.Panic(err.String()) } server.sendUserList(client) - err = client.sendProtoMessage(MessageServerSync, &mumbleproto.ServerSync{ - Session: proto.Uint32(client.Session), - MaxBandwidth: proto.Uint32(server.MaxBandwidth), - }) - if err != nil { + sync := &mumbleproto.ServerSync{} + sync.Session = proto.Uint32(client.Session) + sync.MaxBandwidth = proto.Uint32(server.MaxBandwidth) + if client.UserId == 0 { + sync.Permissions = proto.Uint64(uint64(AllPermissions)) + } else { + server.HasPermission(client, server.root, EnterPermission) + perm := server.aclcache.GetPermission(client, server.root) + if !perm.IsCached() { + client.Panic("Corrupt ACL cache") + return + } + perm.ClearCacheBit() + sync.Permissions = proto.Uint64(uint64(perm)) + } + if err = client.sendProtoMessage(MessageServerSync, sync); err != nil { client.Panic(err.String()) return } @@ -377,6 +403,9 @@ func (server *Server) sendUserList(client *Client) { if user.state != StateClientAuthenticated { continue } + if user == client { + continue + } err := client.sendProtoMessage(MessageUserState, &mumbleproto.UserState{ Session: proto.Uint32(user.Session), @@ -392,6 +421,26 @@ func (server *Server) sendUserList(client *Client) { } +// Send a client its permissions for channel. +func (server *Server) sendClientPermissions(client *Client, channel *Channel) { + // No caching for SuperUser + if client.UserId == 0 { + return + } + + // Update cache + server.HasPermission(client, channel, EnterPermission) + + perm := server.aclcache.GetPermission(client, channel) + log.Printf("Permissions = 0x%x", perm) + + // fixme(mkrautz): Cache which permissions we've already sent. + client.sendProtoMessage(MessagePermissionQuery, &mumbleproto.PermissionQuery{ + ChannelId: proto.Uint32(uint32(channel.Id)), + Permissions: proto.Uint32(uint32(perm)), + }) +} + func (server *Server) broadcastProtoMessage(kind uint16, msg interface{}) (err os.Error) { for _, client := range server.clients { if client.state != StateClientAuthenticated { @@ -438,7 +487,7 @@ func (server *Server) handleIncomingMessage(client *Client, msg *Message) { case MessageVoiceTarget: log.Printf("MessageVoiceTarget from client") case MessagePermissionQuery: - log.Printf("MessagePermissionQuery from client") + server.handlePermissionQuery(msg.client, msg) case MessageCodecVersion: log.Printf("MessageCodecVersion from client") case MessageUserStats: @@ -470,11 +519,11 @@ func (s *Server) SendUDP() { crypted := make([]byte, len(msg.buf)+4) msg.client.crypt.Encrypt(crypted, msg.buf) s.udpconn.WriteTo(crypted, msg.client.udpaddr) - // Non-encrypted + // Non-encrypted } else if msg.address != nil { s.udpconn.WriteTo(msg.buf, msg.address) } else { - // Skipping + // Skipping } } } @@ -564,6 +613,11 @@ func (server *Server) ListenUDP() { } } +// Clear the ACL cache +func (s *Server) ClearACLCache() { + s.aclcache = NewACLCache() +} + // The accept loop of the server. func (s *Server) ListenAndMurmur() {