mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-13 07:29:30 +01:00
Merge pull request #1528 from slingamn/issue1176_operprivs
enhancements to operator privilege handling
This commit is contained in:
commit
bb39399f97
1
Makefile
1
Makefile
@ -32,6 +32,7 @@ test:
|
|||||||
cd irc/modes && go test . && go vet .
|
cd irc/modes && go test . && go vet .
|
||||||
cd irc/mysql && go test . && go vet .
|
cd irc/mysql && go test . && go vet .
|
||||||
cd irc/passwd && go test . && go vet .
|
cd irc/passwd && go test . && go vet .
|
||||||
|
cd irc/sno && go test . && go vet .
|
||||||
cd irc/utils && go test . && go vet .
|
cd irc/utils && go test . && go vet .
|
||||||
./.check-gofmt.sh
|
./.check-gofmt.sh
|
||||||
|
|
||||||
|
@ -593,6 +593,7 @@ oper-classes:
|
|||||||
- "vhosts"
|
- "vhosts"
|
||||||
- "sajoin"
|
- "sajoin"
|
||||||
- "samode"
|
- "samode"
|
||||||
|
- "snomasks"
|
||||||
|
|
||||||
# server admin: has full control of the ircd, including nickname and
|
# server admin: has full control of the ircd, including nickname and
|
||||||
# channel registrations
|
# channel registrations
|
||||||
|
@ -439,7 +439,7 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
|||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
clientData, isJoined := channel.members[client]
|
clientData, isJoined := channel.members[client]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
isOper := client.HasMode(modes.Operator)
|
isOper := client.HasRoleCapabs("sajoin")
|
||||||
respectAuditorium := channel.flags.HasMode(modes.Auditorium) && !isOper &&
|
respectAuditorium := channel.flags.HasMode(modes.Auditorium) && !isOper &&
|
||||||
(!isJoined || clientData.modes.HighestChannelUserMode() == modes.Mode(0))
|
(!isJoined || clientData.modes.HighestChannelUserMode() == modes.Mode(0))
|
||||||
isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
|
isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
|
||||||
@ -607,7 +607,7 @@ func (channel *Channel) hasClient(client *Client) bool {
|
|||||||
|
|
||||||
// <mode> <mode params>
|
// <mode> <mode params>
|
||||||
func (channel *Channel) modeStrings(client *Client) (result []string) {
|
func (channel *Channel) modeStrings(client *Client) (result []string) {
|
||||||
hasPrivs := client.HasMode(modes.Operator)
|
hasPrivs := client.HasRoleCapabs("sajoin")
|
||||||
|
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
@ -1245,12 +1245,12 @@ func (channel *Channel) SendTopic(client *Client, rb *ResponseBuffer, sendNoTopi
|
|||||||
|
|
||||||
// SetTopic sets the topic of this channel, if the client is allowed to do so.
|
// SetTopic sets the topic of this channel, if the client is allowed to do so.
|
||||||
func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffer) {
|
func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffer) {
|
||||||
if !(client.HasMode(modes.Operator) || channel.hasClient(client)) {
|
if !channel.hasClient(client) {
|
||||||
rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, client.Nick(), channel.Name(), client.t("You're not on that channel"))
|
rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, client.Nick(), channel.Name(), client.t("You're not on that channel"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel.flags.HasMode(modes.OpOnlyTopic) && !channel.ClientIsAtLeast(client, modes.Halfop) {
|
if channel.flags.HasMode(modes.OpOnlyTopic) && !(channel.ClientIsAtLeast(client, modes.Halfop) || client.HasRoleCapabs("samode")) {
|
||||||
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.Name(), client.t("You're not a channel operator"))
|
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.Name(), client.t("You're not a channel operator"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1487,10 +1487,6 @@ func (channel *Channel) Quit(client *Client) {
|
|||||||
|
|
||||||
func (channel *Channel) Kick(client *Client, target *Client, comment string, rb *ResponseBuffer, hasPrivs bool) {
|
func (channel *Channel) Kick(client *Client, target *Client, comment string, rb *ResponseBuffer, hasPrivs bool) {
|
||||||
if !hasPrivs {
|
if !hasPrivs {
|
||||||
if !(client.HasMode(modes.Operator) || channel.hasClient(client)) {
|
|
||||||
rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, client.Nick(), channel.Name(), client.t("You're not on that channel"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !channel.ClientHasPrivsOver(client, target) {
|
if !channel.ClientHasPrivsOver(client, target) {
|
||||||
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.Name(), client.t("You don't have enough channel privileges"))
|
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.Name(), client.t("You don't have enough channel privileges"))
|
||||||
return
|
return
|
||||||
|
@ -876,7 +876,7 @@ func csHowToBanHandler(service *ircService, server *Server, client *Client, comm
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(channel.ClientIsAtLeast(client, modes.Operator) || client.HasRoleCapabs("samode")) {
|
if !(channel.ClientIsAtLeast(client, modes.ChannelOperator) || client.HasRoleCapabs("samode")) {
|
||||||
service.Notice(rb, client.t("Insufficient privileges"))
|
service.Notice(rb, client.t("Insufficient privileges"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1512,7 +1512,7 @@ func (client *Client) destroy(session *Session) {
|
|||||||
// decrement stats if we have no more sessions, even if the client will not be destroyed
|
// decrement stats if we have no more sessions, even if the client will not be destroyed
|
||||||
if shouldDecrement {
|
if shouldDecrement {
|
||||||
invisible := client.HasMode(modes.Invisible)
|
invisible := client.HasMode(modes.Invisible)
|
||||||
operator := client.HasMode(modes.LocalOperator) || client.HasMode(modes.Operator)
|
operator := client.HasMode(modes.Operator)
|
||||||
client.server.stats.Remove(registered, invisible, operator)
|
client.server.stats.Remove(registered, invisible, operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
}
|
}
|
||||||
if numSessions == 1 {
|
if numSessions == 1 {
|
||||||
invisible := currentClient.HasMode(modes.Invisible)
|
invisible := currentClient.HasMode(modes.Invisible)
|
||||||
operator := currentClient.HasMode(modes.Operator) || currentClient.HasMode(modes.LocalOperator)
|
operator := currentClient.HasMode(modes.Operator)
|
||||||
client.server.stats.AddRegistered(invisible, operator)
|
client.server.stats.AddRegistered(invisible, operator)
|
||||||
}
|
}
|
||||||
session.autoreplayMissedSince = lastSeen
|
session.autoreplayMissedSince = lastSeen
|
||||||
|
@ -7,13 +7,11 @@ package irc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/goshuirc/irc-go/ircmsg"
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Command represents a command accepted from a client.
|
// Command represents a command accepted from a client.
|
||||||
type Command struct {
|
type Command struct {
|
||||||
handler func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
|
handler func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
|
||||||
oper bool
|
|
||||||
usablePreReg bool
|
usablePreReg bool
|
||||||
allowedInBatch bool // allowed in client-to-server batches
|
allowedInBatch bool // allowed in client-to-server batches
|
||||||
minParams int
|
minParams int
|
||||||
@ -32,10 +30,6 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
|
|||||||
rb.Add(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
|
rb.Add(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if cmd.oper && !client.HasMode(modes.Operator) {
|
|
||||||
rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied - You're not an IRC operator"))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(cmd.capabs) > 0 && !client.HasRoleCapabs(cmd.capabs...) {
|
if len(cmd.capabs) > 0 && !client.HasRoleCapabs(cmd.capabs...) {
|
||||||
rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied"))
|
rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied"))
|
||||||
return false
|
return false
|
||||||
@ -115,7 +109,7 @@ func init() {
|
|||||||
"DEBUG": {
|
"DEBUG": {
|
||||||
handler: debugHandler,
|
handler: debugHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
oper: true,
|
capabs: []string{"rehash"},
|
||||||
},
|
},
|
||||||
"DEFCON": {
|
"DEFCON": {
|
||||||
handler: defconHandler,
|
handler: defconHandler,
|
||||||
@ -124,12 +118,11 @@ func init() {
|
|||||||
"DEOPER": {
|
"DEOPER": {
|
||||||
handler: deoperHandler,
|
handler: deoperHandler,
|
||||||
minParams: 0,
|
minParams: 0,
|
||||||
oper: true,
|
|
||||||
},
|
},
|
||||||
"DLINE": {
|
"DLINE": {
|
||||||
handler: dlineHandler,
|
handler: dlineHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
oper: true,
|
capabs: []string{"ban"},
|
||||||
},
|
},
|
||||||
"EXTJWT": {
|
"EXTJWT": {
|
||||||
handler: extjwtHandler,
|
handler: extjwtHandler,
|
||||||
@ -169,13 +162,12 @@ func init() {
|
|||||||
"KILL": {
|
"KILL": {
|
||||||
handler: killHandler,
|
handler: killHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
oper: true,
|
|
||||||
capabs: []string{"kill"},
|
capabs: []string{"kill"},
|
||||||
},
|
},
|
||||||
"KLINE": {
|
"KLINE": {
|
||||||
handler: klineHandler,
|
handler: klineHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
oper: true,
|
capabs: []string{"ban"},
|
||||||
},
|
},
|
||||||
"LANGUAGE": {
|
"LANGUAGE": {
|
||||||
handler: languageHandler,
|
handler: languageHandler,
|
||||||
@ -278,7 +270,7 @@ func init() {
|
|||||||
"SANICK": {
|
"SANICK": {
|
||||||
handler: sanickHandler,
|
handler: sanickHandler,
|
||||||
minParams: 2,
|
minParams: 2,
|
||||||
oper: true,
|
capabs: []string{"samode"},
|
||||||
},
|
},
|
||||||
"SAMODE": {
|
"SAMODE": {
|
||||||
handler: modeHandler,
|
handler: modeHandler,
|
||||||
@ -308,7 +300,6 @@ func init() {
|
|||||||
"REHASH": {
|
"REHASH": {
|
||||||
handler: rehashHandler,
|
handler: rehashHandler,
|
||||||
minParams: 0,
|
minParams: 0,
|
||||||
oper: true,
|
|
||||||
capabs: []string{"rehash"},
|
capabs: []string{"rehash"},
|
||||||
},
|
},
|
||||||
"TIME": {
|
"TIME": {
|
||||||
@ -327,7 +318,7 @@ func init() {
|
|||||||
"UNDLINE": {
|
"UNDLINE": {
|
||||||
handler: unDLineHandler,
|
handler: unDLineHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
oper: true,
|
capabs: []string{"ban"},
|
||||||
},
|
},
|
||||||
"UNINVITE": {
|
"UNINVITE": {
|
||||||
handler: inviteHandler,
|
handler: inviteHandler,
|
||||||
@ -336,7 +327,7 @@ func init() {
|
|||||||
"UNKLINE": {
|
"UNKLINE": {
|
||||||
handler: unKLineHandler,
|
handler: unKLineHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
oper: true,
|
capabs: []string{"ban"},
|
||||||
},
|
},
|
||||||
"USER": {
|
"USER": {
|
||||||
handler: userHandler,
|
handler: userHandler,
|
||||||
|
@ -757,10 +757,6 @@ func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
rb.Notice(fmt.Sprintf("CPU profiling stopped"))
|
rb.Notice(fmt.Sprintf("CPU profiling stopped"))
|
||||||
|
|
||||||
case "CRASHSERVER":
|
case "CRASHSERVER":
|
||||||
if !client.HasRoleCapabs("rehash") {
|
|
||||||
rb.Notice(client.t("You must have rehash permissions in order to execute DEBUG CRASHSERVER"))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
code := utils.ConfirmationCode(server.name, server.ctime)
|
code := utils.ConfirmationCode(server.name, server.ctime)
|
||||||
if len(msg.Params) == 1 || msg.Params[1] != code {
|
if len(msg.Params) == 1 || msg.Params[1] != code {
|
||||||
rb.Notice(fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/DEBUG CRASHSERVER %s", code)))
|
rb.Notice(fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/DEBUG CRASHSERVER %s", code)))
|
||||||
@ -1293,6 +1289,7 @@ func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
|
|
||||||
// KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
|
// KICK <channel>{,<channel>} <user>{,<user>} [<comment>]
|
||||||
func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
hasPrivs := client.HasRoleCapabs("samode")
|
||||||
channels := strings.Split(msg.Params[0], ",")
|
channels := strings.Split(msg.Params[0], ",")
|
||||||
users := strings.Split(msg.Params[1], ",")
|
users := strings.Split(msg.Params[1], ",")
|
||||||
if (len(channels) != len(users)) && (len(users) != 1) {
|
if (len(channels) != len(users)) && (len(users) != 1) {
|
||||||
@ -1336,7 +1333,7 @@ func kickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
if comment == "" {
|
if comment == "" {
|
||||||
comment = kick.nick
|
comment = kick.nick
|
||||||
}
|
}
|
||||||
channel.Kick(client, target, comment, rb, false)
|
channel.Kick(client, target, comment, rb, hasPrivs)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1618,7 +1615,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
rb.Add(nil, client.server.name, RPL_LIST, nick, name, strconv.Itoa(members), topic)
|
rb.Add(nil, client.server.name, RPL_LIST, nick, name, strconv.Itoa(members), topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
clientIsOp := client.HasMode(modes.Operator)
|
clientIsOp := client.HasRoleCapabs("sajoin")
|
||||||
if len(channels) == 0 {
|
if len(channels) == 0 {
|
||||||
for _, channel := range server.channels.Channels() {
|
for _, channel := range server.channels.Channels() {
|
||||||
if !clientIsOp && channel.flags.HasMode(modes.Secret) {
|
if !clientIsOp && channel.flags.HasMode(modes.Secret) {
|
||||||
@ -1775,7 +1772,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
rb.Add(nil, cDetails.nickMask, "MODE", args...)
|
rb.Add(nil, cDetails.nickMask, "MODE", args...)
|
||||||
} else if hasPrivs {
|
} else if hasPrivs {
|
||||||
rb.Add(nil, server.name, RPL_UMODEIS, targetNick, target.ModeString())
|
rb.Add(nil, server.name, RPL_UMODEIS, targetNick, target.ModeString())
|
||||||
if target.HasMode(modes.LocalOperator) || target.HasMode(modes.Operator) {
|
if target.HasMode(modes.Operator) {
|
||||||
masks := server.snomasks.String(target)
|
masks := server.snomasks.String(target)
|
||||||
if 0 < len(masks) {
|
if 0 < len(masks) {
|
||||||
rb.Add(nil, server.name, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
|
rb.Add(nil, server.name, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
|
||||||
@ -1959,7 +1956,7 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
success := false
|
success := false
|
||||||
channel := server.channels.Get(chname)
|
channel := server.channels.Get(chname)
|
||||||
if channel != nil {
|
if channel != nil {
|
||||||
if !channel.flags.HasMode(modes.Secret) || channel.hasClient(client) || client.HasMode(modes.Operator) {
|
if !channel.flags.HasMode(modes.Secret) || channel.hasClient(client) || client.HasRoleCapabs("sajoin") {
|
||||||
channel.Names(client, rb)
|
channel.Names(client, rb)
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
@ -2338,6 +2335,10 @@ func applyOper(client *Client, oper *Oper, rb *ResponseBuffer) {
|
|||||||
|
|
||||||
// DEOPER
|
// DEOPER
|
||||||
func deoperHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func deoperHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
if client.Oper() == nil {
|
||||||
|
rb.Notice(client.t("Insufficient oper privs"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
// pretend they sent /MODE $nick -o
|
// pretend they sent /MODE $nick -o
|
||||||
fakeModeMsg := ircmsg.MakeMessage(nil, "", "MODE", client.Nick(), "-o")
|
fakeModeMsg := ircmsg.MakeMessage(nil, "", "MODE", client.Nick(), "-o")
|
||||||
return umodeHandler(server, client, fakeModeMsg, rb)
|
return umodeHandler(server, client, fakeModeMsg, rb)
|
||||||
@ -2944,7 +2945,7 @@ func operStatusVisible(client, target *Client, hasPrivs bool) bool {
|
|||||||
|
|
||||||
// USERHOST <nickname>{ <nickname>}
|
// USERHOST <nickname>{ <nickname>}
|
||||||
func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
hasPrivs := client.HasMode(modes.Operator) // TODO(#1176) figure out the right capab for this
|
hasPrivs := client.HasMode(modes.Operator)
|
||||||
returnedClients := make(ClientSet)
|
returnedClients := make(ClientSet)
|
||||||
|
|
||||||
var tl utils.TokenLineBuilder
|
var tl utils.TokenLineBuilder
|
||||||
@ -3083,7 +3084,7 @@ func (fields whoxFields) Has(field rune) bool {
|
|||||||
// <channel> <user> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] :<hopcount> <real name>
|
// <channel> <user> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] :<hopcount> <real name>
|
||||||
// whox format:
|
// whox format:
|
||||||
// <type> <channel> <user> <ip> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] <hops> <idle> <account> <rank> :<real name>
|
// <type> <channel> <user> <ip> <host> <server> <nick> <H|G>[*][~|&|@|%|+][B] <hops> <idle> <account> <rank> :<real name>
|
||||||
func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *ResponseBuffer, hasPrivs, includeRFlag, isWhox bool, fields whoxFields, whoType string) {
|
func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *ResponseBuffer, canSeeIPs, canSeeOpers, includeRFlag, isWhox bool, fields whoxFields, whoType string) {
|
||||||
params := []string{client.Nick()}
|
params := []string{client.Nick()}
|
||||||
|
|
||||||
details := target.Details()
|
details := target.Details()
|
||||||
@ -3103,7 +3104,7 @@ func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *Response
|
|||||||
}
|
}
|
||||||
if fields.Has('i') {
|
if fields.Has('i') {
|
||||||
fIP := "255.255.255.255"
|
fIP := "255.255.255.255"
|
||||||
if hasPrivs || client == target {
|
if canSeeIPs || client == target {
|
||||||
// you can only see a target's IP if they're you or you're an oper
|
// you can only see a target's IP if they're you or you're an oper
|
||||||
fIP = target.IPString()
|
fIP = target.IPString()
|
||||||
}
|
}
|
||||||
@ -3126,7 +3127,7 @@ func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *Response
|
|||||||
flags.WriteRune('H') // Here
|
flags.WriteRune('H') // Here
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.HasMode(modes.Operator) && operStatusVisible(client, target, hasPrivs) {
|
if target.HasMode(modes.Operator) && operStatusVisible(client, target, canSeeOpers) {
|
||||||
flags.WriteRune('*')
|
flags.WriteRune('*')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3229,23 +3230,23 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
|
|||||||
// operatorOnly = true
|
// operatorOnly = true
|
||||||
//}
|
//}
|
||||||
|
|
||||||
isOper := client.HasMode(modes.Operator)
|
oper := client.Oper()
|
||||||
|
hasPrivs := oper.HasRoleCapab("sajoin")
|
||||||
|
canSeeIPs := oper.HasRoleCapab("ban")
|
||||||
if mask[0] == '#' {
|
if mask[0] == '#' {
|
||||||
// TODO implement wildcard matching
|
|
||||||
//TODO(dan): ^ only for opers
|
|
||||||
channel := server.channels.Get(mask)
|
channel := server.channels.Get(mask)
|
||||||
if channel != nil {
|
if channel != nil {
|
||||||
isJoined := channel.hasClient(client)
|
isJoined := channel.hasClient(client)
|
||||||
if !channel.flags.HasMode(modes.Secret) || isJoined || isOper {
|
if !channel.flags.HasMode(modes.Secret) || isJoined || hasPrivs {
|
||||||
var members []*Client
|
var members []*Client
|
||||||
if isOper {
|
if hasPrivs {
|
||||||
members = channel.Members()
|
members = channel.Members()
|
||||||
} else {
|
} else {
|
||||||
members = channel.auditoriumFriends(client)
|
members = channel.auditoriumFriends(client)
|
||||||
}
|
}
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
if !member.HasMode(modes.Invisible) || isJoined || isOper {
|
if !member.HasMode(modes.Invisible) || isJoined || hasPrivs {
|
||||||
client.rplWhoReply(channel, member, rb, isOper, includeRFlag, isWhox, fields, whoType)
|
client.rplWhoReply(channel, member, rb, canSeeIPs, oper != nil, includeRFlag, isWhox, fields, whoType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3275,8 +3276,8 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for mclient := range server.clients.FindAll(mask) {
|
for mclient := range server.clients.FindAll(mask) {
|
||||||
if isOper || !mclient.HasMode(modes.Invisible) || isFriend(mclient) {
|
if hasPrivs || !mclient.HasMode(modes.Invisible) || isFriend(mclient) {
|
||||||
client.rplWhoReply(nil, mclient, rb, isOper, includeRFlag, isWhox, fields, whoType)
|
client.rplWhoReply(nil, mclient, rb, canSeeIPs, oper != nil, includeRFlag, isWhox, fields, whoType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3319,7 +3320,7 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPrivs := client.HasMode(modes.Operator) // TODO(#1176) figure out the right capab for this
|
hasPrivs := client.HasRoleCapabs("samode")
|
||||||
if hasPrivs {
|
if hasPrivs {
|
||||||
for _, mask := range strings.Split(masksString, ",") {
|
for _, mask := range strings.Split(masksString, ",") {
|
||||||
matches := server.clients.FindAll(mask)
|
matches := server.clients.FindAll(mask)
|
||||||
|
40
irc/modes.go
40
irc/modes.go
@ -37,14 +37,14 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
|
|||||||
if change.Mode != modes.ServerNotice {
|
if change.Mode != modes.ServerNotice {
|
||||||
switch change.Op {
|
switch change.Op {
|
||||||
case modes.Add:
|
case modes.Add:
|
||||||
if (change.Mode == modes.Operator || change.Mode == modes.LocalOperator) && !(force && oper != nil) {
|
if (change.Mode == modes.Operator) && !(force && oper != nil) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.SetMode(change.Mode, true) {
|
if client.SetMode(change.Mode, true) {
|
||||||
if change.Mode == modes.Invisible {
|
if change.Mode == modes.Invisible {
|
||||||
client.server.stats.ChangeInvisible(1)
|
client.server.stats.ChangeInvisible(1)
|
||||||
} else if change.Mode == modes.Operator || change.Mode == modes.LocalOperator {
|
} else if change.Mode == modes.Operator {
|
||||||
client.server.stats.ChangeOperators(1)
|
client.server.stats.ChangeOperators(1)
|
||||||
}
|
}
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
@ -55,7 +55,7 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
|
|||||||
if client.SetMode(change.Mode, false) {
|
if client.SetMode(change.Mode, false) {
|
||||||
if change.Mode == modes.Invisible {
|
if change.Mode == modes.Invisible {
|
||||||
client.server.stats.ChangeInvisible(-1)
|
client.server.stats.ChangeInvisible(-1)
|
||||||
} else if change.Mode == modes.Operator || change.Mode == modes.LocalOperator {
|
} else if change.Mode == modes.Operator {
|
||||||
removedSnomasks = client.server.snomasks.String(client)
|
removedSnomasks = client.server.snomasks.String(client)
|
||||||
client.server.stats.ChangeOperators(-1)
|
client.server.stats.ChangeOperators(-1)
|
||||||
applyOper(client, nil, nil)
|
applyOper(client, nil, nil)
|
||||||
@ -75,26 +75,28 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// server notices are weird
|
// server notices are weird
|
||||||
if !client.HasMode(modes.Operator) {
|
if !client.HasMode(modes.Operator) || change.Op == modes.List {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var masks []sno.Mask
|
|
||||||
if change.Op == modes.Add || change.Op == modes.Remove {
|
currentMasks := client.server.snomasks.MasksEnabled(client)
|
||||||
var newArg string
|
addMasks, removeMasks, newArg := sno.EvaluateSnomaskChanges(change.Op == modes.Add, change.Arg, currentMasks)
|
||||||
for _, char := range change.Arg {
|
|
||||||
mask := sno.Mask(char)
|
success := false
|
||||||
if sno.ValidMasks[mask] {
|
if len(addMasks) != 0 {
|
||||||
masks = append(masks, mask)
|
oper := client.Oper()
|
||||||
newArg += string(char)
|
// #1176: require special operator privileges to subscribe to snomasks
|
||||||
}
|
if oper.HasRoleCapab("snomasks") || oper.HasRoleCapab("ban") {
|
||||||
|
success = true
|
||||||
|
client.server.snomasks.AddMasks(client, addMasks...)
|
||||||
}
|
}
|
||||||
change.Arg = newArg
|
|
||||||
}
|
}
|
||||||
if change.Op == modes.Add {
|
if len(removeMasks) != 0 {
|
||||||
client.server.snomasks.AddMasks(client, masks...)
|
success = true
|
||||||
applied = append(applied, change)
|
client.server.snomasks.RemoveMasks(client, removeMasks...)
|
||||||
} else if change.Op == modes.Remove {
|
}
|
||||||
client.server.snomasks.RemoveMasks(client, masks...)
|
if success {
|
||||||
|
change.Arg = newArg
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,6 @@ func (modes Modes) String() string {
|
|||||||
const (
|
const (
|
||||||
Bot Mode = 'B'
|
Bot Mode = 'B'
|
||||||
Invisible Mode = 'i'
|
Invisible Mode = 'i'
|
||||||
LocalOperator Mode = 'O'
|
|
||||||
Operator Mode = 'o'
|
Operator Mode = 'o'
|
||||||
Restricted Mode = 'r'
|
Restricted Mode = 'r'
|
||||||
RegisteredOnly Mode = 'R'
|
RegisteredOnly Mode = 'R'
|
||||||
@ -213,12 +212,10 @@ func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
|||||||
// put arg into modechange if needed
|
// put arg into modechange if needed
|
||||||
switch Mode(mode) {
|
switch Mode(mode) {
|
||||||
case ServerNotice:
|
case ServerNotice:
|
||||||
// always require arg
|
// arg is optional for ServerNotice (we accept bare `-s`)
|
||||||
if len(params) > skipArgs {
|
if len(params) > skipArgs {
|
||||||
change.Arg = params[skipArgs]
|
change.Arg = params[skipArgs]
|
||||||
skipArgs++
|
skipArgs++
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,38 @@ func assertEqual(supplied, expected interface{}, t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseUserModeChanges(t *testing.T) {
|
||||||
|
emptyUnknown := make(map[rune]bool)
|
||||||
|
changes, unknown := ParseUserModeChanges("+i")
|
||||||
|
assertEqual(unknown, emptyUnknown, t)
|
||||||
|
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}}, t)
|
||||||
|
|
||||||
|
// no-op change to sno
|
||||||
|
changes, unknown = ParseUserModeChanges("+is")
|
||||||
|
assertEqual(unknown, emptyUnknown, t)
|
||||||
|
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}, ModeChange{Op: Add, Mode: ServerNotice}}, t)
|
||||||
|
|
||||||
|
// add snomasks
|
||||||
|
changes, unknown = ParseUserModeChanges("+is", "ac")
|
||||||
|
assertEqual(unknown, emptyUnknown, t)
|
||||||
|
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}, ModeChange{Op: Add, Mode: ServerNotice, Arg: "ac"}}, t)
|
||||||
|
|
||||||
|
// remove snomasks
|
||||||
|
changes, unknown = ParseUserModeChanges("+s", "-cx")
|
||||||
|
assertEqual(unknown, emptyUnknown, t)
|
||||||
|
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: ServerNotice, Arg: "-cx"}}, t)
|
||||||
|
|
||||||
|
// remove all snomasks (arg is parsed but has no meaning)
|
||||||
|
changes, unknown = ParseUserModeChanges("-is", "ac")
|
||||||
|
assertEqual(unknown, emptyUnknown, t)
|
||||||
|
assertEqual(changes, ModeChanges{ModeChange{Op: Remove, Mode: Invisible}, ModeChange{Op: Remove, Mode: ServerNotice, Arg: "ac"}}, t)
|
||||||
|
|
||||||
|
// remove all snomasks
|
||||||
|
changes, unknown = ParseUserModeChanges("-is")
|
||||||
|
assertEqual(unknown, emptyUnknown, t)
|
||||||
|
assertEqual(changes, ModeChanges{ModeChange{Op: Remove, Mode: Invisible}, ModeChange{Op: Remove, Mode: ServerNotice}}, t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssue874(t *testing.T) {
|
func TestIssue874(t *testing.T) {
|
||||||
emptyUnknown := make(map[rune]bool)
|
emptyUnknown := make(map[rune]bool)
|
||||||
modes, unknown := ParseChannelModeChanges("+k")
|
modes, unknown := ParseChannelModeChanges("+k")
|
||||||
|
@ -459,15 +459,13 @@ func (server *Server) MOTD(client *Client, rb *ResponseBuffer) {
|
|||||||
rb.Add(nil, server.name, RPL_ENDOFMOTD, client.nick, client.t("End of MOTD command"))
|
rb.Add(nil, server.name, RPL_ENDOFMOTD, client.nick, client.t("End of MOTD command"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhoisChannelsNames returns the common channel names between two users.
|
func (client *Client) whoisChannelsNames(target *Client, multiPrefix bool, hasPrivs bool) []string {
|
||||||
func (client *Client) WhoisChannelsNames(target *Client, multiPrefix bool) []string {
|
|
||||||
var chstrs []string
|
var chstrs []string
|
||||||
|
targetInvis := target.HasMode(modes.Invisible)
|
||||||
for _, channel := range target.Channels() {
|
for _, channel := range target.Channels() {
|
||||||
// channel is secret and the target can't see it
|
if !hasPrivs && (targetInvis || channel.flags.HasMode(modes.Secret)) && !channel.hasClient(client) {
|
||||||
if !client.HasMode(modes.Operator) {
|
// client can't see *this* channel membership
|
||||||
if (target.HasMode(modes.Invisible) || channel.flags.HasMode(modes.Secret)) && !channel.hasClient(client) {
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
chstrs = append(chstrs, channel.ClientPrefixes(target, multiPrefix)+channel.name)
|
chstrs = append(chstrs, channel.ClientPrefixes(target, multiPrefix)+channel.name)
|
||||||
}
|
}
|
||||||
@ -475,23 +473,26 @@ func (client *Client) WhoisChannelsNames(target *Client, multiPrefix bool) []str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuffer) {
|
func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuffer) {
|
||||||
|
oper := client.Oper()
|
||||||
cnick := client.Nick()
|
cnick := client.Nick()
|
||||||
targetInfo := target.Details()
|
targetInfo := target.Details()
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISUSER, cnick, targetInfo.nick, targetInfo.username, targetInfo.hostname, "*", targetInfo.realname)
|
rb.Add(nil, client.server.name, RPL_WHOISUSER, cnick, targetInfo.nick, targetInfo.username, targetInfo.hostname, "*", targetInfo.realname)
|
||||||
tnick := targetInfo.nick
|
tnick := targetInfo.nick
|
||||||
|
|
||||||
whoischannels := client.WhoisChannelsNames(target, rb.session.capabilities.Has(caps.MultiPrefix))
|
whoischannels := client.whoisChannelsNames(target, rb.session.capabilities.Has(caps.MultiPrefix), oper.HasRoleCapab("sajoin"))
|
||||||
if whoischannels != nil {
|
if whoischannels != nil {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, cnick, tnick, strings.Join(whoischannels, " "))
|
rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, cnick, tnick, strings.Join(whoischannels, " "))
|
||||||
}
|
}
|
||||||
if target.HasMode(modes.Operator) && operStatusVisible(client, target, hasPrivs) {
|
if target.HasMode(modes.Operator) && operStatusVisible(client, target, oper != nil) {
|
||||||
tOper := target.Oper()
|
tOper := target.Oper()
|
||||||
if tOper != nil {
|
if tOper != nil {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, cnick, tnick, tOper.WhoisLine)
|
rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, cnick, tnick, tOper.WhoisLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if client == target || hasPrivs {
|
if client == target || oper.HasRoleCapab("ban") {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, target.RawHostname()), target.IPString(), client.t("Actual user@host, Actual IP"))
|
rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, target.RawHostname()), target.IPString(), client.t("Actual user@host, Actual IP"))
|
||||||
|
}
|
||||||
|
if client == target || oper.HasRoleCapab("samode") {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISMODES, cnick, tnick, fmt.Sprintf(client.t("is using modes +%s"), target.modes.String()))
|
rb.Add(nil, client.server.name, RPL_WHOISMODES, cnick, tnick, fmt.Sprintf(client.t("is using modes +%s"), target.modes.String()))
|
||||||
}
|
}
|
||||||
if target.HasMode(modes.TLS) {
|
if target.HasMode(modes.TLS) {
|
||||||
@ -504,7 +505,7 @@ func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuff
|
|||||||
rb.Add(nil, client.server.name, RPL_WHOISBOT, cnick, tnick, fmt.Sprintf(ircfmt.Unescape(client.t("is a $bBot$b on %s")), client.server.Config().Network.Name))
|
rb.Add(nil, client.server.name, RPL_WHOISBOT, cnick, tnick, fmt.Sprintf(ircfmt.Unescape(client.t("is a $bBot$b on %s")), client.server.Config().Network.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if client == target || hasPrivs {
|
if client == target || oper.HasRoleCapab("ban") {
|
||||||
for _, session := range target.Sessions() {
|
for _, session := range target.Sessions() {
|
||||||
if session.certfp != "" {
|
if session.certfp != "" {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISCERTFP, cnick, tnick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), session.certfp))
|
rb.Add(nil, client.server.name, RPL_WHOISCERTFP, cnick, tnick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), session.certfp))
|
||||||
|
@ -7,6 +7,8 @@ package sno
|
|||||||
// Mask is a type of server notice mask.
|
// Mask is a type of server notice mask.
|
||||||
type Mask rune
|
type Mask rune
|
||||||
|
|
||||||
|
type Masks []Mask
|
||||||
|
|
||||||
// Notice mask types
|
// Notice mask types
|
||||||
const (
|
const (
|
||||||
LocalAnnouncements Mask = 'a'
|
LocalAnnouncements Mask = 'a'
|
||||||
@ -18,8 +20,8 @@ const (
|
|||||||
LocalQuits Mask = 'q'
|
LocalQuits Mask = 'q'
|
||||||
Stats Mask = 't'
|
Stats Mask = 't'
|
||||||
LocalAccounts Mask = 'u'
|
LocalAccounts Mask = 'u'
|
||||||
LocalXline Mask = 'x'
|
|
||||||
LocalVhosts Mask = 'v'
|
LocalVhosts Mask = 'v'
|
||||||
|
LocalXline Mask = 'x'
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -39,17 +41,17 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidMasks contains the snomasks that we support.
|
// ValidMasks contains the snomasks that we support.
|
||||||
ValidMasks = map[Mask]bool{
|
ValidMasks = []Mask{
|
||||||
LocalAnnouncements: true,
|
LocalAnnouncements,
|
||||||
LocalConnects: true,
|
LocalConnects,
|
||||||
LocalChannels: true,
|
LocalChannels,
|
||||||
LocalKills: true,
|
LocalKills,
|
||||||
LocalNicks: true,
|
LocalNicks,
|
||||||
LocalOpers: true,
|
LocalOpers,
|
||||||
LocalQuits: true,
|
LocalQuits,
|
||||||
Stats: true,
|
Stats,
|
||||||
LocalAccounts: true,
|
LocalAccounts,
|
||||||
LocalXline: true,
|
LocalVhosts,
|
||||||
LocalVhosts: true,
|
LocalXline,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
87
irc/sno/utils.go
Normal file
87
irc/sno/utils.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) 2020 Shivaram Lingamneni
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package sno
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsValidMask(r rune) bool {
|
||||||
|
for _, m := range ValidMasks {
|
||||||
|
if m == Mask(r) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (masks Masks) String() string {
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.Grow(len(masks))
|
||||||
|
for _, m := range masks {
|
||||||
|
buf.WriteRune(rune(m))
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (masks Masks) Contains(mask Mask) bool {
|
||||||
|
for _, m := range masks {
|
||||||
|
if mask == m {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate changes to snomasks made with MODE. There are several cases:
|
||||||
|
// adding snomasks with `/mode +s a` or `/mode +s +a`, removing them with `/mode +s -a`,
|
||||||
|
// adding all with `/mode +s *` or `/mode +s +*`, removing all with `/mode +s -*` or `/mode -s`
|
||||||
|
func EvaluateSnomaskChanges(add bool, arg string, currentMasks Masks) (addMasks, removeMasks Masks, newArg string) {
|
||||||
|
if add {
|
||||||
|
if len(arg) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
add := true
|
||||||
|
switch arg[0] {
|
||||||
|
case '+':
|
||||||
|
arg = arg[1:]
|
||||||
|
case '-':
|
||||||
|
add = false
|
||||||
|
arg = arg[1:]
|
||||||
|
default:
|
||||||
|
// add
|
||||||
|
}
|
||||||
|
if strings.IndexByte(arg, '*') != -1 {
|
||||||
|
if add {
|
||||||
|
for _, mask := range ValidMasks {
|
||||||
|
if !currentMasks.Contains(mask) {
|
||||||
|
addMasks = append(addMasks, mask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removeMasks = currentMasks
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, r := range arg {
|
||||||
|
if IsValidMask(r) {
|
||||||
|
m := Mask(r)
|
||||||
|
if add && !currentMasks.Contains(m) {
|
||||||
|
addMasks = append(addMasks, m)
|
||||||
|
} else if !add && currentMasks.Contains(m) {
|
||||||
|
removeMasks = append(removeMasks, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(addMasks) != 0 {
|
||||||
|
newArg = "+" + addMasks.String()
|
||||||
|
} else if len(removeMasks) != 0 {
|
||||||
|
newArg = "-" + removeMasks.String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removeMasks = currentMasks
|
||||||
|
newArg = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
53
irc/sno/utils_test.go
Normal file
53
irc/sno/utils_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2020 Shivaram Lingamneni
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package sno
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertEqual(supplied, expected interface{}, t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(supplied, expected) {
|
||||||
|
panic(fmt.Sprintf("expected %#v but got %#v", expected, supplied))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvaluateSnomaskChanges(t *testing.T) {
|
||||||
|
add, remove, newArg := EvaluateSnomaskChanges(true, "*", nil)
|
||||||
|
assertEqual(add, Masks{'a', 'c', 'j', 'k', 'n', 'o', 'q', 't', 'u', 'v', 'x'}, t)
|
||||||
|
assertEqual(len(remove), 0, t)
|
||||||
|
assertEqual(newArg, "+acjknoqtuvx", t)
|
||||||
|
|
||||||
|
add, remove, newArg = EvaluateSnomaskChanges(true, "*", Masks{'a', 'u'})
|
||||||
|
assertEqual(add, Masks{'c', 'j', 'k', 'n', 'o', 'q', 't', 'v', 'x'}, t)
|
||||||
|
assertEqual(len(remove), 0, t)
|
||||||
|
assertEqual(newArg, "+cjknoqtvx", t)
|
||||||
|
|
||||||
|
add, remove, newArg = EvaluateSnomaskChanges(true, "-a", Masks{'a', 'u'})
|
||||||
|
assertEqual(len(add), 0, t)
|
||||||
|
assertEqual(remove, Masks{'a'}, t)
|
||||||
|
assertEqual(newArg, "-a", t)
|
||||||
|
|
||||||
|
add, remove, newArg = EvaluateSnomaskChanges(true, "-*", Masks{'a', 'u'})
|
||||||
|
assertEqual(len(add), 0, t)
|
||||||
|
assertEqual(remove, Masks{'a', 'u'}, t)
|
||||||
|
assertEqual(newArg, "-au", t)
|
||||||
|
|
||||||
|
add, remove, newArg = EvaluateSnomaskChanges(true, "+c", Masks{'a', 'u'})
|
||||||
|
assertEqual(add, Masks{'c'}, t)
|
||||||
|
assertEqual(len(remove), 0, t)
|
||||||
|
assertEqual(newArg, "+c", t)
|
||||||
|
|
||||||
|
add, remove, newArg = EvaluateSnomaskChanges(false, "", Masks{'a', 'u'})
|
||||||
|
assertEqual(len(add), 0, t)
|
||||||
|
assertEqual(remove, Masks{'a', 'u'}, t)
|
||||||
|
assertEqual(newArg, "", t)
|
||||||
|
|
||||||
|
add, remove, newArg = EvaluateSnomaskChanges(false, "*", Masks{'a', 'u'})
|
||||||
|
assertEqual(len(add), 0, t)
|
||||||
|
assertEqual(remove, Masks{'a', 'u'}, t)
|
||||||
|
assertEqual(newArg, "", t)
|
||||||
|
}
|
@ -24,11 +24,6 @@ func (m *SnoManager) AddMasks(client *Client, masks ...sno.Mask) {
|
|||||||
defer m.sendListMutex.Unlock()
|
defer m.sendListMutex.Unlock()
|
||||||
|
|
||||||
for _, mask := range masks {
|
for _, mask := range masks {
|
||||||
// confirm mask is valid
|
|
||||||
if !sno.ValidMasks[mask] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentClientList := m.sendLists[mask]
|
currentClientList := m.sendLists[mask]
|
||||||
|
|
||||||
if currentClientList == nil {
|
if currentClientList == nil {
|
||||||
@ -101,19 +96,23 @@ func (m *SnoManager) Send(mask sno.Mask, content string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the snomasks currently enabled.
|
// MasksEnabled returns the snomasks currently enabled.
|
||||||
func (m *SnoManager) String(client *Client) string {
|
func (m *SnoManager) MasksEnabled(client *Client) (result sno.Masks) {
|
||||||
m.sendListMutex.RLock()
|
m.sendListMutex.RLock()
|
||||||
defer m.sendListMutex.RUnlock()
|
defer m.sendListMutex.RUnlock()
|
||||||
|
|
||||||
var masks string
|
|
||||||
for mask, clients := range m.sendLists {
|
for mask, clients := range m.sendLists {
|
||||||
for c := range clients {
|
for c := range clients {
|
||||||
if c == client {
|
if c == client {
|
||||||
masks += string(mask)
|
result = append(result, mask)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return masks
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnoManager) String(client *Client) string {
|
||||||
|
masks := m.MasksEnabled(client)
|
||||||
|
return masks.String()
|
||||||
}
|
}
|
||||||
|
@ -565,6 +565,7 @@ oper-classes:
|
|||||||
- "vhosts"
|
- "vhosts"
|
||||||
- "sajoin"
|
- "sajoin"
|
||||||
- "samode"
|
- "samode"
|
||||||
|
- "snomasks"
|
||||||
|
|
||||||
# server admin: has full control of the ircd, including nickname and
|
# server admin: has full control of the ircd, including nickname and
|
||||||
# channel registrations
|
# channel registrations
|
||||||
|
Loading…
Reference in New Issue
Block a user