Add support for draft/account-registration proxying

This adds support for the draft/account-registration extension [1].
This allows downstreams to register on upstream networks.

[1]: https://ircv3.net/specs/extensions/account-registration
This commit is contained in:
Simon Ser 2021-11-30 11:54:11 +01:00
parent e42b507377
commit 23fd727618
2 changed files with 71 additions and 8 deletions

View File

@ -1079,6 +1079,21 @@ func (dc *downstreamConn) updateSupportedCaps() {
dc.unsetSupportedCap("sasl") dc.unsetSupportedCap("sasl")
} }
if uc := dc.upstream(); uc != nil && uc.caps["draft/account-registration"] {
// Strip "before-connect", because we require downstreams to be fully
// connected before attempting account registration.
values := strings.Split(uc.supportedCaps["draft/account-registration"], ",")
for i, v := range values {
if v == "before-connect" {
values = append(values[:i], values[i+1:]...)
break
}
}
dc.setSupportedCap("draft/account-registration", strings.Join(values, ","))
} else {
dc.unsetSupportedCap("draft/account-registration")
}
if _, ok := dc.user.msgStore.(chatHistoryMessageStore); ok && dc.network != nil { if _, ok := dc.user.msgStore.(chatHistoryMessageStore); ok && dc.network != nil {
dc.setSupportedCap("draft/event-playback", "") dc.setSupportedCap("draft/event-playback", "")
} else { } else {
@ -2408,7 +2423,6 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
uc := dc.upstream() uc := dc.upstream()
if uc == nil || !uc.caps["sasl"] { if uc == nil || !uc.caps["sasl"] {
return ircError{&irc.Message{ return ircError{&irc.Message{
Prefix: dc.srv.prefix(),
Command: irc.ERR_SASLFAIL, Command: irc.ERR_SASLFAIL,
Params: []string{dc.nick, "Upstream network authentication not supported"}, Params: []string{dc.nick, "Upstream network authentication not supported"},
}} }}
@ -2436,6 +2450,23 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
Params: []string{"PLAIN"}, Params: []string{"PLAIN"},
}) })
} }
case "REGISTER", "VERIFY":
// Check number of params here, since we'll use that to save the
// credentials on command success
if (msg.Command == "REGISTER" && len(msg.Params) < 3) || (msg.Command == "VERIFY" && len(msg.Params) < 2) {
return newNeedMoreParamsError(msg.Command)
}
uc := dc.upstream()
if uc == nil || !uc.caps["draft/account-registration"] {
return ircError{&irc.Message{
Command: "FAIL",
Params: []string{msg.Command, "TEMPORARILY_UNAVAILABLE", "*", "Upstream network account registration not supported"},
}}
}
uc.logger.Printf("starting %v with account name %v", msg.Command, msg.Params[0])
uc.enqueueCommand(dc, msg)
case "MONITOR": case "MONITOR":
// MONITOR is unsupported in multi-upstream mode // MONITOR is unsupported in multi-upstream mode
uc := dc.upstream() uc := dc.upstream()

View File

@ -35,7 +35,8 @@ var permanentUpstreamCaps = map[string]bool{
"server-time": true, "server-time": true,
"setname": true, "setname": true,
"draft/extended-monitor": true, "draft/account-registration": true,
"draft/extended-monitor": true,
} }
type registrationError string type registrationError string
@ -300,6 +301,12 @@ func (uc *upstreamConn) endPendingCommands() {
Command: irc.ERR_SASLABORTED, Command: irc.ERR_SASLABORTED,
Params: []string{dc.nick, "SASL authentication aborted"}, Params: []string{dc.nick, "SASL authentication aborted"},
}) })
case "REGISTER", "VERIFY":
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: "FAIL",
Params: []string{pendingCmd.msg.Command, "TEMPORARILY_UNAVAILABLE", pendingCmd.msg.Params[0], "Command aborted"},
})
default: default:
panic(fmt.Errorf("Unsupported pending command %q", pendingCmd.msg.Command)) panic(fmt.Errorf("Unsupported pending command %q", pendingCmd.msg.Command))
} }
@ -318,7 +325,7 @@ func (uc *upstreamConn) sendNextPendingCommand(cmd string) {
func (uc *upstreamConn) enqueueCommand(dc *downstreamConn, msg *irc.Message) { func (uc *upstreamConn) enqueueCommand(dc *downstreamConn, msg *irc.Message) {
switch msg.Command { switch msg.Command {
case "LIST", "WHO", "AUTHENTICATE": case "LIST", "WHO", "AUTHENTICATE", "REGISTER", "VERIFY":
// Supported // Supported
default: default:
panic(fmt.Errorf("Unsupported pending command %q", msg.Command)) panic(fmt.Errorf("Unsupported pending command %q", msg.Command))
@ -633,6 +640,21 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
Params: []string{"END"}, Params: []string{"END"},
}) })
} }
case "REGISTER", "VERIFY":
if dc, cmd := uc.dequeueCommand(msg.Command); dc != nil {
if msg.Command == "REGISTER" {
var account, password string
if err := parseMessageParams(msg, nil, &account); err != nil {
return err
}
if err := parseMessageParams(cmd, nil, nil, &password); err != nil {
return err
}
uc.network.autoSaveSASLPlain(context.TODO(), account, password)
}
dc.SendMessage(msg)
}
case irc.RPL_WELCOME: case irc.RPL_WELCOME:
uc.registered = true uc.registered = true
uc.logger.Printf("connection registered") uc.logger.Printf("connection registered")
@ -1569,11 +1591,8 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
return err return err
} }
if command == "LIST" || command == "WHO" { if dc, _ := uc.dequeueCommand(command); dc != nil && downstreamID == 0 {
dc, _ := uc.dequeueCommand(command) downstreamID = dc.id
if dc != nil && downstreamID == 0 {
downstreamID = dc.id
}
} }
uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) { uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
@ -1583,6 +1602,19 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
Params: []string{dc.nick, command, reason}, Params: []string{dc.nick, command, reason},
}) })
}) })
case "FAIL":
var command string
if err := parseMessageParams(msg, &command); err != nil {
return err
}
if dc, _ := uc.dequeueCommand(command); dc != nil && downstreamID == 0 {
downstreamID = dc.id
}
uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
dc.SendMessage(msg)
})
case "ACK": case "ACK":
// Ignore // Ignore
case irc.RPL_NOWAWAY, irc.RPL_UNAWAY: case irc.RPL_NOWAWAY, irc.RPL_UNAWAY: