From 47c514a9cfeb496b01dddfde4a240499b42fc786 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 25 May 2021 20:24:45 +0200 Subject: [PATCH] Add support for IRCv3 setname References: https://todo.sr.ht/~emersion/soju/41 --- downstream.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++- upstream.go | 25 ++++++++++++++++++ user.go | 1 + 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/downstream.go b/downstream.go index c59a56f..86a3e16 100644 --- a/downstream.go +++ b/downstream.go @@ -122,6 +122,7 @@ var permanentDownstreamCaps = map[string]string{ "message-tags": "", "sasl": "PLAIN", "server-time": "", + "setname": "", "soju.im/bouncer-networks": "", "soju.im/bouncer-networks-notify": "", @@ -154,6 +155,7 @@ var passthroughIsupport = map[string]bool{ "MAXLIST": true, "MAXTARGETS": true, "MODES": true, + "NAMELEN": true, "NETWORK": true, "NICKLEN": true, "PREFIX": true, @@ -380,6 +382,9 @@ func (dc *downstreamConn) SendMessage(msg *irc.Message) { if msg.Command == "JOIN" && !dc.caps["extended-join"] { msg.Params = msg.Params[:1] } + if msg.Command == "SETNAME" && !dc.caps["setname"] { + return + } dc.conn.SendMessage(msg) } @@ -459,7 +464,7 @@ func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Me msg.Params[1] = dc.marshalEntity(net, msg.Params[1]) case "TOPIC": msg.Params[0] = dc.marshalEntity(net, msg.Params[0]) - case "QUIT": + case "QUIT", "SETNAME": // This space is intentionally left blank default: panic(fmt.Sprintf("unexpected %q message", msg.Command)) @@ -869,6 +874,17 @@ func (dc *downstreamConn) updateNick() { } } +func (dc *downstreamConn) updateRealname() { + if uc := dc.upstream(); uc != nil && uc.realname != dc.realname && dc.caps["setname"] { + dc.SendMessage(&irc.Message{ + Prefix: dc.prefix(), + Command: "SETNAME", + Params: []string{uc.realname}, + }) + dc.realname = uc.realname + } +} + func sanityCheckServer(addr string) error { dialer := net.Dialer{Timeout: 30 * time.Second} conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil) @@ -1062,6 +1078,7 @@ func (dc *downstreamConn) welcome() error { }) dc.updateNick() + dc.updateRealname() dc.updateSupportedCaps() if dc.caps["soju.im/bouncer-networks-notify"] { @@ -1343,6 +1360,58 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { dc.nick = nick dc.nickCM = casemapASCII(dc.nick) } + case "SETNAME": + var realname string + if err := parseMessageParams(msg, &realname); err != nil { + return err + } + + var storeErr error + var needUpdate []Network + dc.forEachNetwork(func(n *network) { + // We only need to call updateNetwork for upstreams that don't + // support setname + if uc := n.conn; uc != nil && uc.caps["setname"] { + uc.SendMessageLabeled(dc.id, &irc.Message{ + Command: "SETNAME", + Params: []string{realname}, + }) + + n.Realname = realname + if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil { + dc.logger.Printf("failed to store network realname: %v", err) + storeErr = err + } + return + } + + record := n.Network // copy network record because we'll mutate it + record.Realname = realname + needUpdate = append(needUpdate, record) + }) + + // Walk the network list as a second step, because updateNetwork + // mutates the original list + for _, record := range needUpdate { + if _, err := dc.user.updateNetwork(&record); err != nil { + dc.logger.Printf("failed to update network realname: %v", err) + storeErr = err + } + } + if storeErr != nil { + return ircError{&irc.Message{ + Command: "FAIL", + Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"}, + }} + } + + if dc.upstream() == nil && dc.caps["setname"] { + dc.SendMessage(&irc.Message{ + Prefix: dc.prefix(), + Command: "SETNAME", + Params: []string{realname}, + }) + } case "JOIN": var namesStr string if err := parseMessageParams(msg, &namesStr); err != nil { diff --git a/upstream.go b/upstream.go index c476cdb..c812ef1 100644 --- a/upstream.go +++ b/upstream.go @@ -29,6 +29,7 @@ var permanentUpstreamCaps = map[string]bool{ "message-tags": true, "multi-prefix": true, "server-time": true, + "setname": true, } type registrationError string @@ -732,6 +733,30 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { dc.updateNick() }) } + case "SETNAME": + if msg.Prefix == nil { + return fmt.Errorf("expected a prefix") + } + + var newRealname string + if err := parseMessageParams(msg, &newRealname); err != nil { + return err + } + + // TODO: consider appending this message to logs + + if uc.isOurNick(msg.Prefix.Name) { + uc.logger.Printf("changed realname from %q to %q", uc.realname, newRealname) + uc.realname = newRealname + + uc.forEachDownstream(func(dc *downstreamConn) { + dc.updateRealname() + }) + } else { + uc.forEachDownstream(func(dc *downstreamConn) { + dc.SendMessage(dc.marshalMessage(msg, uc.network)) + }) + } case "JOIN": if msg.Prefix == nil { return fmt.Errorf("expected a prefix") diff --git a/user.go b/user.go index 1d64b4c..192b9ff 100644 --- a/user.go +++ b/user.go @@ -529,6 +529,7 @@ func (u *user) run() { } dc.updateNick() + dc.updateRealname() }) uc.network.lastError = nil case eventUpstreamDisconnected: