Add support for IRCv3 setname

References: https://todo.sr.ht/~emersion/soju/41
This commit is contained in:
Simon Ser 2021-05-25 20:24:45 +02:00
parent 1b43b05588
commit 47c514a9cf
3 changed files with 96 additions and 1 deletions

View file

@ -122,6 +122,7 @@ var permanentDownstreamCaps = map[string]string{
"message-tags": "", "message-tags": "",
"sasl": "PLAIN", "sasl": "PLAIN",
"server-time": "", "server-time": "",
"setname": "",
"soju.im/bouncer-networks": "", "soju.im/bouncer-networks": "",
"soju.im/bouncer-networks-notify": "", "soju.im/bouncer-networks-notify": "",
@ -154,6 +155,7 @@ var passthroughIsupport = map[string]bool{
"MAXLIST": true, "MAXLIST": true,
"MAXTARGETS": true, "MAXTARGETS": true,
"MODES": true, "MODES": true,
"NAMELEN": true,
"NETWORK": true, "NETWORK": true,
"NICKLEN": true, "NICKLEN": true,
"PREFIX": true, "PREFIX": true,
@ -380,6 +382,9 @@ func (dc *downstreamConn) SendMessage(msg *irc.Message) {
if msg.Command == "JOIN" && !dc.caps["extended-join"] { if msg.Command == "JOIN" && !dc.caps["extended-join"] {
msg.Params = msg.Params[:1] msg.Params = msg.Params[:1]
} }
if msg.Command == "SETNAME" && !dc.caps["setname"] {
return
}
dc.conn.SendMessage(msg) 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]) msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
case "TOPIC": case "TOPIC":
msg.Params[0] = dc.marshalEntity(net, msg.Params[0]) msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
case "QUIT": case "QUIT", "SETNAME":
// This space is intentionally left blank // This space is intentionally left blank
default: default:
panic(fmt.Sprintf("unexpected %q message", msg.Command)) 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 { func sanityCheckServer(addr string) error {
dialer := net.Dialer{Timeout: 30 * time.Second} dialer := net.Dialer{Timeout: 30 * time.Second}
conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil) conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
@ -1062,6 +1078,7 @@ func (dc *downstreamConn) welcome() error {
}) })
dc.updateNick() dc.updateNick()
dc.updateRealname()
dc.updateSupportedCaps() dc.updateSupportedCaps()
if dc.caps["soju.im/bouncer-networks-notify"] { if dc.caps["soju.im/bouncer-networks-notify"] {
@ -1343,6 +1360,58 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
dc.nick = nick dc.nick = nick
dc.nickCM = casemapASCII(dc.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": case "JOIN":
var namesStr string var namesStr string
if err := parseMessageParams(msg, &namesStr); err != nil { if err := parseMessageParams(msg, &namesStr); err != nil {

View file

@ -29,6 +29,7 @@ var permanentUpstreamCaps = map[string]bool{
"message-tags": true, "message-tags": true,
"multi-prefix": true, "multi-prefix": true,
"server-time": true, "server-time": true,
"setname": true,
} }
type registrationError string type registrationError string
@ -732,6 +733,30 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
dc.updateNick() 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": case "JOIN":
if msg.Prefix == nil { if msg.Prefix == nil {
return fmt.Errorf("expected a prefix") return fmt.Errorf("expected a prefix")

View file

@ -529,6 +529,7 @@ func (u *user) run() {
} }
dc.updateNick() dc.updateNick()
dc.updateRealname()
}) })
uc.network.lastError = nil uc.network.lastError = nil
case eventUpstreamDisconnected: case eventUpstreamDisconnected: