From a9f89dbc645aafc68daa9fc8d589f55104b535c7 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 30 Dec 2020 09:21:32 -0800 Subject: [PATCH] Add support for stateless bridging via draft/relaymsg (irc) (#1339) * irc: add support for stateless bridging via draft/relaymsg As discussed at https://github.com/42wim/matterbridge/issues/667#issuecomment-634214165 * irc: handle the draft/relaymsg tag in spoofed messages too * Apply suggestions from code review Co-authored-by: Wim * Run gofmt on irc.go * Document relaymsg in matterbridge.toml.sample Co-authored-by: Wim --- bridge/irc/irc.go | 73 +++++++++++++++++++++++++++++++--------- matterbridge.toml.sample | 13 +++++++ 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 2352fcd7..4ccdee95 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -205,27 +205,58 @@ func (b *Birc) doConnect() { } } +// Sanitize nicks for RELAYMSG: replace IRC characters with special meanings with "-" +func sanitizeNick(nick string) string { + sanitize := func(r rune) rune { + if strings.ContainsRune("!+%@&#$:'\"?*,. ", r) { + return '-' + } + return r + } + return strings.Map(sanitize, nick) +} + func (b *Birc) doSend() { rate := time.Millisecond * time.Duration(b.MessageDelay) throttle := time.NewTicker(rate) for msg := range b.Local { <-throttle.C username := msg.Username - if b.GetBool("Colornicks") && len(username) > 1 { - checksum := crc32.ChecksumIEEE([]byte(msg.Username)) - colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes - username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username) - } + // Optional support for the proposed RELAYMSG extension, described at + // https://github.com/jlu5/ircv3-specifications/blob/master/extensions/relaymsg.md + // nolint:nestif + if (b.i.HasCapability("overdrivenetworks.com/relaymsg") || b.i.HasCapability("draft/relaymsg")) && + b.GetBool("UseRelayMsg") { + username = sanitizeNick(username) + text := msg.Text - switch msg.Event { - case config.EventUserAction: - b.i.Cmd.Action(msg.Channel, username+msg.Text) - case config.EventNoticeIRC: - b.Log.Debugf("Sending notice to channel %s", msg.Channel) - b.i.Cmd.Notice(msg.Channel, username+msg.Text) - default: - b.Log.Debugf("Sending to channel %s", msg.Channel) - b.i.Cmd.Message(msg.Channel, username+msg.Text) + // Work around girc chomping leading commas on single word messages? + if strings.HasPrefix(text, ":") && !strings.ContainsRune(text, ' ') { + text = ":" + text + } + + if msg.Event == config.EventUserAction { + b.i.Cmd.SendRawf("RELAYMSG %s %s :\x01ACTION %s\x01", msg.Channel, username, text) //nolint:errcheck + } else { + b.Log.Debugf("Sending RELAYMSG to channel %s: nick=%s", msg.Channel, username) + b.i.Cmd.SendRawf("RELAYMSG %s %s :%s", msg.Channel, username, text) //nolint:errcheck + } + } else { + if b.GetBool("Colornicks") { + checksum := crc32.ChecksumIEEE([]byte(msg.Username)) + colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes + username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username) + } + switch msg.Event { + case config.EventUserAction: + b.i.Cmd.Action(msg.Channel, username+msg.Text) + case config.EventNoticeIRC: + b.Log.Debugf("Sending notice to channel %s", msg.Channel) + b.i.Cmd.Notice(msg.Channel, username+msg.Text) + default: + b.Log.Debugf("Sending to channel %s", msg.Channel) + b.i.Cmd.Message(msg.Channel, username+msg.Text) + } } } } @@ -273,8 +304,9 @@ func (b *Birc) getClient() (*girc.Client, error) { TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, //nolint:gosec PingDelay: pingDelay, // skip gIRC internal rate limiting, since we have our own throttling - AllowFlood: true, - Debug: debug, + AllowFlood: true, + Debug: debug, + SupportedCaps: map[string][]string{"overdrivenetworks.com/relaymsg": nil, "draft/relaymsg": nil}, }) return i, nil } @@ -311,6 +343,15 @@ func (b *Birc) skipPrivMsg(event girc.Event) bool { if event.Source.Name == b.Nick { return true } + // don't forward messages we sent via RELAYMSG + if relayedNick, ok := event.Tags.Get("draft/relaymsg"); ok && relayedNick == b.Nick { + return true + } + // This is the old name of the cap sent in spoofed messages; I've kept this in + // for compatibility reasons + if relayedNick, ok := event.Tags.Get("relaymsg"); ok && relayedNick == b.Nick { + return true + } return false } diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index 9749080f..a92892b0 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -193,6 +193,19 @@ ShowTopicChange=false #OPTIONAL (default 0) JoinDelay=0 +#Use the optional RELAYMSG extension for username spoofing on IRC. +#This requires an IRCd that supports the draft/relaymsg specification: currently this includes +#Oragono 2.4.0+ and InspIRCd 3 with the m_relaymsg contrib module. +#See https://github.com/42wim/matterbridge/issues/667#issuecomment-634214165 for more details. +#Spoofed nicks will use the configured RemoteNickFormat, replacing reserved IRC characters +#(!+%@&#$:'"?*,.) with a hyphen (-). +#On most configurations, the RemoteNickFormat must include a separator character such as "/". +#You should make sure that the settings here match your IRCd. +#This option overrides ColorNicks. +#OPTIONAL (default false) +UseRelayMsg=false +#RemoteNickFormat="{NICK}/{PROTOCOL}" + ################################################################### #XMPP section ###################################################################