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,
|
"TOPICLEN": true,
|
||||||
"USERLEN": true,
|
"USERLEN": true,
|
||||||
"UTF8ONLY": true,
|
"UTF8ONLY": true,
|
||||||
|
"WHOX": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
type downstreamConn struct {
|
type downstreamConn struct {
|
||||||
@ -1157,6 +1158,10 @@ func (dc *downstreamConn) welcome() error {
|
|||||||
isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
|
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 {
|
if uc := dc.upstream(); uc != nil {
|
||||||
for k := range passthroughIsupport {
|
for k := range passthroughIsupport {
|
||||||
v, ok := uc.isupport[k]
|
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":
|
case "WHO":
|
||||||
if len(msg.Params) == 0 {
|
if len(msg.Params) == 0 {
|
||||||
// TODO: support WHO without parameters
|
// TODO: support WHO without parameters
|
||||||
@ -1893,52 +1902,80 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: support WHO masks
|
// Clients will use the first mask to match RPL_ENDOFWHO
|
||||||
entity := msg.Params[0]
|
endOfWhoToken := msg.Params[0]
|
||||||
entityCM := casemapASCII(entity)
|
|
||||||
|
|
||||||
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
|
// TODO: support AWAY (H/G) in self WHO reply
|
||||||
flags := "H"
|
flags := "H"
|
||||||
if dc.user.Admin {
|
if dc.user.Admin {
|
||||||
flags += "*"
|
flags += "*"
|
||||||
}
|
}
|
||||||
dc.SendMessage(&irc.Message{
|
info := whoxInfo{
|
||||||
Prefix: dc.srv.prefix(),
|
Token: whoxToken,
|
||||||
Command: irc.RPL_WHOREPLY,
|
Username: dc.user.Username,
|
||||||
Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, flags, "0 " + dc.realname},
|
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{
|
dc.SendMessage(&irc.Message{
|
||||||
Prefix: dc.srv.prefix(),
|
Prefix: dc.srv.prefix(),
|
||||||
Command: irc.RPL_ENDOFWHO,
|
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
|
return nil
|
||||||
}
|
}
|
||||||
if entityCM == serviceNickCM {
|
if maskCM == serviceNickCM {
|
||||||
dc.SendMessage(&irc.Message{
|
info := whoxInfo{
|
||||||
Prefix: dc.srv.prefix(),
|
Token: whoxToken,
|
||||||
Command: irc.RPL_WHOREPLY,
|
Username: servicePrefix.User,
|
||||||
Params: []string{serviceNick, "*", servicePrefix.User, servicePrefix.Host, dc.srv.Hostname, serviceNick, "H*", "0 " + serviceRealname},
|
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{
|
dc.SendMessage(&irc.Message{
|
||||||
Prefix: dc.srv.prefix(),
|
Prefix: dc.srv.prefix(),
|
||||||
Command: irc.RPL_ENDOFWHO,
|
Command: irc.RPL_ENDOFWHO,
|
||||||
Params: []string{dc.nick, serviceNick, "End of /WHO list"},
|
Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
uc, upstreamName, err := dc.unmarshalEntity(entity)
|
// TODO: properly support WHO masks
|
||||||
|
uc, upstreamMask, err := dc.unmarshalEntity(mask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var params []string
|
params := []string{upstreamMask}
|
||||||
if len(msg.Params) == 2 {
|
if options != "" {
|
||||||
params = []string{upstreamName, msg.Params[1]}
|
params = append(params, options)
|
||||||
} else {
|
|
||||||
params = []string{upstreamName}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uc.SendMessageLabeled(dc.id, &irc.Message{
|
uc.SendMessageLabeled(dc.id, &irc.Message{
|
||||||
|
78
irc.go
78
irc.go
@ -17,6 +17,7 @@ const (
|
|||||||
rpl_globalusers = "266"
|
rpl_globalusers = "266"
|
||||||
rpl_creationtime = "329"
|
rpl_creationtime = "329"
|
||||||
rpl_topicwhotime = "333"
|
rpl_topicwhotime = "333"
|
||||||
|
rpl_whospcrpl = "354"
|
||||||
err_invalidcapcmd = "410"
|
err_invalidcapcmd = "410"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -682,3 +683,80 @@ func parseChatHistoryBound(param string) time.Time {
|
|||||||
return 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
|
// Ignore
|
||||||
case irc.RPL_YOURHOST, irc.RPL_CREATED:
|
case irc.RPL_YOURHOST, irc.RPL_CREATED:
|
||||||
// Ignore
|
// 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:
|
case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
|
||||||
fallthrough
|
fallthrough
|
||||||
case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
|
case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
|
||||||
|
Loading…
Reference in New Issue
Block a user