From 495705f538f969a32fb846dd1583fb5451da522b Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 25 May 2018 02:46:36 -0400 Subject: [PATCH] implement SAJOIN, allow channel founders to join unconditionally --- irc/channel.go | 21 ++++++++++++--------- irc/channelmanager.go | 4 ++-- irc/commands.go | 5 +++++ irc/handlers.go | 34 +++++++++++++++++++++++++++++++++- irc/help.go | 7 +++++++ oragono.yaml | 1 + 6 files changed, 60 insertions(+), 12 deletions(-) diff --git a/irc/channel.go b/irc/channel.go index a48e7d8b..73749d6c 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -350,32 +350,36 @@ func (channel *Channel) IsEmpty() bool { } // Join joins the given client to this channel (if they can be joined). -//TODO(dan): /SAJOIN and maybe a ForceJoin function? -func (channel *Channel) Join(client *Client, key string, rb *ResponseBuffer) { +func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) { if channel.hasClient(client) { // already joined, no message needs to be sent return } - chname := channel.Name() + channel.stateMutex.RLock() + chname := channel.name + founder := channel.registeredFounder + channel.stateMutex.RUnlock() + account := client.Account() + hasPrivs := isSajoin || (founder != "" && founder == account) - if channel.IsFull() { + if !hasPrivs && channel.IsFull() { rb.Add(nil, client.server.name, ERR_CHANNELISFULL, chname, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "l")) return } - if !channel.CheckKey(key) { + if !hasPrivs && !channel.CheckKey(key) { rb.Add(nil, client.server.name, ERR_BADCHANNELKEY, chname, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "k")) return } isInvited := channel.lists[modes.InviteMask].Match(client.nickMaskCasefolded) - if channel.flags.HasMode(modes.InviteOnly) && !isInvited { + if !hasPrivs && channel.flags.HasMode(modes.InviteOnly) && !isInvited { rb.Add(nil, client.server.name, ERR_INVITEONLYCHAN, chname, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "i")) return } - if channel.lists[modes.BanMask].Match(client.nickMaskCasefolded) && + if !hasPrivs && channel.lists[modes.BanMask].Match(client.nickMaskCasefolded) && !isInvited && !channel.lists[modes.ExceptMask].Match(client.nickMaskCasefolded) { rb.Add(nil, client.server.name, ERR_BANNEDFROMCHAN, chname, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "b")) @@ -389,7 +393,6 @@ func (channel *Channel) Join(client *Client, key string, rb *ResponseBuffer) { defer channel.joinPartMutex.Unlock() func() { - account := client.Account() channel.stateMutex.Lock() defer channel.stateMutex.Unlock() @@ -779,7 +782,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb return } if !channel.ClientHasPrivsOver(client, target) { - rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator")) + rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You don't have enough channel privileges")) return } diff --git a/irc/channelmanager.go b/irc/channelmanager.go index 3d58c543..5729dcfe 100644 --- a/irc/channelmanager.go +++ b/irc/channelmanager.go @@ -45,7 +45,7 @@ func (cm *ChannelManager) Get(name string) *Channel { } // Join causes `client` to join the channel named `name`, creating it if necessary. -func (cm *ChannelManager) Join(client *Client, name string, key string, rb *ResponseBuffer) error { +func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error { server := client.server casefoldedName, err := CasefoldChannel(name) if err != nil || len(casefoldedName) > server.Limits().ChannelLen { @@ -74,7 +74,7 @@ func (cm *ChannelManager) Join(client *Client, name string, key string, rb *Resp entry.pendingJoins += 1 cm.Unlock() - entry.channel.Join(client, key, rb) + entry.channel.Join(client, key, isSajoin, rb) cm.maybeCleanup(entry.channel, true) diff --git a/irc/commands.go b/irc/commands.go index 8b21e3e4..02e969e1 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -229,6 +229,11 @@ func init() { usablePreReg: true, minParams: 1, }, + "SAJOIN": { + handler: sajoinHandler, + minParams: 1, + capabs: []string{"sajoin"}, + }, "SANICK": { handler: sanickHandler, minParams: 2, diff --git a/irc/handlers.go b/irc/handlers.go index a41a538a..df8def48 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -889,7 +889,7 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp if len(keys) > i { key = keys[i] } - err := server.channels.Join(client, name, key, rb) + err := server.channels.Join(client, name, key, false, rb) if err == errNoSuchChannel { rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), name, client.t("No such channel")) } @@ -897,6 +897,38 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp return false } +// SAJOIN [nick] #channel{,#channel} +func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { + var target *Client + var channelString string + if strings.HasPrefix(msg.Params[0], "#") { + target = client + channelString = msg.Params[0] + } else { + if len(msg.Params) == 1 { + rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), "KICK", client.t("Not enough parameters")) + return false + } else { + target = server.clients.Get(msg.Params[0]) + if target == nil { + rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), msg.Params[0], "No such nick") + return false + } + channelString = msg.Params[1] + rb = NewResponseBuffer(target) + } + } + + channels := strings.Split(channelString, ",") + for _, chname := range channels { + server.channels.Join(target, chname, "", true, rb) + } + if client != target { + rb.Send() + } + return false +} + // KICK {,} {,} [] func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { channels := strings.Split(msg.Params[0], ",") diff --git a/irc/help.go b/irc/help.go index f8b42aa5..7ec87912 100644 --- a/irc/help.go +++ b/irc/help.go @@ -400,6 +400,13 @@ Renames the given channel with the given reason, if possible. For example: RENAME #ircv2 #ircv3 :Protocol upgrades!`, + }, + "sajoin": { + oper: true, + text: `SAJOIN [nick] #channel{,#channel} + +Forcibly joins a user to a channel, ignoring restrictions like bans, user limits +and channel keys. If [nick] is omitted, it defaults to the operator.`, }, "sanick": { oper: true, diff --git a/oragono.yaml b/oragono.yaml index 020d5226..69f6400c 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -281,6 +281,7 @@ oper-classes: - "oper:rehash" - "oper:die" - "unregister" + - "sajoin" - "samode" - "vhosts"