pax_global_header00006660000000000000000000000064136463013630014517gustar00rootroot0000000000000052 comment=1b5168f4bf129c35c878f7191034369c83c0a03e golang-github-emersion-go-sasl-0.0~git20191210.430746e/000077500000000000000000000000001364630136300217245ustar00rootroot00000000000000golang-github-emersion-go-sasl-0.0~git20191210.430746e/.gitignore000066400000000000000000000004121364630136300237110ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof golang-github-emersion-go-sasl-0.0~git20191210.430746e/.travis.yml000066400000000000000000000000311364630136300240270ustar00rootroot00000000000000language: go go: - 1.5 golang-github-emersion-go-sasl-0.0~git20191210.430746e/LICENSE000066400000000000000000000020631364630136300227320ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 emersion Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-emersion-go-sasl-0.0~git20191210.430746e/README.md000066400000000000000000000014041364630136300232020ustar00rootroot00000000000000# go-sasl [![GoDoc](https://godoc.org/github.com/emersion/go-sasl?status.svg)](https://godoc.org/github.com/emersion/go-sasl) [![Build Status](https://travis-ci.org/emersion/go-sasl.svg?branch=master)](https://travis-ci.org/emersion/go-sasl) A [SASL](https://tools.ietf.org/html/rfc4422) library written in Go. Implemented mechanisms: * [ANONYMOUS](https://tools.ietf.org/html/rfc4505) * [EXTERNAL](https://tools.ietf.org/html/rfc4422#appendix-A) * [LOGIN](https://tools.ietf.org/html/draft-murchison-sasl-login-00) (obsolete, use PLAIN instead) * [PLAIN](https://tools.ietf.org/html/rfc4616) * [OAUTHBEARER](https://tools.ietf.org/html/rfc7628) * [XOAUTH2](https://developers.google.com/gmail/xoauth2_protocol) (non-standard, use OAUTHBEARER instead) ## License MIT golang-github-emersion-go-sasl-0.0~git20191210.430746e/anonymous.go000066400000000000000000000024431364630136300243060ustar00rootroot00000000000000package sasl // The ANONYMOUS mechanism name. const Anonymous = "ANONYMOUS" type anonymousClient struct { Trace string } func (c *anonymousClient) Start() (mech string, ir []byte, err error) { mech = Anonymous ir = []byte(c.Trace) return } func (c *anonymousClient) Next(challenge []byte) (response []byte, err error) { return nil, ErrUnexpectedServerChallenge } // A client implementation of the ANONYMOUS authentication mechanism, as // described in RFC 4505. func NewAnonymousClient(trace string) Client { return &anonymousClient{trace} } // Get trace information from clients logging in anonymously. type AnonymousAuthenticator func(trace string) error type anonymousServer struct { done bool authenticate AnonymousAuthenticator } func (s *anonymousServer) Next(response []byte) (challenge []byte, done bool, err error) { if s.done { err = ErrUnexpectedClientResponse return } // No initial response, send an empty challenge if response == nil { return []byte{}, false, nil } s.done = true err = s.authenticate(string(response)) done = true return } // A server implementation of the ANONYMOUS authentication mechanism, as // described in RFC 4505. func NewAnonymousServer(authenticator AnonymousAuthenticator) Server { return &anonymousServer{authenticate: authenticator} } golang-github-emersion-go-sasl-0.0~git20191210.430746e/external.go000066400000000000000000000013051364630136300240740ustar00rootroot00000000000000package sasl // The EXTERNAL mechanism name. const External = "EXTERNAL" type externalClient struct { Identity string } func (a *externalClient) Start() (mech string, ir []byte, err error) { mech = External ir = []byte(a.Identity) return } func (a *externalClient) Next(challenge []byte) (response []byte, err error) { return nil, ErrUnexpectedServerChallenge } // An implementation of the EXTERNAL authentication mechanism, as described in // RFC 4422. Authorization identity may be left blank to indicate that the // client is requesting to act as the identity associated with the // authentication credentials. func NewExternalClient(identity string) Client { return &externalClient{identity} } golang-github-emersion-go-sasl-0.0~git20191210.430746e/login.go000066400000000000000000000042761364630136300233740ustar00rootroot00000000000000package sasl import ( "bytes" ) // The LOGIN mechanism name. const Login = "LOGIN" var expectedChallenge = []byte("Password:") type loginClient struct { Username string Password string } func (a *loginClient) Start() (mech string, ir []byte, err error) { mech = "LOGIN" ir = []byte(a.Username) return } func (a *loginClient) Next(challenge []byte) (response []byte, err error) { if bytes.Compare(challenge, expectedChallenge) != 0 { return nil, ErrUnexpectedServerChallenge } else { return []byte(a.Password), nil } } // A client implementation of the LOGIN authentication mechanism for SMTP, // as described in http://www.iana.org/go/draft-murchison-sasl-login // // It is considered obsolete, and should not be used when other mechanisms are // available. For plaintext password authentication use PLAIN mechanism. func NewLoginClient(username, password string) Client { return &loginClient{username, password} } // Authenticates users with an username and a password. type LoginAuthenticator func(username, password string) error type loginState int const ( loginNotStarted loginState = iota loginWaitingUsername loginWaitingPassword ) type loginServer struct { state loginState username, password string authenticate LoginAuthenticator } // A server implementation of the LOGIN authentication mechanism, as described // in https://tools.ietf.org/html/draft-murchison-sasl-login-00. // // LOGIN is obsolete and should only be enabled for legacy clients that cannot // be updated to use PLAIN. func NewLoginServer(authenticator LoginAuthenticator) Server { return &loginServer{authenticate: authenticator} } func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) { switch a.state { case loginNotStarted: // Check for initial response field, as per RFC4422 section 3 if response == nil { challenge = []byte("Username:") break } a.state++ fallthrough case loginWaitingUsername: a.username = string(response) challenge = []byte("Password:") case loginWaitingPassword: a.password = string(response) err = a.authenticate(a.username, a.password) done = true default: err = ErrUnexpectedClientResponse } a.state++ return } golang-github-emersion-go-sasl-0.0~git20191210.430746e/login_test.go000066400000000000000000000056771364630136300244410ustar00rootroot00000000000000package sasl_test import ( "bytes" "errors" "testing" "github.com/emersion/go-sasl" ) func TestNewLoginClient(t *testing.T) { c := sasl.NewLoginClient("username", "Password:") mech, resp, err := c.Start() if err != nil { t.Fatal("Error while starting client:", err) } if mech != "LOGIN" { t.Error("Invalid mechanism name:", mech) } expected := []byte{117, 115, 101, 114, 110, 97, 109, 101} if bytes.Compare(resp, expected) != 0 { t.Error("Invalid initial response:", resp) } resp, err = c.Next(expected) if err != sasl.ErrUnexpectedServerChallenge { t.Error("Invalid chalange") } expected = []byte("Password:") resp, err = c.Next(expected) if bytes.Compare(resp, expected) != 0 { t.Error("Invalid initial response:", resp) } } func TestNewLoginServer(t *testing.T) { var authenticated = false s := sasl.NewLoginServer(func(username, password string) error { if username != "tim" { return errors.New("Invalid username: " + username) } if password != "tanstaaftanstaaf" { return errors.New("Invalid password: " + password) } authenticated = true return nil }) challenge, done, err := s.Next(nil) if err != nil { t.Fatal("Error while starting server:", err) } if done { t.Fatal("Done after starting server") } if string(challenge) != "Username:" { t.Error("Invalid first challenge:", challenge) } challenge, done, err = s.Next([]byte("tim")) if err != nil { t.Fatal("Error while sending username:", err) } if done { t.Fatal("Done after sending username") } if string(challenge) != "Password:" { t.Error("Invalid challenge after sending username:", challenge) } challenge, done, err = s.Next([]byte("tanstaaftanstaaf")) if err != nil { t.Fatal("Error while sending password:", err) } if !done { t.Fatal("Authentication not finished after sending password") } if len(challenge) > 0 { t.Error("Invalid non-empty final challenge:", challenge) } if !authenticated { t.Error("Not authenticated") } // Tests with initial response field, as per RFC4422 section 3 authenticated = false s = sasl.NewLoginServer(func(username, password string) error { if username != "tim" { return errors.New("Invalid username: " + username) } if password != "tanstaaftanstaaf" { return errors.New("Invalid password: " + password) } authenticated = true return nil }) challenge, done, err = s.Next([]byte("tim")) if err != nil { t.Fatal("Error while sending username:", err) } if done { t.Fatal("Done after sending username") } if string(challenge) != "Password:" { t.Error("Invalid challenge after sending username:", string(challenge)) } challenge, done, err = s.Next([]byte("tanstaaftanstaaf")) if err != nil { t.Fatal("Error while sending password:", err) } if !done { t.Fatal("Authentication not finished after sending password") } if len(challenge) > 0 { t.Error("Invalid non-empty final challenge:", challenge) } if !authenticated { t.Error("Not authenticated") } } golang-github-emersion-go-sasl-0.0~git20191210.430746e/oauthbearer.go000066400000000000000000000024411364630136300245550ustar00rootroot00000000000000package sasl import ( "encoding/json" "fmt" "strconv" ) // The OAUTHBEARER mechanism name. const OAuthBearer = "OAUTHBEARER" type OAuthBearerError struct { Status string `json:"status"` Schemes string `json:"schemes"` Scope string `json:"scope"` } type OAuthBearerOptions struct { Username string Token string Host string Port int } // Implements error func (err *OAuthBearerError) Error() string { return fmt.Sprintf("OAUTHBEARER authentication error (%v)", err.Status) } type oauthBearerClient struct { OAuthBearerOptions } func (a *oauthBearerClient) Start() (mech string, ir []byte, err error) { mech = OAuthBearer var str = "n,a=" + a.Username + "," if a.Host != "" { str += "\x01host=" + a.Host } if a.Port != 0 { str += "\x01port=" + strconv.Itoa(a.Port) } str += "\x01auth=Bearer " + a.Token + "\x01\x01" ir = []byte(str) return } func (a *oauthBearerClient) Next(challenge []byte) ([]byte, error) { authBearerErr := &OAuthBearerError{} if err := json.Unmarshal(challenge, authBearerErr); err != nil { return nil, err } else { return nil, authBearerErr } } // An implementation of the OAUTHBEARER authentication mechanism, as // described in RFC 7628. func NewOAuthBearerClient(opt *OAuthBearerOptions) Client { return &oauthBearerClient{*opt} } golang-github-emersion-go-sasl-0.0~git20191210.430746e/oauthbearer_test.go000066400000000000000000000045331364630136300256200ustar00rootroot00000000000000package sasl_test import ( "bytes" "testing" "github.com/emersion/go-sasl" ) func TestNewOAuthBearerClientNoHostOrPort(t *testing.T) { c := sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{ Username: "user@example.com", Token: "vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==", }) mech, ir, err := c.Start() if err != nil { t.Fatal("Error while starting client:", err) } if mech != "OAUTHBEARER" { t.Error("Invalid mechanism name:", mech) } expected := []byte{110, 44, 97, 61, 117, 115, 101, 114, 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 44, 1, 97, 117, 116, 104, 61, 66, 101, 97, 114, 101, 114, 32, 118, 70, 57, 100, 102, 116, 52, 113, 109, 84, 99, 50, 78, 118, 98, 51, 82, 108, 99, 107, 66, 104, 98, 72, 82, 104, 100, 109, 108, 122, 100, 71, 69, 117, 89, 50, 57, 116, 67, 103, 61, 61, 1, 1} if bytes.Compare(ir, expected) != 0 { t.Error("Invalid initial response:", ir) } } func TestNewOAuthBearerClient(t *testing.T) { c := sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{ Username: "user@example.com", Token: "vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==", Host: "server.example.com", Port: 143, }) mech, ir, err := c.Start() if err != nil { t.Fatal("Error while starting client:", err) } if mech != "OAUTHBEARER" { t.Error("Invalid mechanism name:", mech) } expected := []byte{110, 44, 97, 61, 117, 115, 101, 114, 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 44, 1, 104, 111, 115, 116, 61, 115, 101, 114, 118, 101, 114, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 1, 112, 111, 114, 116, 61, 49, 52, 51, 1, 97, 117, 116, 104, 61, 66, 101, 97, 114, 101, 114, 32, 118, 70, 57, 100, 102, 116, 52, 113, 109, 84, 99, 50, 78, 118, 98, 51, 82, 108, 99, 107, 66, 104, 98, 72, 82, 104, 100, 109, 108, 122, 100, 71, 69, 117, 89, 50, 57, 116, 67, 103, 61, 61, 1, 1} if bytes.Compare(ir, expected) != 0 { t.Error("Invalid initial response:", ir) } challenge := []byte("eyJzdGF0dXMiOiJpbnZhbGlkX3Rva2VuIiwic2NvcGUiOiJleGFt" + "cGxlX3Njb3BlIiwib3BlbmlkLWNvbmZpZ3VyYXRpb24iOiJodHRwczovL2V4YW1wbGUu" + "Y29tLy53ZWxsLWtub3duL29wZW5pZC1jb25maWd1cmF0aW9uIn0=") if _, err := c.Next(challenge); err == nil { t.Fatal("Expected error from handling challenge") } if _, err := c.Next([]byte("")); err == nil { t.Fatal("Expected error from handling challenge") } } golang-github-emersion-go-sasl-0.0~git20191210.430746e/plain.go000066400000000000000000000036121364630136300233600ustar00rootroot00000000000000package sasl import ( "bytes" "errors" ) // The PLAIN mechanism name. const Plain = "PLAIN" type plainClient struct { Identity string Username string Password string } func (a *plainClient) Start() (mech string, ir []byte, err error) { mech = "PLAIN" ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password) return } func (a *plainClient) Next(challenge []byte) (response []byte, err error) { return nil, ErrUnexpectedServerChallenge } // A client implementation of the PLAIN authentication mechanism, as described // in RFC 4616. Authorization identity may be left blank to indicate that it is // the same as the username. func NewPlainClient(identity, username, password string) Client { return &plainClient{identity, username, password} } // Authenticates users with an identity, a username and a password. If the // identity is left blank, it indicates that it is the same as the username. // If identity is not empty and the server doesn't support it, an error must be // returned. type PlainAuthenticator func(identity, username, password string) error type plainServer struct { done bool authenticate PlainAuthenticator } func (a *plainServer) Next(response []byte) (challenge []byte, done bool, err error) { if a.done { err = ErrUnexpectedClientResponse return } // No initial response, send an empty challenge if response == nil { return []byte{}, false, nil } a.done = true parts := bytes.Split(response, []byte("\x00")) if len(parts) != 3 { err = errors.New("Invalid response") return } identity := string(parts[0]) username := string(parts[1]) password := string(parts[2]) err = a.authenticate(identity, username, password) done = true return } // A server implementation of the PLAIN authentication mechanism, as described // in RFC 4616. func NewPlainServer(authenticator PlainAuthenticator) Server { return &plainServer{authenticate: authenticator} } golang-github-emersion-go-sasl-0.0~git20191210.430746e/plain_test.go000066400000000000000000000033701364630136300244200ustar00rootroot00000000000000package sasl_test import ( "bytes" "errors" "testing" "github.com/emersion/go-sasl" ) func TestNewPlainClient(t *testing.T) { c := sasl.NewPlainClient("identity", "username", "password") mech, ir, err := c.Start() if err != nil { t.Fatal("Error while starting client:", err) } if mech != "PLAIN" { t.Error("Invalid mechanism name:", mech) } expected := []byte{105, 100, 101, 110, 116, 105, 116, 121, 0, 117, 115, 101, 114, 110, 97, 109, 101, 0, 112, 97, 115, 115, 119, 111, 114, 100} if bytes.Compare(ir, expected) != 0 { t.Error("Invalid initial response:", ir) } } func TestNewPlainServer(t *testing.T) { var authenticated = false s := sasl.NewPlainServer(func (identity, username, password string) error { if username != "username" { return errors.New("Invalid username: " + username) } if password != "password" { return errors.New("Invalid password: " + password) } if identity != "identity" { return errors.New("Invalid identity: " + identity) } authenticated = true return nil }) challenge, done, err := s.Next(nil) if err != nil { t.Fatal("Error while starting server:", err) } if done { t.Fatal("Done after starting server") } if len(challenge) > 0 { t.Error("Invalid non-empty initial challenge:", challenge) } response := []byte{105, 100, 101, 110, 116, 105, 116, 121, 0, 117, 115, 101, 114, 110, 97, 109, 101, 0, 112, 97, 115, 115, 119, 111, 114, 100} challenge, done, err = s.Next(response) if err != nil { t.Fatal("Error while finishing authentication:", err) } if !done { t.Fatal("Authentication not finished after sending PLAIN credentials") } if len(challenge) > 0 { t.Error("Invalid non-empty final challenge:", challenge) } if !authenticated { t.Error("Not authenticated") } } golang-github-emersion-go-sasl-0.0~git20191210.430746e/sasl.go000066400000000000000000000033371364630136300232230ustar00rootroot00000000000000// Library for Simple Authentication and Security Layer (SASL) defined in RFC 4422. package sasl // Note: // Most of this code was copied, with some modifications, from net/smtp. It // would be better if Go provided a standard package (e.g. crypto/sasl) that // could be shared by SMTP, IMAP, and other packages. import ( "errors" ) // Common SASL errors. var ( ErrUnexpectedClientResponse = errors.New("sasl: unexpected client response") ErrUnexpectedServerChallenge = errors.New("sasl: unexpected server challenge") ) // Client interface to perform challenge-response authentication. type Client interface { // Begins SASL authentication with the server. It returns the // authentication mechanism name and "initial response" data (if required by // the selected mechanism). A non-nil error causes the client to abort the // authentication attempt. // // A nil ir value is different from a zero-length value. The nil value // indicates that the selected mechanism does not use an initial response, // while a zero-length value indicates an empty initial response, which must // be sent to the server. Start() (mech string, ir []byte, err error) // Continues challenge-response authentication. A non-nil error causes // the client to abort the authentication attempt. Next(challenge []byte) (response []byte, err error) } // Server interface to perform challenge-response authentication. type Server interface { // Begins or continues challenge-response authentication. If the client // supplies an initial response, response is non-nil. // // If the authentication is finished, done is set to true. If the // authentication has failed, an error is returned. Next(response []byte) (challenge []byte, done bool, err error) } golang-github-emersion-go-sasl-0.0~git20191210.430746e/xoauth2.go000066400000000000000000000021251364630136300236450ustar00rootroot00000000000000package sasl import ( "encoding/json" "fmt" ) // The XOAUTH2 mechanism name. const Xoauth2 = "XOAUTH2" // An XOAUTH2 error. type Xoauth2Error struct { Status string `json:"status"` Schemes string `json:"schemes"` Scope string `json:"scope"` } // Implements error. func (err *Xoauth2Error) Error() string { return fmt.Sprintf("XOAUTH2 authentication error (%v)", err.Status) } type xoauth2Client struct { Username string Token string } func (a *xoauth2Client) Start() (mech string, ir []byte, err error) { mech = Xoauth2 ir = []byte("user=" + a.Username + "\x01auth=Bearer " + a.Token + "\x01\x01") return } func (a *xoauth2Client) Next(challenge []byte) ([]byte, error) { // Server sent an error response xoauth2Err := &Xoauth2Error{} if err := json.Unmarshal(challenge, xoauth2Err); err != nil { return nil, err } else { return nil, xoauth2Err } } // An implementation of the XOAUTH2 authentication mechanism, as // described in https://developers.google.com/gmail/xoauth2_protocol. func NewXoauth2Client(username, token string) Client { return &xoauth2Client{username, token} }