implement basic support for sasl plain
This commit is contained in:
parent
9542a7aac6
commit
039539a6a3
@ -59,6 +59,15 @@ func (c *Client) registerBuiltins() {
|
|||||||
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY))
|
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY))
|
||||||
c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
|
c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
|
||||||
c.Handlers.register(true, ALLEVENTS, HandlerFunc(handleTags))
|
c.Handlers.register(true, ALLEVENTS, HandlerFunc(handleTags))
|
||||||
|
|
||||||
|
// SASL IRCv3 support.
|
||||||
|
c.Handlers.register(true, AUTHENTICATE, HandlerFunc(handleSASL))
|
||||||
|
c.Handlers.register(true, RPL_SASLSUCCESS, HandlerFunc(handleSASL))
|
||||||
|
c.Handlers.register(true, RPL_NICKLOCKED, HandlerFunc(handleSASLError))
|
||||||
|
c.Handlers.register(true, ERR_SASLFAIL, HandlerFunc(handleSASLError))
|
||||||
|
c.Handlers.register(true, ERR_SASLTOOLONG, HandlerFunc(handleSASLError))
|
||||||
|
c.Handlers.register(true, ERR_SASLABORTED, HandlerFunc(handleSASLError))
|
||||||
|
c.Handlers.register(true, RPL_SASLMECHS, HandlerFunc(handleSASLError))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nickname collisions.
|
// Nickname collisions.
|
||||||
|
87
cap.go
87
cap.go
@ -6,6 +6,7 @@ package girc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
@ -34,6 +35,10 @@ func (c *Client) listCAP() {
|
|||||||
func possibleCapList(c *Client) map[string][]string {
|
func possibleCapList(c *Client) map[string][]string {
|
||||||
out := make(map[string][]string)
|
out := make(map[string][]string)
|
||||||
|
|
||||||
|
if c.Config.SASL != nil {
|
||||||
|
out["sasl"] = nil
|
||||||
|
}
|
||||||
|
|
||||||
for k := range c.Config.SupportedCaps {
|
for k := range c.Config.SupportedCaps {
|
||||||
out[k] = c.Config.SupportedCaps[k]
|
out[k] = c.Config.SupportedCaps[k]
|
||||||
}
|
}
|
||||||
@ -150,14 +155,96 @@ func handleCAP(c *Client, e Event) {
|
|||||||
if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK {
|
if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK {
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
c.state.enabledCap = strings.Split(e.Trailing, " ")
|
c.state.enabledCap = strings.Split(e.Trailing, " ")
|
||||||
|
|
||||||
|
// Do we need to do sasl auth?
|
||||||
|
wantsSASL := false
|
||||||
|
for i := 0; i < len(c.state.enabledCap); i++ {
|
||||||
|
if c.state.enabledCap[i] == "sasl" {
|
||||||
|
wantsSASL = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
|
|
||||||
|
if wantsSASL {
|
||||||
|
c.write(&Event{Command: AUTHENTICATE, Params: []string{"PLAIN"}})
|
||||||
|
// Don't "CAP END", since we want to authenticate.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Let the server know that we're done.
|
// Let the server know that we're done.
|
||||||
c.write(&Event{Command: CAP, Params: []string{CAP_END}})
|
c.write(&Event{Command: CAP, Params: []string{CAP_END}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SASLAuth contains the user and password needed for PLAIN SASL authentication.
|
||||||
|
type SASLAuth struct {
|
||||||
|
User string // User is the username for SASL.
|
||||||
|
Pass string // Pass is the password for SASL.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sasl *SASLAuth) encode() (chunks []string) {
|
||||||
|
in := []byte(sasl.User)
|
||||||
|
|
||||||
|
in = append(in, 0x0)
|
||||||
|
in = append(in, []byte(sasl.User)...)
|
||||||
|
in = append(in, 0x0)
|
||||||
|
in = append(in, []byte(sasl.Pass)...)
|
||||||
|
|
||||||
|
out := base64.StdEncoding.EncodeToString(in)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if len(out) > 400 {
|
||||||
|
chunks = append(chunks, out[0:399])
|
||||||
|
out = out[400:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(out) <= 400 {
|
||||||
|
chunks = append(chunks, out)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSASL(c *Client, e Event) {
|
||||||
|
if e.Command == RPL_SASLSUCCESS || e.Command == ERR_SASLALREADY {
|
||||||
|
// Let the server know that we're done.
|
||||||
|
c.write(&Event{Command: CAP, Params: []string{CAP_END}})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Params) == 1 && e.Params[0] == "+" {
|
||||||
|
// Assume they want us to handle sending auth.
|
||||||
|
auth := c.Config.SASL.encode()
|
||||||
|
|
||||||
|
// Send in 400 byte chunks. If the last chuck is exactly 400 bytes,
|
||||||
|
// send a "AUTHENTICATE +" 0-byte response to let the server know
|
||||||
|
// that we're done.
|
||||||
|
for i := 0; i < len(auth); i++ {
|
||||||
|
c.write(&Event{Command: AUTHENTICATE, Params: []string{auth[i]}})
|
||||||
|
|
||||||
|
if i-1 == len(auth) && len(auth[i]) == 400 {
|
||||||
|
c.write(&Event{Command: AUTHENTICATE, Params: []string{"+"}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSASLError(c *Client, e Event) {
|
||||||
|
if c.Config.SASL != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is supposed to panic. Per the IRCv3 spec, one must disconnect upon
|
||||||
|
// authentication error. Maybe though, just a QUIT would be better?
|
||||||
|
panic(fmt.Sprintf("unable to use SASL authentication: %s (%s)", e.Command, e.Trailing))
|
||||||
|
}
|
||||||
|
|
||||||
// handleCHGHOST handles incoming IRCv3 hostname change events. CHGHOST is
|
// handleCHGHOST handles incoming IRCv3 hostname change events. CHGHOST is
|
||||||
// what occurs (when enabled) when a servers services change the hostname of
|
// what occurs (when enabled) when a servers services change the hostname of
|
||||||
// a user. Traditionally, this was simply resolved with a quick QUIT and JOIN,
|
// a user. Traditionally, this was simply resolved with a quick QUIT and JOIN,
|
||||||
|
@ -71,6 +71,9 @@ type Config struct {
|
|||||||
// Name is the "realname" that's used during connection. This only has an
|
// Name is the "realname" that's used during connection. This only has an
|
||||||
// affect during the dial process.
|
// affect during the dial process.
|
||||||
Name string
|
Name string
|
||||||
|
// SASL contains the necessary authentication data to authenticate
|
||||||
|
// with SASL. At this time, only PLAIN is supported.
|
||||||
|
SASL *SASLAuth
|
||||||
// Proxy is a proxy based address, used during the dial process when
|
// Proxy is a proxy based address, used during the dial process when
|
||||||
// connecting to the server. This only has an affect during the dial
|
// connecting to the server. This only has an affect during the dial
|
||||||
// process. Currently, x/net/proxy only supports socks5, however you can
|
// process. Currently, x/net/proxy only supports socks5, however you can
|
||||||
|
Loading…
Reference in New Issue
Block a user