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
View File

@ -161,7 +161,7 @@ func handleCAP(c *Client, e Event) {
c.state.mu.Unlock()
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.
return
}
@ -172,13 +172,46 @@ func handleCAP(c *Client, e Event) {
}
}
// SASLAuth contains the user and password needed for PLAIN SASL authentication.
type SASLAuth struct {
// SASLMethod is an representation of what a SASL mechanism should support.
// 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.
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 = append(in, 0x0)
@ -213,13 +246,20 @@ func handleSASL(c *Client, e Event) {
if len(e.Params) == 1 && e.Params[0] == "+" {
// 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 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]}})
c.write(&Event{Command: AUTHENTICATE, Params: []string{auth[i]}, Sensitive: true})
if i-1 == len(auth) && len(auth[i]) == 400 {
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
// 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

View File

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

View File

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

View File

@ -304,7 +304,7 @@ func (c *Client) internalConnect(mock net.Conn) error {
// Passwords first.
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.
@ -315,7 +315,7 @@ func (c *Client) internalConnect(mock net.Conn) error {
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
// support.
@ -437,7 +437,9 @@ func (c *Client) sendLoop(errs chan error, done chan struct{}, wg *sync.WaitGrou
select {
case event := <-c.tx:
// Log the event.
if !event.Sensitive {
if event.Sensitive {
c.debug.Printf("> %s ***redacted***", event.Command)
} else {
c.debug.Print("> ", StripRaw(event.String()))
}
if c.Config.Out != nil {

View File

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