Allow admins to broadcast message to all bouncer users

Typically done via:

    /notice $<bouncer> <message>

Or, for a connection not bound to a specific network:

    /notice $* <message>

The message is broadcast as BouncerServ, because that's the only
user that can be trusted to belong to the bouncer by users. Any
other prefix would conflict with the upstream network.
This commit is contained in:
Simon Ser 2021-06-23 19:21:18 +02:00
parent eca4c41223
commit d7b1c5a9a2
4 changed files with 49 additions and 0 deletions

View File

@ -1913,6 +1913,33 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
tags := copyClientTags(msg.Tags) tags := copyClientTags(msg.Tags)
for _, name := range strings.Split(targetsStr, ",") { for _, name := range strings.Split(targetsStr, ",") {
if name == "$"+dc.srv.Hostname || (name == "$*" && dc.network == nil) {
// "$" means a server mask follows. If it's the bouncer's
// hostname, broadcast the message to all bouncer users.
if !dc.user.Admin {
return ircError{&irc.Message{
Prefix: dc.srv.prefix(),
Command: irc.ERR_BADMASK,
Params: []string{dc.nick, name, "Permission denied to broadcast message to all bouncer users"},
}}
}
dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
broadcastTags := tags.Copy()
broadcastTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
broadcastMsg := &irc.Message{
Tags: broadcastTags,
Prefix: servicePrefix,
Command: msg.Command,
Params: []string{name, text},
}
dc.srv.forEachUser(func(u *user) {
u.events <- eventBroadcast{broadcastMsg}
})
continue
}
if dc.network == nil && casemapASCII(name) == dc.nickCM { if dc.network == nil && casemapASCII(name) == dc.nickCM {
dc.SendMessage(msg) dc.SendMessage(msg)
continue continue

View File

@ -124,6 +124,14 @@ func (s *Server) createUser(user *User) (*user, error) {
return s.addUserLocked(user), nil return s.addUserLocked(user), nil
} }
func (s *Server) forEachUser(f func(*user)) {
s.lock.Lock()
for _, u := range s.users {
f(u)
}
s.lock.Unlock()
}
func (s *Server) getUser(name string) *user { func (s *Server) getUser(name string) *user {
s.lock.Lock() s.lock.Lock()
u := s.users[name] u := s.users[name]

View File

@ -1768,6 +1768,11 @@ func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string
return "" return ""
} }
// Don't store messages with a server mask target
if strings.HasPrefix(entity, "$") {
return ""
}
entityCM := uc.network.casemap(entity) entityCM := uc.network.casemap(entity)
if entityCM == "nickserv" { if entityCM == "nickserv" {
// The messages sent/received from NickServ may contain // The messages sent/received from NickServ may contain

View File

@ -53,6 +53,10 @@ type eventChannelDetach struct {
name string name string
} }
type eventBroadcast struct {
msg *irc.Message
}
type eventStop struct{} type eventStop struct{}
type deliveredClientMap map[string]string // client name -> msg ID type deliveredClientMap map[string]string // client name -> msg ID
@ -633,6 +637,11 @@ func (u *user) run() {
dc.logger.Printf("failed to handle message %q: %v", msg, err) dc.logger.Printf("failed to handle message %q: %v", msg, err)
dc.Close() dc.Close()
} }
case eventBroadcast:
msg := e.msg
u.forEachDownstream(func(dc *downstreamConn) {
dc.SendMessage(msg)
})
case eventStop: case eventStop:
u.forEachDownstream(func(dc *downstreamConn) { u.forEachDownstream(func(dc *downstreamConn) {
dc.Close() dc.Close()