diff --git a/irc/channel.go b/irc/channel.go index d4950e9d..adf29229 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -18,8 +18,8 @@ import ( ) type Channel struct { - flags ChannelModeSet - lists map[ChannelMode]*UserMaskSet + flags ModeSet + lists map[Mode]*UserMaskSet key string membersMutex sync.RWMutex members MemberSet @@ -43,8 +43,8 @@ func NewChannel(s *Server, name string, addDefaultModes bool) *Channel { } channel := &Channel{ - flags: make(ChannelModeSet), - lists: map[ChannelMode]*UserMaskSet{ + flags: make(ModeSet), + lists: map[Mode]*UserMaskSet{ BanMask: NewUserMaskSet(), ExceptMask: NewUserMaskSet(), InviteMask: NewUserMaskSet(), @@ -110,14 +110,14 @@ func (channel *Channel) namesNoMutex(client *Client) { } // ClientIsAtLeast returns whether the client has at least the given channel privilege. -func (channel *Channel) ClientIsAtLeast(client *Client, permission ChannelMode) bool { +func (channel *Channel) ClientIsAtLeast(client *Client, permission Mode) bool { channel.membersMutex.RLock() defer channel.membersMutex.RUnlock() return channel.clientIsAtLeastNoMutex(client, permission) } -func (channel *Channel) clientIsAtLeastNoMutex(client *Client, permission ChannelMode) bool { +func (channel *Channel) clientIsAtLeastNoMutex(client *Client, permission Mode) bool { // requires RLock() // get voice, since it's not a part of ChannelPrivModes @@ -140,7 +140,7 @@ func (channel *Channel) clientIsAtLeastNoMutex(client *Client, permission Channe } // Prefixes returns a list of prefixes for the given set of channel modes. -func (modes ChannelModeSet) Prefixes(isMultiPrefix bool) string { +func (modes ModeSet) Prefixes(isMultiPrefix bool) string { var prefixes string // add prefixes in order from highest to lowest privs @@ -394,11 +394,11 @@ func (channel *Channel) CanSpeak(client *Client) bool { } // TagMsg sends a tag message to everyone in this channel who can accept them. -func (channel *Channel) TagMsg(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) { +func (channel *Channel) TagMsg(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) { channel.sendMessage(msgid, "TAGMSG", []Capability{MessageTags}, minPrefix, clientOnlyTags, client, nil) } -func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) { +func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) { if !channel.CanSpeak(client) { client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return @@ -408,7 +408,7 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability defer channel.membersMutex.RUnlock() // for STATUSMSG - var minPrefixMode ChannelMode + var minPrefixMode Mode if minPrefix != nil { minPrefixMode = *minPrefix } @@ -445,16 +445,16 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability } // SplitPrivMsg sends a private message to everyone in this channel. -func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { +func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { channel.sendSplitMessage(msgid, "PRIVMSG", minPrefix, clientOnlyTags, client, &message) } // SplitNotice sends a private message to everyone in this channel. -func (channel *Channel) SplitNotice(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { +func (channel *Channel) SplitNotice(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { channel.sendSplitMessage(msgid, "NOTICE", minPrefix, clientOnlyTags, client, &message) } -func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *SplitMessage) { +func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *SplitMessage) { if !channel.CanSpeak(client) { client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return @@ -464,7 +464,7 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *ChannelMo defer channel.membersMutex.RUnlock() // for STATUSMSG - var minPrefixMode ChannelMode + var minPrefixMode Mode if minPrefix != nil { minPrefixMode = *minPrefix } @@ -489,7 +489,7 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *ChannelMo } } -func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode, +func (channel *Channel) applyModeFlag(client *Client, mode Mode, op ModeOp) bool { if !channel.ClientIsAtLeast(client, ChannelOperator) { client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") @@ -514,8 +514,8 @@ func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode, return false } -func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode, - op ModeOp, nick string) *ChannelModeChange { +func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode, + op ModeOp, nick string) *ModeChange { // requires Lock() if nick == "" { @@ -542,7 +542,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode, return nil } channel.members[target][mode] = true - return &ChannelModeChange{ + return &ModeChange{ op: Add, mode: mode, arg: nick, @@ -553,7 +553,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode, return nil } channel.members[target][mode] = false - return &ChannelModeChange{ + return &ModeChange{ op: Remove, mode: mode, arg: nick, @@ -562,7 +562,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode, return nil } -func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) { +func (channel *Channel) ShowMaskList(client *Client, mode Mode) { //TODO(dan): WE NEED TO fiX this PROPERLY log.Fatal("Implement ShowMaskList") /* @@ -572,7 +572,7 @@ func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) { client.RplEndOfMaskList(mode, channel)*/ } -func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, mask string) bool { +func (channel *Channel) applyModeMask(client *Client, mode Mode, op ModeOp, mask string) bool { list := channel.lists[mode] if list == nil { // This should never happen, but better safe than panicky. diff --git a/irc/client.go b/irc/client.go index 31abd3b9..6fb18540 100644 --- a/irc/client.go +++ b/irc/client.go @@ -43,7 +43,7 @@ type Client struct { channels ChannelSet class *OperClass ctime time.Time - flags map[UserMode]bool + flags map[Mode]bool isDestroyed bool isQuitting bool hasQuit bool @@ -83,7 +83,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { capVersion: Cap301, channels: make(ChannelSet), ctime: now, - flags: make(map[UserMode]bool), + flags: make(map[Mode]bool), monitoring: make(map[string]bool), server: server, socket: &socket, diff --git a/irc/modes.go b/irc/modes.go index 911cca13..53f001c0 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -178,8 +178,8 @@ func SplitChannelMembershipPrefixes(target string) (prefixes string, name string } // GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes. -func GetLowestChannelModePrefix(prefixes string) *ChannelMode { - var lowest *ChannelMode +func GetLowestChannelModePrefix(prefixes string) *Mode { + var lowest *Mode if strings.Contains(prefixes, "+") { lowest = &Voice @@ -208,8 +208,8 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { return umodeHandler(server, client, msg) } -// applyModeChanges applies the given changes, and returns the applied changes. -func (client *Client) applyModeChanges(ModeChanges) ModeChanges { +// applyUserModeChanges applies the given changes, and returns the applied changes. +func (client *Client) applyUserModeChanges(changes ModeChanges) ModeChanges { applied := make(ModeChanges, 0) for _, change := range changes { @@ -289,13 +289,13 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { op = ModeOp(mode) continue } - changes = append(changes, &ModeChange{ + changes = append(changes, ModeChange{ mode: Mode(mode), op: op, }) } - applied := target.applyModeChanges(changes) + applied = target.applyUserModeChanges(changes) } if len(applied) > 0 { @@ -306,15 +306,219 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { return false } -////// -////// -////// -////// -////// -////// -////// -////// -////// +// ParseChannelModeChanges returns the valid changes, and the list of unknown chars. +func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) { + changes := make(ModeChanges, 0) + unknown := make(map[rune]bool) + + if len(params) > 1 { + modeArg := params[0] + op := ModeOp(modeArg[0]) + if (op == Add) || (op == Remove) { + modeArg = modeArg[1:] + } else { + unknown[rune(modeArg[0])] = true + return changes, unknown + } + + skipArgs := 1 + + for _, mode := range modeArg { + if mode == '-' || mode == '+' { + op = ModeOp(mode) + continue + } + change := ModeChange{ + mode: Mode(mode), + op: op, + } + + // put arg into modechange if needed + switch Mode(mode) { + case BanMask, ExceptMask, InviteMask: + if len(params) > skipArgs { + change.arg = params[skipArgs] + skipArgs++ + } else { + change.op = List + } + case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: + if len(params) > skipArgs { + change.arg = params[skipArgs] + skipArgs++ + } else { + continue + } + case Key, UserLimit: + // don't require value when removing + if change.op == Add { + if len(params) > skipArgs { + change.arg = params[skipArgs] + skipArgs++ + } else { + continue + } + } + default: + unknown[mode] = true + } + + changes = append(changes, change) + } + } + + return changes, unknown +} + +// ApplyChannelModeChanges applies a given set of mode changes. +func ApplyChannelModeChanges(channel *Channel, client *Client, isSamode bool, changes ModeChanges) ModeChanges { + // so we only output one warning for each list type when full + listFullWarned := make(map[Mode]bool) + + clientIsOp := channel.clientIsAtLeastNoMutex(client, ChannelOperator) + var alreadySentPrivError bool + + applied := make(ModeChanges, 0) + + for _, change := range changes { + // chan priv modes are checked specially so ignore them + // means regular users can't view ban/except lists... but I'm not worried about that + if isSamode && ChannelModePrefixes[change.mode] == "" && !clientIsOp { + if !alreadySentPrivError { + alreadySentPrivError = true + client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") + } + continue + } + + switch change.mode { + case BanMask, ExceptMask, InviteMask: + mask := change.arg + list := channel.lists[change.mode] + if list == nil { + // This should never happen, but better safe than panicky. + client.Send(nil, client.server.name, ERR_UNKNOWNERROR, client.nick, "MODE", "Could not complete MODE command") + return changes + } + + if (change.op == List) || (mask == "") { + channel.ShowMaskList(client, change.mode) + continue + } + + // confirm mask looks valid + mask, err := Casefold(mask) + if err != nil { + continue + } + + switch change.op { + case Add: + if len(list.masks) >= client.server.limits.ChanListModes { + if !listFullWarned[change.mode] { + client.Send(nil, client.server.name, ERR_BANLISTFULL, client.nick, channel.name, change.mode.String(), "Channel list is full") + listFullWarned[change.mode] = true + } + continue + } + + list.Add(mask) + applied = append(applied, change) + + case Remove: + list.Remove(mask) + applied = append(applied, change) + } + + case UserLimit: + switch change.op { + case Add: + val, err := strconv.ParseUint(change.arg, 10, 64) + if err == nil { + channel.userLimit = val + applied = append(applied, change) + } + + case Remove: + channel.userLimit = 0 + applied = append(applied, change) + } + + case Key: + switch change.op { + case Add: + channel.key = change.arg + + case Remove: + channel.key = "" + } + applied = append(applied, change) + + case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret, ChanRoleplaying: + switch change.op { + case Add: + if channel.flags[change.mode] { + continue + } + channel.flags[change.mode] = true + applied = append(applied, change) + + case Remove: + if !channel.flags[change.mode] { + continue + } + delete(channel.flags, change.mode) + applied = append(applied, change) + } + + case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: + // make sure client has privs to edit the given prefix + hasPrivs := isSamode + + if !hasPrivs { + for _, mode := range ChannelPrivModes { + if channel.members[client][mode] { + hasPrivs = true + + // Admins can't give other people Admin or remove it from others, + // standard for that channel mode, we worry about this later + if mode == ChannelAdmin && change.mode == ChannelAdmin { + hasPrivs = false + } + + break + } else if mode == change.mode { + break + } + } + } + + casefoldedName, err := CasefoldName(change.arg) + if err != nil { + continue + } + + if !hasPrivs { + if change.op == Remove && casefoldedName == client.nickCasefolded { + // success! + } else { + if !alreadySentPrivError { + alreadySentPrivError = true + client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") + } + continue + } + } + + change := channel.applyModeMemberNoMutex(client, change.mode, change.op, change.arg) + if change != nil { + applied = append(applied, *change) + } + } + } + + return applied +} // MODE [ [...]] func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { @@ -329,208 +533,24 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { return false } - // assemble changes - //TODO(dan): split out assembling changes into func that returns changes, err - changes := make(ChannelModeChanges, 0) - applied := make(ChannelModeChanges, 0) + // applied mode changes + applied := make(ModeChanges, 0) - // TODO(dan): look at separating these into the type A/B/C/D args and using those lists here - if len(msg.Params) > 1 { - modeArg := msg.Params[1] - op := ModeOp(modeArg[0]) - if (op == Add) || (op == Remove) { - modeArg = modeArg[1:] - } else { - client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me") + if 1 < len(msg.Params) { + // parse out real mode changes + params := msg.Params[1:] + changes, unknown := ParseChannelModeChanges(params...) + + // alert for unknown mode changes + for char := range unknown { + client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), "is an unknown mode character to me") + } + if len(unknown) == 1 && len(changes) == 0 { return false } - skipArgs := 2 - for _, mode := range modeArg { - if mode == '-' || mode == '+' { - op = ModeOp(mode) - continue - } - change := ChannelModeChange{ - mode: ChannelMode(mode), - op: op, - } - - // put arg into modechange if needed - switch ChannelMode(mode) { - case BanMask, ExceptMask, InviteMask: - if len(msg.Params) > skipArgs { - change.arg = msg.Params[skipArgs] - skipArgs++ - } else { - change.op = List - } - case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: - if len(msg.Params) > skipArgs { - change.arg = msg.Params[skipArgs] - skipArgs++ - } else { - continue - } - case Key, UserLimit: - // don't require value when removing - if change.op == Add { - if len(msg.Params) > skipArgs { - change.arg = msg.Params[skipArgs] - skipArgs++ - } else { - continue - } - } - } - - changes = append(changes, &change) - } - - // so we only output one warning for each list type when full - listFullWarned := make(map[ChannelMode]bool) - - clientIsOp := channel.clientIsAtLeastNoMutex(client, ChannelOperator) - var alreadySentPrivError bool - - for _, change := range changes { - // chan priv modes are checked specially so ignore them - // means regular users can't view ban/except lists... but I'm not worried about that - if msg.Command != "SAMODE" && ChannelModePrefixes[change.mode] == "" && !clientIsOp { - if !alreadySentPrivError { - alreadySentPrivError = true - client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") - } - continue - } - - switch change.mode { - case BanMask, ExceptMask, InviteMask: - mask := change.arg - list := channel.lists[change.mode] - if list == nil { - // This should never happen, but better safe than panicky. - client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "MODE", "Could not complete MODE command") - return false - } - - if (change.op == List) || (mask == "") { - channel.ShowMaskList(client, change.mode) - continue - } - - // confirm mask looks valid - mask, err = Casefold(mask) - if err != nil { - continue - } - - switch change.op { - case Add: - if len(list.masks) >= server.limits.ChanListModes { - if !listFullWarned[change.mode] { - client.Send(nil, server.name, ERR_BANLISTFULL, client.nick, channel.name, change.mode.String(), "Channel list is full") - listFullWarned[change.mode] = true - } - continue - } - - list.Add(mask) - applied = append(applied, change) - - case Remove: - list.Remove(mask) - applied = append(applied, change) - } - - case UserLimit: - switch change.op { - case Add: - val, err := strconv.ParseUint(change.arg, 10, 64) - if err == nil { - channel.userLimit = val - applied = append(applied, change) - } - - case Remove: - channel.userLimit = 0 - applied = append(applied, change) - } - - case Key: - switch change.op { - case Add: - channel.key = change.arg - - case Remove: - channel.key = "" - } - applied = append(applied, change) - - case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret, ChanRoleplaying: - switch change.op { - case Add: - if channel.flags[change.mode] { - continue - } - channel.flags[change.mode] = true - applied = append(applied, change) - - case Remove: - if !channel.flags[change.mode] { - continue - } - delete(channel.flags, change.mode) - applied = append(applied, change) - } - - case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: - // make sure client has privs to edit the given prefix - var hasPrivs bool - - if msg.Command == "SAMODE" { - hasPrivs = true - } else { - for _, mode := range ChannelPrivModes { - if channel.members[client][mode] { - hasPrivs = true - - // Admins can't give other people Admin or remove it from others, - // standard for that channel mode, we worry about this later - if mode == ChannelAdmin && change.mode == ChannelAdmin { - hasPrivs = false - } - - break - } else if mode == change.mode { - break - } - } - } - - casefoldedName, err := CasefoldName(change.arg) - if err != nil { - continue - } - - if !hasPrivs { - if change.op == Remove && casefoldedName == client.nickCasefolded { - // success! - } else { - if !alreadySentPrivError { - alreadySentPrivError = true - client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator") - } - continue - } - } - - change := channel.applyModeMemberNoMutex(client, change.mode, change.op, change.arg) - if change != nil { - applied = append(applied, change) - } - } - } + // apply mode changes + applied = ApplyChannelModeChanges(channel, client, msg.Command == "SAMODE", changes) } if len(applied) > 0 { diff --git a/irc/server.go b/irc/server.go index 5deace4e..e8cac62c 100644 --- a/irc/server.go +++ b/irc/server.go @@ -339,7 +339,7 @@ func (server *Server) setISupport() { server.isupport = NewISupportList() server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen)) server.isupport.Add("CASEMAPPING", casemappingName) - server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ",")) + server.isupport.Add("CHANMODES", strings.Join([]string{Modes{BanMask, ExceptMask, InviteMask}.String(), "", Modes{UserLimit, Key}.String(), Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ",")) server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen)) server.isupport.Add("CHANTYPES", "#") server.isupport.Add("EXCEPTS", "") @@ -376,7 +376,7 @@ func (server *Server) setISupport() { server.isupport.RegenerateCachedReply() } -func loadChannelList(channel *Channel, list string, maskMode ChannelMode) { +func loadChannelList(channel *Channel, list string, maskMode Mode) { if list == "" { return } @@ -1242,7 +1242,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { client.Send(nil, server.name, RPL_YOUREOPER, client.nick, "You are now an IRC operator") //TODO(dan): Should this be sent automagically as part of setting the flag/mode? - modech := ModeChanges{&ModeChange{ + modech := ModeChanges{ModeChange{ mode: Operator, op: Add, }} @@ -1521,7 +1521,7 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { client.Send(nil, server.name, RPL_UNAWAY, client.nick, "You are no longer marked as being away") } //TODO(dan): Should this be sent automagically as part of setting the flag/mode? - modech := ModeChanges{&ModeChange{ + modech := ModeChanges{ModeChange{ mode: Away, op: op, }} diff --git a/irc/types.go b/irc/types.go index bf119aa9..0b64642e 100644 --- a/irc/types.go +++ b/irc/types.go @@ -39,9 +39,9 @@ func (channels ChannelNameMap) Remove(channel *Channel) error { return nil } -type ChannelModeSet map[ChannelMode]bool +type ModeSet map[Mode]bool -func (set ChannelModeSet) String() string { +func (set ModeSet) String() string { if len(set) == 0 { return "" } @@ -68,10 +68,10 @@ func (clients ClientSet) Has(client *Client) bool { return clients[client] } -type MemberSet map[*Client]ChannelModeSet +type MemberSet map[*Client]ModeSet func (members MemberSet) Add(member *Client) { - members[member] = make(ChannelModeSet) + members[member] = make(ModeSet) } func (members MemberSet) Remove(member *Client) { @@ -83,7 +83,7 @@ func (members MemberSet) Has(member *Client) bool { return ok } -func (members MemberSet) HasMode(member *Client, mode ChannelMode) bool { +func (members MemberSet) HasMode(member *Client, mode Mode) bool { modes, ok := members[member] if !ok { return false @@ -91,7 +91,7 @@ func (members MemberSet) HasMode(member *Client, mode ChannelMode) bool { return modes[mode] } -func (members MemberSet) AnyHasMode(mode ChannelMode) bool { +func (members MemberSet) AnyHasMode(mode Mode) bool { for _, modes := range members { if modes[mode] { return true