start working on additional sasl implementation support

This commit is contained in:
Liam Stanley 2017-06-14 07:19:31 -04:00
parent a5f52432c3
commit e3f53ab9ec
5 changed files with 58 additions and 15 deletions

54
cap.go

@ -161,7 +161,7 @@ func handleCAP(c *Client, e Event) {
c.state.mu.Unlock() c.state.mu.Unlock()
if wantsSASL { if wantsSASL {
c.write(&Event{Command: AUTHENTICATE, Params: []string{"PLAIN"}}) c.write(&Event{Command: AUTHENTICATE, Params: []string{c.Config.SASL.Method()}})
// Don't "CAP END", since we want to authenticate. // Don't "CAP END", since we want to authenticate.
return return
} }
@ -172,13 +172,46 @@ func handleCAP(c *Client, e Event) {
} }
} }
// SASLAuth contains the user and password needed for PLAIN SASL authentication. // SASLMethod is an representation of what a SASL mechanism should support.
type SASLAuth struct { // See SASLExternal and SASLPlain for implementations of this.
type SASLMethod interface {
// Method returns the uppercase version of the SASL mechanism name.
Method() string
// Encode returns the response that the SASL mechanism wants to use,
// chunked out as necessary. if this returns nil, an "AUTHENTICATE +" will
// be used to respond (essentially telling the server that it should handle
// the rest.)
Encode(chunkSize int) (chunks []string)
}
// SASLExternal implements the "EXTERNAL" SASL type.
type SASLExternal struct {
}
// Method identifies what type of SASL this implements.
func (sasl *SASLExternal) Method() string {
return "EXTERNAL"
}
// Encode is not directly used by SASLExternal -- it currently only returns
// nil.
func (sasl *SASLExternal) Encode(chunkSize int) (chunks []string) {
return nil
}
// SASLPlain contains the user and password needed for PLAIN SASL authentication.
type SASLPlain struct {
User string // User is the username for SASL. User string // User is the username for SASL.
Pass string // Pass is the password for SASL. Pass string // Pass is the password for SASL.
} }
func (sasl *SASLAuth) encode(chunkSize int) (chunks []string) { // Method identifies what type of SASL this implements.
func (sasl *SASLPlain) Method() string {
return "PLAIN"
}
// Encode encodes the plain user+password into a standardized chunk size.
func (sasl *SASLPlain) Encode(chunkSize int) (chunks []string) {
in := []byte(sasl.User) in := []byte(sasl.User)
in = append(in, 0x0) in = append(in, 0x0)
@ -213,13 +246,20 @@ func handleSASL(c *Client, e Event) {
if len(e.Params) == 1 && e.Params[0] == "+" { if len(e.Params) == 1 && e.Params[0] == "+" {
// Assume they want us to handle sending auth. // Assume they want us to handle sending auth.
auth := c.Config.SASL.encode(400) auth := c.Config.SASL.Encode(400)
if auth == nil {
// Assume the SASL authentication method doesn't need to encode
// data and pass it to the server.
c.write(&Event{Command: AUTHENTICATE, Params: []string{"+"}})
return
}
// Send in 400 byte chunks. If the last chuck is exactly 400 bytes, // Send in 400 byte chunks. If the last chuck is exactly 400 bytes,
// send a "AUTHENTICATE +" 0-byte response to let the server know // send a "AUTHENTICATE +" 0-byte response to let the server know
// that we're done. // that we're done.
for i := 0; i < len(auth); i++ { for i := 0; i < len(auth); i++ {
c.write(&Event{Command: AUTHENTICATE, Params: []string{auth[i]}}) c.write(&Event{Command: AUTHENTICATE, Params: []string{auth[i]}, Sensitive: true})
if i-1 == len(auth) && len(auth[i]) == 400 { if i-1 == len(auth) && len(auth[i]) == 400 {
c.write(&Event{Command: AUTHENTICATE, Params: []string{"+"}}) c.write(&Event{Command: AUTHENTICATE, Params: []string{"+"}})
@ -236,7 +276,7 @@ func handleSASLError(c *Client, e Event) {
// This is supposed to panic. Per the IRCv3 spec, one must disconnect upon // This is supposed to panic. Per the IRCv3 spec, one must disconnect upon
// authentication error. Maybe though, just a QUIT would be better? // authentication error. Maybe though, just a QUIT would be better?
panic(fmt.Sprintf("unable to use SASL authentication: %s (%s)", e.Command, e.Trailing)) panic(fmt.Sprintf("unable to use SASL %s authentication: %s (%s)", c.Config.SASL.Method(), e.Command, e.Trailing))
} }
// handleCHGHOST handles incoming IRCv3 hostname change events. CHGHOST is // handleCHGHOST handles incoming IRCv3 hostname change events. CHGHOST is

@ -12,7 +12,7 @@ func TestCapList(t *testing.T) {
Server: "irc.example.com", Server: "irc.example.com",
Nick: "test", Nick: "test",
User: "user", User: "user",
SASL: &SASLAuth{User: "test", Pass: "example"}, SASL: &SASLPlain{User: "test", Pass: "example"},
SupportedCaps: map[string][]string{"example": nil}, SupportedCaps: map[string][]string{"example": nil},
}) })

@ -80,9 +80,10 @@ type Config struct {
// affect during the dial process. // affect during the dial process.
Name string Name string
// SASL contains the necessary authentication data to authenticate // SASL contains the necessary authentication data to authenticate
// with SASL. At this time, only PLAIN is supported. Capability tracking // with SASL. See the documentation for SASLMethod for what is currently
// must be enabled for this to work, as this requires IRCv3 CAP handling. // supported. Capability tracking must be enabled for this to work, as
SASL *SASLAuth // this requires IRCv3 CAP handling.
SASL SASLMethod
// 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

@ -304,7 +304,7 @@ func (c *Client) internalConnect(mock net.Conn) error {
// Passwords first. // Passwords first.
if c.Config.ServerPass != "" { if c.Config.ServerPass != "" {
c.write(&Event{Command: PASS, Params: []string{c.Config.ServerPass}}) c.write(&Event{Command: PASS, Params: []string{c.Config.ServerPass}, Sensitive: true})
} }
// Then nickname. // Then nickname.
@ -315,7 +315,7 @@ func (c *Client) internalConnect(mock net.Conn) error {
c.Config.Name = c.Config.User c.Config.Name = c.Config.User
} }
c.write(&Event{Command: USER, Params: []string{c.Config.User, "+iw", "*"}, Trailing: c.Config.Name}) c.write(&Event{Command: USER, Params: []string{c.Config.User, "*", "*"}, Trailing: c.Config.Name})
// List the IRCv3 capabilities, specifically with the max protocol we // List the IRCv3 capabilities, specifically with the max protocol we
// support. // support.
@ -437,7 +437,9 @@ func (c *Client) sendLoop(errs chan error, done chan struct{}, wg *sync.WaitGrou
select { select {
case event := <-c.tx: case event := <-c.tx:
// Log the event. // Log the event.
if !event.Sensitive { if event.Sensitive {
c.debug.Printf("> %s ***redacted***", event.Command)
} else {
c.debug.Print("> ", StripRaw(event.String())) c.debug.Print("> ", StripRaw(event.String()))
} }
if c.Config.Out != nil { if c.Config.Out != nil {

@ -19,7 +19,7 @@ func ExampleNew() {
Port: 6667, Port: 6667,
Nick: "test", Nick: "test",
User: "user", User: "user",
SASL: &girc.SASLAuth{User: "user1", Pass: "securepass1"}, SASL: &girc.SASLPlain{User: "user1", Pass: "securepass1"},
Out: os.Stdout, Out: os.Stdout,
}) })