Add support for WHOX
This adds support for WHOX, without bothering about flags and mask2 because Solanum and Ergo [1] don't support it either. The motivation is to allow clients to reliably query account names. It's not possible to use WHOX tokens to route replies to the right client, because RPL_ENDOFWHO doesn't contain it. [1]: https://github.com/ergochat/ergo/pull/1184 Closes: https://todo.sr.ht/~emersion/soju/135
This commit is contained in:
parent
8c7c907d6f
commit
241e27b00e
@ -236,6 +236,7 @@ var passthroughIsupport = map[string]bool{
|
||||
"TOPICLEN": true,
|
||||
"USERLEN": true,
|
||||
"UTF8ONLY": true,
|
||||
"WHOX": true,
|
||||
}
|
||||
|
||||
type downstreamConn struct {
|
||||
@ -1157,6 +1158,10 @@ func (dc *downstreamConn) welcome() error {
|
||||
isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
|
||||
}
|
||||
|
||||
if dc.network == nil && dc.caps["soju.im/bouncer-networks"] {
|
||||
isupport = append(isupport, "WHOX")
|
||||
}
|
||||
|
||||
if uc := dc.upstream(); uc != nil {
|
||||
for k := range passthroughIsupport {
|
||||
v, ok := uc.isupport[k]
|
||||
@ -1882,6 +1887,10 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
||||
})
|
||||
}
|
||||
}
|
||||
// For WHOX docs, see:
|
||||
// - http://faerion.sourceforge.net/doc/irc/whox.var
|
||||
// - https://github.com/quakenet/snircd/blob/master/doc/readme.who
|
||||
// Note, many features aren't widely implemented, such as flags and mask2
|
||||
case "WHO":
|
||||
if len(msg.Params) == 0 {
|
||||
// TODO: support WHO without parameters
|
||||
@ -1893,52 +1902,80 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: support WHO masks
|
||||
entity := msg.Params[0]
|
||||
entityCM := casemapASCII(entity)
|
||||
// Clients will use the first mask to match RPL_ENDOFWHO
|
||||
endOfWhoToken := msg.Params[0]
|
||||
|
||||
if dc.network == nil && entityCM == dc.nickCM {
|
||||
// TODO: add support for WHOX mask2
|
||||
mask := msg.Params[0]
|
||||
var options string
|
||||
if len(msg.Params) > 1 {
|
||||
options = msg.Params[1]
|
||||
}
|
||||
|
||||
optionsParts := strings.SplitN(options, "%", 2)
|
||||
// TODO: add support for WHOX flags in optionsParts[0]
|
||||
var fields, whoxToken string
|
||||
if len(optionsParts) == 2 {
|
||||
optionsParts := strings.SplitN(optionsParts[1], ",", 2)
|
||||
fields = strings.ToLower(optionsParts[0])
|
||||
if len(optionsParts) == 2 && strings.Contains(fields, "t") {
|
||||
whoxToken = optionsParts[1]
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support mixed bouncer/upstream WHO queries
|
||||
maskCM := casemapASCII(mask)
|
||||
if dc.network == nil && maskCM == dc.nickCM {
|
||||
// TODO: support AWAY (H/G) in self WHO reply
|
||||
flags := "H"
|
||||
if dc.user.Admin {
|
||||
flags += "*"
|
||||
}
|
||||
dc.SendMessage(&irc.Message{
|
||||
Prefix: dc.srv.prefix(),
|
||||
Command: irc.RPL_WHOREPLY,
|
||||
Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, flags, "0 " + dc.realname},
|
||||
})
|
||||
info := whoxInfo{
|
||||
Token: whoxToken,
|
||||
Username: dc.user.Username,
|
||||
Hostname: dc.hostname,
|
||||
Server: dc.srv.Hostname,
|
||||
Nickname: dc.nick,
|
||||
Flags: flags,
|
||||
Realname: dc.realname,
|
||||
}
|
||||
dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
|
||||
dc.SendMessage(&irc.Message{
|
||||
Prefix: dc.srv.prefix(),
|
||||
Command: irc.RPL_ENDOFWHO,
|
||||
Params: []string{dc.nick, dc.nick, "End of /WHO list"},
|
||||
Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
if entityCM == serviceNickCM {
|
||||
dc.SendMessage(&irc.Message{
|
||||
Prefix: dc.srv.prefix(),
|
||||
Command: irc.RPL_WHOREPLY,
|
||||
Params: []string{serviceNick, "*", servicePrefix.User, servicePrefix.Host, dc.srv.Hostname, serviceNick, "H*", "0 " + serviceRealname},
|
||||
})
|
||||
if maskCM == serviceNickCM {
|
||||
info := whoxInfo{
|
||||
Token: whoxToken,
|
||||
Username: servicePrefix.User,
|
||||
Hostname: servicePrefix.Host,
|
||||
Server: dc.srv.Hostname,
|
||||
Nickname: serviceNick,
|
||||
Flags: "H*",
|
||||
Realname: serviceRealname,
|
||||
}
|
||||
dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
|
||||
dc.SendMessage(&irc.Message{
|
||||
Prefix: dc.srv.prefix(),
|
||||
Command: irc.RPL_ENDOFWHO,
|
||||
Params: []string{dc.nick, serviceNick, "End of /WHO list"},
|
||||
Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
uc, upstreamName, err := dc.unmarshalEntity(entity)
|
||||
// TODO: properly support WHO masks
|
||||
uc, upstreamMask, err := dc.unmarshalEntity(mask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var params []string
|
||||
if len(msg.Params) == 2 {
|
||||
params = []string{upstreamName, msg.Params[1]}
|
||||
} else {
|
||||
params = []string{upstreamName}
|
||||
params := []string{upstreamMask}
|
||||
if options != "" {
|
||||
params = append(params, options)
|
||||
}
|
||||
|
||||
uc.SendMessageLabeled(dc.id, &irc.Message{
|
||||
|
78
irc.go
78
irc.go
@ -17,6 +17,7 @@ const (
|
||||
rpl_globalusers = "266"
|
||||
rpl_creationtime = "329"
|
||||
rpl_topicwhotime = "333"
|
||||
rpl_whospcrpl = "354"
|
||||
err_invalidcapcmd = "410"
|
||||
)
|
||||
|
||||
@ -682,3 +683,80 @@ func parseChatHistoryBound(param string) time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
type whoxInfo struct {
|
||||
Token string
|
||||
Username string
|
||||
Hostname string
|
||||
Server string
|
||||
Nickname string
|
||||
Flags string
|
||||
Account string
|
||||
Realname string
|
||||
}
|
||||
|
||||
func generateWHOXReply(prefix *irc.Prefix, nick, fields string, info *whoxInfo) *irc.Message {
|
||||
if fields == "" {
|
||||
return &irc.Message{
|
||||
Prefix: prefix,
|
||||
Command: irc.RPL_WHOREPLY,
|
||||
Params: []string{nick, "*", info.Username, info.Hostname, info.Server, info.Nickname, info.Flags, "0 " + info.Realname},
|
||||
}
|
||||
}
|
||||
|
||||
fieldSet := make(map[byte]bool)
|
||||
for i := 0; i < len(fields); i++ {
|
||||
fieldSet[fields[i]] = true
|
||||
}
|
||||
|
||||
var params []string
|
||||
if fieldSet['t'] {
|
||||
params = append(params, info.Token)
|
||||
}
|
||||
if fieldSet['c'] {
|
||||
params = append(params, "*")
|
||||
}
|
||||
if fieldSet['u'] {
|
||||
params = append(params, info.Username)
|
||||
}
|
||||
if fieldSet['i'] {
|
||||
params = append(params, "255.255.255.255")
|
||||
}
|
||||
if fieldSet['h'] {
|
||||
params = append(params, info.Hostname)
|
||||
}
|
||||
if fieldSet['s'] {
|
||||
params = append(params, info.Server)
|
||||
}
|
||||
if fieldSet['n'] {
|
||||
params = append(params, info.Nickname)
|
||||
}
|
||||
if fieldSet['f'] {
|
||||
params = append(params, info.Flags)
|
||||
}
|
||||
if fieldSet['d'] {
|
||||
params = append(params, "0")
|
||||
}
|
||||
if fieldSet['l'] { // idle time
|
||||
params = append(params, "0")
|
||||
}
|
||||
if fieldSet['a'] {
|
||||
account := "0" // WHOX uses "0" to mean "no account"
|
||||
if info.Account != "" && info.Account != "*" {
|
||||
account = info.Account
|
||||
}
|
||||
params = append(params, account)
|
||||
}
|
||||
if fieldSet['o'] {
|
||||
params = append(params, "0")
|
||||
}
|
||||
if fieldSet['r'] {
|
||||
params = append(params, info.Realname)
|
||||
}
|
||||
|
||||
return &irc.Message{
|
||||
Prefix: prefix,
|
||||
Command: rpl_whospcrpl,
|
||||
Params: append([]string{nick}, params...),
|
||||
}
|
||||
}
|
||||
|
@ -1452,6 +1452,11 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
||||
// Ignore
|
||||
case irc.RPL_YOURHOST, irc.RPL_CREATED:
|
||||
// Ignore
|
||||
case rpl_whospcrpl:
|
||||
// Not supported in multi-upstream mode, forward as-is
|
||||
uc.forEachDownstream(func(dc *downstreamConn) {
|
||||
dc.SendMessage(msg)
|
||||
})
|
||||
case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
|
||||
fallthrough
|
||||
case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
|
||||
|
Loading…
Reference in New Issue
Block a user