diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a0fd3ed..5a986630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ Improved compatibility, more features, etc. ### Security ### Added -* Added integrated help. -* Support for IRCv3 capability [`account-notify`](http://ircv3.net/specs/extensions/account-notify-3.1.html), and draft capability [`message-tags`](http://ircv3.net/specs/core/message-tags-3.3.html) as `draft/message-tags`. +* Added integrated help (with the `/HELP` command). +* Added support for IRCv3.2 [capability negotiation](http://ircv3.net/specs/core/capability-negotiation-3.2.html) including CAP values. +* Added support for IRCv3 capability [`account-notify`](http://ircv3.net/specs/extensions/account-notify-3.1.html), and draft capability [`message-tags`](http://ircv3.net/specs/core/message-tags-3.3.html) as `draft/message-tags`. ### Changed * Casemapping changed from custom unicode mapping to preliminary [rfc7700](https://github.com/ircv3/ircv3-specifications/pull/272) mapping. @@ -43,7 +44,7 @@ Initial release of Oragono! * Ability to parse complex mode change syntax commonly used these days (i.e. `+h-ov dan dan dan`). * User mode for clients connected via TLS (`+Z`). * Ability to register and login to accounts (with passphrase or certfp). -* Support for IRCv3 capabilities [`account-tag`](http://ircv3.net/specs/extensions/account-tag-3.2.html), [`away-notify`](http://ircv3.net/specs/extensions/away-notify-3.1.html), [`extended-join`](http://ircv3.net/specs/extensions/extended-join-3.1.html), [`sasl`](http://ircv3.net/specs/extensions/sasl-3.1.html), [`server-time`](http://ircv3.net/specs/extensions/server-time-3.2.html), and [`userhost-in-names`](http://ircv3.net/specs/extensions/userhost-in-names-3.2.html). +* Added support for IRCv3 capabilities [`account-tag`](http://ircv3.net/specs/extensions/account-tag-3.2.html), [`away-notify`](http://ircv3.net/specs/extensions/away-notify-3.1.html), [`extended-join`](http://ircv3.net/specs/extensions/extended-join-3.1.html), [`sasl`](http://ircv3.net/specs/extensions/sasl-3.1.html), [`server-time`](http://ircv3.net/specs/extensions/server-time-3.2.html), and [`userhost-in-names`](http://ircv3.net/specs/extensions/userhost-in-names-3.2.html). ### Changed * Channel creator (`O`) privilege changed to founder/admin/halfops (`qah`) privileges. diff --git a/irc/capability.go b/irc/capability.go index cf3f56a3..c00963b4 100644 --- a/irc/capability.go +++ b/irc/capability.go @@ -37,6 +37,9 @@ var ( ServerTime: true, UserhostInNames: true, } + CapValues = map[Capability]string{ + SASL: "PLAIN,EXTERNAL", + } ) func (capability Capability) String() string { @@ -65,14 +68,32 @@ const ( CapNegotiated CapState = iota ) +// CapVersion is used to select which max version of CAP the client supports. +type CapVersion uint + +const ( + // Cap301 refers to the base CAP spec. + Cap301 CapVersion = 301 + // Cap302 refers to the IRCv3.2 CAP spec. + Cap302 CapVersion = 302 +) + +// CapabilitySet is used to track supported, enabled, and existing caps. type CapabilitySet map[Capability]bool -func (set CapabilitySet) String() string { +func (set CapabilitySet) String(version CapVersion) string { strs := make([]string, len(set)) index := 0 for capability := range set { - strs[index] = string(capability) - index += 1 + capString := string(capability) + if version == Cap302 { + val, exists := CapValues[capability] + if exists { + capString += "=" + val + } + } + strs[index] = capString + index++ } return strings.Join(strs, " ") } @@ -108,13 +129,17 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if !client.registered { client.capState = CapNegotiating } - // client.server needs to be here to workaround a parsing bug in weechat 1.4 - // and let it connect to the server (otherwise it doesn't respond to the CAP - // message with anything and just hangs on connection) - client.Send(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String()) + if len(msg.Params) > 1 && msg.Params[1] == "302" { + client.capVersion = 302 + } + // weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains + // the server.name source... otherwise it doesn't respond to the CAP message with + // anything and just hangs on connection. + //TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate. + client.Send(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String(client.capVersion)) case "LIST": - client.Send(nil, server.name, "CAP", client.nick, subCommand, client.capabilities.String()) + client.Send(nil, server.name, "CAP", client.nick, subCommand, client.capabilities.String(Cap301)) // values not sent on LIST so force 3.1 case "REQ": // make sure all capabilities actually exist diff --git a/irc/client.go b/irc/client.go index 2ac1c47f..a8a2657c 100644 --- a/irc/client.go +++ b/irc/client.go @@ -33,6 +33,7 @@ type Client struct { awayMessage string capabilities CapabilitySet capState CapState + capVersion CapVersion certfp string channels ChannelSet ctime time.Time @@ -64,8 +65,9 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { client := &Client{ atime: now, authorized: server.password == nil, - capState: CapNone, capabilities: make(CapabilitySet), + capState: CapNone, + capVersion: Cap301, channels: make(ChannelSet), ctime: now, flags: make(map[UserMode]bool),