implement basic support for sasl plain

This commit is contained in:
Liam Stanley 2017-04-23 14:03:12 -04:00
parent 9542a7aac6
commit 039539a6a3
3 changed files with 99 additions and 0 deletions

View File

@ -59,6 +59,15 @@ func (c *Client) registerBuiltins() {
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY))
c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
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.

87
cap.go
View File

@ -6,6 +6,7 @@ package girc
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"strings"
@ -34,6 +35,10 @@ func (c *Client) listCAP() {
func possibleCapList(c *Client) map[string][]string {
out := make(map[string][]string)
if c.Config.SASL != nil {
out["sasl"] = nil
}
for k := range c.Config.SupportedCaps {
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 {
c.state.mu.Lock()
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()
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.
c.write(&Event{Command: CAP, Params: []string{CAP_END}})
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
// 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,

View File

@ -71,6 +71,9 @@ type Config struct {
// Name is the "realname" that's used during connection. This only has an
// affect during the dial process.
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
// connecting to the server. This only has an affect during the dial
// process. Currently, x/net/proxy only supports socks5, however you can