From d7b1c5a9a20e15eadb8151b67fdfcd59a522238a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 23 Jun 2021 19:21:18 +0200 Subject: [PATCH] Allow admins to broadcast message to all bouncer users Typically done via: /notice $ Or, for a connection not bound to a specific network: /notice $* 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. --- downstream.go | 27 +++++++++++++++++++++++++++ server.go | 8 ++++++++ upstream.go | 5 +++++ user.go | 9 +++++++++ 4 files changed, 49 insertions(+) diff --git a/downstream.go b/downstream.go index 238f4eb..8de1152 100644 --- a/downstream.go +++ b/downstream.go @@ -1913,6 +1913,33 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { tags := copyClientTags(msg.Tags) 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 { dc.SendMessage(msg) continue diff --git a/server.go b/server.go index be91152..db08a73 100644 --- a/server.go +++ b/server.go @@ -124,6 +124,14 @@ func (s *Server) createUser(user *User) (*user, error) { 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 { s.lock.Lock() u := s.users[name] diff --git a/upstream.go b/upstream.go index 6c46814..e033410 100644 --- a/upstream.go +++ b/upstream.go @@ -1768,6 +1768,11 @@ func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string return "" } + // Don't store messages with a server mask target + if strings.HasPrefix(entity, "$") { + return "" + } + entityCM := uc.network.casemap(entity) if entityCM == "nickserv" { // The messages sent/received from NickServ may contain diff --git a/user.go b/user.go index adb315d..1b5f4f2 100644 --- a/user.go +++ b/user.go @@ -53,6 +53,10 @@ type eventChannelDetach struct { name string } +type eventBroadcast struct { + msg *irc.Message +} + type eventStop struct{} 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.Close() } + case eventBroadcast: + msg := e.msg + u.forEachDownstream(func(dc *downstreamConn) { + dc.SendMessage(msg) + }) case eventStop: u.forEachDownstream(func(dc *downstreamConn) { dc.Close()