diff --git a/irc/channel.go b/irc/channel.go index f862a844..a6755d22 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -11,6 +11,8 @@ import ( "strconv" "time" + "sync" + "github.com/DanielOaks/girc-go/ircmsg" ) @@ -18,6 +20,7 @@ type Channel struct { flags ChannelModeSet lists map[ChannelMode]*UserMaskSet key string + membersMutex sync.RWMutex members MemberSet name string nameCasefolded string @@ -63,6 +66,8 @@ func NewChannel(s *Server, name string, addDefaultModes bool) *Channel { } func (channel *Channel) IsEmpty() bool { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() return len(channel.members) == 0 } @@ -93,6 +98,9 @@ func (channel *Channel) Names(client *Client) { // ClientIsAtLeast returns whether the client has at least the given channel privilege. func (channel *Channel) ClientIsAtLeast(client *Client, permission ChannelMode) bool { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + // get voice, since it's not a part of ChannelPrivModes if channel.members.HasMode(client, permission) { return true @@ -134,6 +142,9 @@ func (modes ChannelModeSet) Prefixes(isMultiPrefix bool) string { } func (channel *Channel) Nicks(target *Client) []string { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + isMultiPrefix := (target != nil) && target.capabilities[MultiPrefix] isUserhostInNames := (target != nil) && target.capabilities[UserhostInNames] nicks := make([]string, len(channel.members)) @@ -160,7 +171,9 @@ func (channel *Channel) Nick() string { // func (channel *Channel) ModeString(client *Client) (str string) { + channel.membersMutex.RLock() isMember := client.flags[Operator] || channel.members.Has(client) + channel.membersMutex.RUnlock() showKey := isMember && (channel.key != "") showUserLimit := channel.userLimit > 0 @@ -192,6 +205,9 @@ func (channel *Channel) ModeString(client *Client) (str string) { } func (channel *Channel) IsFull() bool { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + return (channel.userLimit > 0) && (uint64(len(channel.members)) >= channel.userLimit) } @@ -201,6 +217,9 @@ func (channel *Channel) CheckKey(key string) bool { } func (channel *Channel) Join(client *Client, key string) { + channel.membersMutex.Lock() + defer channel.membersMutex.Unlock() + if channel.members.Has(client) { // already joined, no message? return @@ -256,6 +275,9 @@ func (channel *Channel) Join(client *Client, key string) { } func (channel *Channel) Part(client *Client, message string) { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + if !channel.members.Has(client) { client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return @@ -268,6 +290,9 @@ func (channel *Channel) Part(client *Client, message string) { } func (channel *Channel) GetTopic(client *Client) { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + if !channel.members.Has(client) { client.Send(nil, client.server.name, ERR_NOTONCHANNEL, client.nick, channel.name, "You're not on that channel") return @@ -283,6 +308,9 @@ func (channel *Channel) GetTopic(client *Client) { } func (channel *Channel) SetTopic(client *Client, topic string) { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + if !(client.flags[Operator] || channel.members.Has(client)) { client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return @@ -307,6 +335,9 @@ func (channel *Channel) SetTopic(client *Client, topic string) { } func (channel *Channel) CanSpeak(client *Client) bool { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + if client.flags[Operator] { return true } @@ -335,6 +366,10 @@ func (channel *Channel) sendMessage(cmd string, minPrefix *ChannelMode, clientOn client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return } + + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + // for STATUSMSG var minPrefixMode ChannelMode if minPrefix != nil { @@ -383,6 +418,9 @@ func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode, func (channel *Channel) applyModeMember(client *Client, mode ChannelMode, op ModeOp, nick string) *ChannelModeChange { + channel.membersMutex.Lock() + defer channel.membersMutex.Unlock() + if nick == "" { //TODO(dan): shouldn't this be handled before it reaches this function? client.Send(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters") @@ -466,6 +504,9 @@ func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeO } func (channel *Channel) Quit(client *Client) { + channel.membersMutex.Lock() + defer channel.membersMutex.Unlock() + channel.members.Remove(client) client.channels.Remove(channel) @@ -475,6 +516,9 @@ func (channel *Channel) Quit(client *Client) { } func (channel *Channel) Kick(client *Client, target *Client, comment string) { + channel.membersMutex.Lock() + defer channel.membersMutex.Unlock() + if !(client.flags[Operator] || channel.members.Has(client)) { client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return @@ -504,6 +548,9 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) { return } + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + if !channel.members.Has(inviter) { inviter.Send(nil, inviter.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") return diff --git a/irc/client.go b/irc/client.go index fb738b6e..95b0a90a 100644 --- a/irc/client.go +++ b/irc/client.go @@ -308,6 +308,8 @@ func (client *Client) Friends(Capabilities ...Capability) ClientSet { } for channel := range client.channels { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() for member := range channel.members { // make sure they have all the required caps for _, Cap := range Capabilities { diff --git a/irc/modes.go b/irc/modes.go index 0d29d781..ce8a1ed0 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -345,6 +345,9 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { channelName, err := CasefoldChannel(msg.Params[0]) channel := server.channels.Get(channelName) + channel.membersMutex.Lock() + defer channel.membersMutex.Unlock() + if err != nil || channel == nil { client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, msg.Params[0], "No such channel") return false diff --git a/irc/roleplay.go b/irc/roleplay.go index c23dc8d6..a3ec71bd 100644 --- a/irc/roleplay.go +++ b/irc/roleplay.go @@ -87,12 +87,14 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt return } + channel.membersMutex.RLock() for member := range channel.members { if member == client && !client.capabilities[EchoMessage] { continue } member.Send(nil, source, "PRIVMSG", channel.name, message) } + channel.membersMutex.RUnlock() } else { target, err := CasefoldName(targetString) user := server.clients.Get(target) diff --git a/irc/server.go b/irc/server.go index d472a454..fc1655a0 100644 --- a/irc/server.go +++ b/irc/server.go @@ -843,6 +843,9 @@ func (client *Client) WhoisChannelsNames(target *Client) []string { var chstrs []string index := 0 for channel := range client.channels { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + // channel is secret and the target can't see it if !target.flags[Operator] && channel.flags[Secret] && !channel.members.Has(target) { continue @@ -933,6 +936,9 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) { } if channel != nil { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + flags += channel.members[client].Prefixes(target.capabilities[MultiPrefix]) channelName = channel.name } @@ -940,6 +946,9 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) { } func whoChannel(client *Client, channel *Channel, friends ClientSet) { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + for member := range channel.members { if !client.flags[Invisible] || friends[client] { client.RplWhoReply(channel, member) @@ -1372,6 +1381,9 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // make sure client has privs to kick the given user //TODO(dan): split this into a separate function that checks if users have privs // over other users, useful for things like -aoh as well + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + var hasPrivs bool for _, mode := range ChannelPrivModes { if channel.members[client][mode] { @@ -1448,6 +1460,9 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } func (target *Client) RplList(channel *Channel) { + channel.membersMutex.RLock() + defer channel.membersMutex.RUnlock() + // get the correct number of channel members var memberCount int if target.flags[Operator] || channel.members.Has(target) {