pax_global_header00006660000000000000000000000064144420134570014516gustar00rootroot0000000000000052 comment=1d333a02b2ccbbc9882cf1b73352c85debd2c144 golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/000077500000000000000000000000001444201345700217665ustar00rootroot00000000000000golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/.build.yml000066400000000000000000000006661444201345700236760ustar00rootroot00000000000000image: alpine/latest packages: - go # Required by codecov - bash - findutils sources: - https://github.com/emersion/go-sasl tasks: - build: | cd go-sasl go build -v ./... - test: | cd go-sasl go test -coverprofile=coverage.txt -covermode=atomic ./... - upload-coverage: | cd go-sasl export CODECOV_TOKEN=3f257f71-a128-4834-8f68-2b534e9f4cb1 curl -s https://codecov.io/bash | bash golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/.gitignore000066400000000000000000000004121444201345700237530ustar00rootroot00000000000000# 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~git20230613.1d333a0/LICENSE000066400000000000000000000020631444201345700227740ustar00rootroot00000000000000The 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~git20230613.1d333a0/README.md000066400000000000000000000012371444201345700232500ustar00rootroot00000000000000# go-sasl [![godocs.io](https://godocs.io/github.com/emersion/go-sasl?status.svg)](https://godocs.io/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) ## License MIT golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/anonymous.go000066400000000000000000000024531444201345700243510ustar00rootroot00000000000000package 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~git20230613.1d333a0/external.go000066400000000000000000000035071444201345700241440ustar00rootroot00000000000000package sasl import ( "bytes" "errors" ) // 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} } // ExternalAuthenticator authenticates users with the EXTERNAL mechanism. If // the identity is left blank, it indicates that it is the same as the one used // in the external credentials. If identity is not empty and the server doesn't // support it, an error must be returned. type ExternalAuthenticator func(identity string) error type externalServer struct { done bool authenticate ExternalAuthenticator } func (a *externalServer) Next(response []byte) (challenge []byte, done bool, err error) { if a.done { return nil, false, ErrUnexpectedClientResponse } // No initial response, send an empty challenge if response == nil { return []byte{}, false, nil } a.done = true if bytes.Contains(response, []byte("\x00")) { return nil, false, errors.New("identity contains a NUL character") } return nil, true, a.authenticate(string(response)) } // NewExternalServer creates a server implementation of the EXTERNAL // authentication mechanism, as described in RFC 4422. func NewExternalServer(authenticator ExternalAuthenticator) Server { return &externalServer{authenticate: authenticator} } golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/go.mod000066400000000000000000000000541444201345700230730ustar00rootroot00000000000000module github.com/emersion/go-sasl go 1.12 golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/login.go000066400000000000000000000042761444201345700234360ustar00rootroot00000000000000package 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~git20230613.1d333a0/login_test.go000066400000000000000000000056771444201345700245030ustar00rootroot00000000000000package 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~git20230613.1d333a0/oauthbearer.go000066400000000000000000000112561444201345700246230ustar00rootroot00000000000000package sasl import ( "bytes" "encoding/json" "errors" "fmt" "strconv" "strings" ) // 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) { var authzid string if a.Username != "" { authzid = "a=" + a.Username } str := "n," + authzid + "," 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 OAuthBearer, ir, nil } 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} } type OAuthBearerAuthenticator func(opts OAuthBearerOptions) *OAuthBearerError type oauthBearerServer struct { done bool failErr error authenticate OAuthBearerAuthenticator } func (a *oauthBearerServer) fail(descr string) ([]byte, bool, error) { blob, err := json.Marshal(OAuthBearerError{ Status: "invalid_request", Schemes: "bearer", }) if err != nil { panic(err) // wtf } a.failErr = errors.New(descr) return blob, false, nil } func (a *oauthBearerServer) Next(response []byte) (challenge []byte, done bool, err error) { // Per RFC, we cannot just send an error, we need to return JSON-structured // value as a challenge and then after getting dummy response from the // client stop the exchange. if a.failErr != nil { // Server libraries (go-smtp, go-imap) will not call Next on // protocol-specific SASL cancel response ('*'). However, GS2 (and // indirectly OAUTHBEARER) defines a protocol-independent way to do so // using 0x01. if len(response) != 1 && response[0] != 0x01 { return nil, true, errors.New("unexpected response") } return nil, true, a.failErr } if a.done { err = ErrUnexpectedClientResponse return } // Generate empty challenge. if response == nil { return []byte{}, false, nil } a.done = true // Cut n,a=username,\x01host=...\x01auth=... // into // n // a=username // \x01host=...\x01auth=...\x01\x01 parts := bytes.SplitN(response, []byte{','}, 3) if len(parts) != 3 { return a.fail("Invalid response") } flag := parts[0] authzid := parts[1] if !bytes.Equal(flag, []byte{'n'}) { return a.fail("Invalid response, missing 'n' in gs2-cb-flag") } opts := OAuthBearerOptions{} if len(authzid) > 0 { if !bytes.HasPrefix(authzid, []byte("a=")) { return a.fail("Invalid response, missing 'a=' in gs2-authzid") } opts.Username = string(bytes.TrimPrefix(authzid, []byte("a="))) } // Cut \x01host=...\x01auth=...\x01\x01 // into // *empty* // host=... // auth=... // *empty* // // Note that this code does not do a lot of checks to make sure the input // follows the exact format specified by RFC. params := bytes.Split(parts[2], []byte{0x01}) for _, p := range params { // Skip empty fields (one at start and end). if len(p) == 0 { continue } pParts := bytes.SplitN(p, []byte{'='}, 2) if len(pParts) != 2 { return a.fail("Invalid response, missing '='") } switch string(pParts[0]) { case "host": opts.Host = string(pParts[1]) case "port": port, err := strconv.ParseUint(string(pParts[1]), 10, 16) if err != nil { return a.fail("Invalid response, malformed 'port' value") } opts.Port = int(port) case "auth": const prefix = "bearer " strValue := string(pParts[1]) // Token type is case-insensitive. if !strings.HasPrefix(strings.ToLower(strValue), prefix) { return a.fail("Unsupported token type") } opts.Token = strValue[len(prefix):] default: return a.fail("Invalid response, unknown parameter: " + string(pParts[0])) } } authzErr := a.authenticate(opts) if authzErr != nil { blob, err := json.Marshal(authzErr) if err != nil { panic(err) // wtf } a.failErr = authzErr return blob, false, nil } return nil, true, nil } func NewOAuthBearerServer(auth OAuthBearerAuthenticator) Server { return &oauthBearerServer{authenticate: auth} } golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/oauthbearer_test.go000066400000000000000000000107151444201345700256610ustar00rootroot00000000000000package 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") } } func TestOAuthBearerServerAndClient(t *testing.T) { oauthErr := sasl.OAuthBearerError{ Status: "invalid_token", Scope: "email", Schemes: "bearer", } authenticator := func(opts sasl.OAuthBearerOptions) *sasl.OAuthBearerError { if opts.Username == "fxcp" && opts.Token == "VkIvciKi9ijpiKNWrQmYCJrzgd9QYCMB" { return nil } return &oauthErr } t.Run("valid token", func(t *testing.T) { s := sasl.NewOAuthBearerServer(authenticator) c := sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{ Username: "fxcp", Token: "VkIvciKi9ijpiKNWrQmYCJrzgd9QYCMB", }) _, ir, err := c.Start() if err != nil { t.Fatal(err) } _, done, err := s.Next(ir) if err != nil { t.Fatal("Unexpected error") } if !done { t.Fatal("Exchange is not complete") } }) t.Run("invalid token", func(t *testing.T) { s := sasl.NewOAuthBearerServer(authenticator) c := sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{ Username: "fxcp", Token: "adiffrentone", }) _, ir, err := c.Start() if err != nil { t.Fatal(err) } val, done, err := s.Next(ir) if err != nil { t.Fatal(err) } if done { t.Fatal("Exchange is marked complete") } _, err = c.Next(val) if err == nil { t.Fatal("Expected an error") } authzErr, ok := err.(*sasl.OAuthBearerError) if !ok { t.Fatal("Not OAuthBearerError") } if authzErr.Status != "invalid_token" { t.Fatal("Wrong status:", authzErr.Status) } if authzErr.Scope != "email" { t.Fatal("Wrong scope:", authzErr.Scope) } }) authenticator = func(opts sasl.OAuthBearerOptions) *sasl.OAuthBearerError { if opts.Username == "" && opts.Token == "VkIvciKi9ijpiKNWrQmYCJrzgd9QYCMB" { return nil } return &oauthErr } t.Run("valid token, no username", func(t *testing.T) { s := sasl.NewOAuthBearerServer(authenticator) c := sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{ Token: "VkIvciKi9ijpiKNWrQmYCJrzgd9QYCMB", }) _, ir, err := c.Start() if err != nil { t.Fatal(err) } _, done, err := s.Next(ir) if err != nil { t.Fatal("Unexpected error") } if !done { t.Fatal("Exchange is not complete") } }) } golang-github-emersion-go-sasl-0.0~git20230613.1d333a0/plain.go000066400000000000000000000036221444201345700234230ustar00rootroot00000000000000package 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~git20230613.1d333a0/plain_test.go000066400000000000000000000033671444201345700244700ustar00rootroot00000000000000package 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~git20230613.1d333a0/sasl.go000066400000000000000000000033401444201345700232570ustar00rootroot00000000000000// 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) }