Mercurial > projects > managesieve
view managesieve_test.go @ 4:f9bb517e9447
Return warning messages from the CHECKSCRIPT and PUTSCRIPT commands
author | Guido Berhoerster <guido+managesieve@berhoerster.name> |
---|---|
date | Tue, 27 Oct 2020 17:47:14 +0100 |
parents | 6369453d47a3 |
children | b790df0733d4 |
line wrap: on
line source
// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name> // // 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. package managesieve_test import ( "bufio" "bytes" "errors" "fmt" "io" "net" "strings" "testing" "time" "go.guido-berhoerster.org/managesieve" ) func quoteClientString(s string) string { return fmt.Sprintf("{%d+}\r\n%s", len(s), s) } func quoteServerString(s string) string { return fmt.Sprintf("{%d}\r\n%s", len(s), s) } // turn LF into CRLF line endeings and substitute passed key-value string pairs func fixClientOutput(s string, replacements ...string) string { return strings.NewReplacer(append(replacements, "\n", "\r\n")...).Replace(s) } type fakeConn struct { io.ReadWriter buf bytes.Buffer w *bufio.Writer } func newFakeConn(s string) *fakeConn { f := &fakeConn{} r := bufio.NewReader(strings.NewReader(s)) f.w = bufio.NewWriter(&f.buf) f.ReadWriter = bufio.NewReadWriter(r, f.w) return f } func (f *fakeConn) Close() error { return nil } func (f *fakeConn) LocalAddr() net.Addr { return nil } func (f *fakeConn) RemoteAddr() net.Addr { return nil } func (f *fakeConn) SetDeadline(time.Time) error { return nil } func (f *fakeConn) SetReadDeadline(time.Time) error { return nil } func (f *fakeConn) SetWriteDeadline(time.Time) error { return nil } func (f *fakeConn) Written() []byte { f.w.Flush() return f.buf.Bytes() } // basic test of net-unicode validation func TestNetUnicode(t *testing.T) { if !managesieve.IsNetUnicode("abc\u00a9") { t.Fatalf("expected valid net-unicode") } if managesieve.IsNetUnicode("a\tbc") { t.Fatalf("expected invalid net-unicode") } if managesieve.IsNetUnicode("a\u0080bc") { t.Fatalf("expected invalid net-unicode") } if managesieve.IsNetUnicode("a\u2028bc") { t.Fatalf("expected invalid net-unicode") } } var validScript string = `redirect "111@example.net"; if size :under 10k { redirect "mobile@cell.example.com"; } if envelope :contains "to" "tmartin+lists" { redirect "lists@groups.example.com"; } ` var expectedWarnings = "line 8: server redirect action limit is 2, this redirect might be ignored" // basic functionality var basicServer string = `"IMPlemENTATION" "Example1 ManageSieved v001" "SASl" "PLAIN DIGEST-MD5 GSSAPI" "SIeVE" "fileinto vacation" "StaRTTLS" "NOTIFY" "xmpp mailto" "MAXREdIRECTS" "5" "VERSION" "1.0" OK OK "IMPlemENTATION" "Example1 ManageSieved v001" "SASl" "PLAIN DIGEST-MD5 GSSAPI" "SIeVE" "fileinto vacation" "StaRTTLS" "NOTIFY" "xmpp mailto" "MAXREdIRECTS" "5" "VERSION" "1.0" OK OK (WARNINGS) "` + expectedWarnings + `" OK OK (WARNINGS) "` + expectedWarnings + `" OK "default" ACTIVE OK ` + quoteServerString(validScript) + ` OK OK OK OK OK OK ` var basicExpectedOutput string = fixClientOutput(`AUTHENTICATE "PLAIN" "AGZvbwBTM2NSM1Q=" CAPABILITY CHECKSCRIPT @quotedScript@ HAVESPACE {7+} default @scriptLength@ PUTSCRIPT {7+} default @quotedScript@ SETACTIVE {7+} default LISTSCRIPTS GETSCRIPT {7+} default SETACTIVE {0+} RENAMESCRIPT {7+} default {4+} test DELETESCRIPT {4+} test NOOP LOGOUT `, "@quotedScript@", quoteClientString(validScript), "@scriptLength@", fmt.Sprint(len(validScript))) func TestBasicFunctionality(t *testing.T) { conn := newFakeConn(basicServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } if !c.SupportsRFC5804() { t.Fatal("RFC5804 support unexpectedly not detected") } if !c.SupportsTLS() { t.Fatal("STARTTLS support unexpectedly not detected") } if ext := c.Extensions(); strings.Join(ext, " ") != "fileinto vacation" { t.Fatalf("expected extensions: [fileinto vacation], got: %v", ext) } if notify := c.NotifyMethods(); strings.Join(notify, " ") != "xmpp mailto" { t.Fatalf("expected notify methods: [xmpp mailto], got: %v", notify) } if redir := c.MaxRedirects(); redir != 5 { t.Fatalf("expected max redirects: 5 got: %d", redir) } if strings.Join(c.SASLMechanisms(), " ") != "PLAIN DIGEST-MD5 GSSAPI" { t.Fatal("failed check SASL methods") } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") if err := c.Authenticate(auth); err != nil { t.Fatalf("plain authentication failed: %s", err) } if warnings, err := c.CheckScript(validScript); err != nil { t.Fatalf("CHECKSCRIPT failed: %s", err) } else if warnings != expectedWarnings { t.Fatalf("CHECKSCRIPT expected: %s, got %s", warnings, expectedWarnings) } if ok, err := c.HaveSpace("default", int64(len(validScript))); err != nil { t.Fatalf("HAVESPACE failed: %s", err) } else if !ok { t.Fatal("HaveSpace unexpectedly returned false") } if warnings, err := c.PutScript("default", validScript); err != nil { t.Fatalf("PUTSCRIPT failed: %s", err) } else if warnings != expectedWarnings { t.Fatalf("PUTSCRIPT expected: %s, got %s", warnings, expectedWarnings) } if err = c.ActivateScript("default"); err != nil { t.Fatalf("SETACTIVE failed: %s", err) } if scripts, active, err := c.ListScripts(); err != nil { t.Fatalf("LISTSCRIPTS failed: %s", err) } else if active != "default" { t.Fatalf("failed to get active script") } else if len(scripts) != 1 || scripts[0] != "default" { t.Fatal("failed to get scripts") } if recievedScript, err := c.GetScript("default"); err != nil { t.Fatalf("GETSCRIPT failed: %s", err) } else if recievedScript != validScript { t.Fatalf("GETSCRIPT expected:\n%s\ngot:\n%s\n", validScript, recievedScript) } if err = c.ActivateScript(""); err != nil { t.Fatalf("SETACTIVE failed: %s", err) } if err = c.RenameScript("default", "test"); err != nil { t.Fatalf("RENAMESCRIPT failed: %s", err) } if err = c.DeleteScript("test"); err != nil { t.Fatalf("DELETESCRIPT failed: %s", err) } if err = c.Noop(); err != nil { t.Fatalf("NOOP failed unexpectedly: %s", err) } if err = c.Logout(); err != nil { t.Fatalf("failed to log out: %s", err) } clientOutput := string(conn.Written()) if clientOutput != basicExpectedOutput { t.Fatalf("expected:\n%s\ngot:\n%s\n", basicExpectedOutput, clientOutput) } } // unexpected EOF after length in literal string func TestUnexpectedEOFInLiteral(t *testing.T) { conn := newFakeConn(`"IMPLEMENTATION" {10}`) _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if err != io.ErrUnexpectedEOF { t.Fatalf("expected io.ErrUnexpectedEOF, got %T (%q)", err, err) } } // CR without LF after length in literal string func TestInvalidCRInLiteral(t *testing.T) { conn := newFakeConn("{2}\rXX\nOK\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // overlong literal string func TestOverlongLiteral(t *testing.T) { conn := newFakeConn(fmt.Sprintf("{%d}\r\n", managesieve.ReadLimit+1)) _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // string in place of an response code atom func TestInvalidResponseCode(t *testing.T) { conn := newFakeConn("NO (\"XXX\")\r\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // EOF after response code func TestResponseUnexpectedEOF(t *testing.T) { conn := newFakeConn("NO (XXX") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if err != io.ErrUnexpectedEOF { t.Fatalf("expected io.ErrUnexpectedEOF, got %T (%q)", err, err) } } // invalid atom in place of the response message string func TestInvalidResponseMessage(t *testing.T) { conn := newFakeConn("BYE XXX\r\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // trailing invalid atom after response message func TestResponseTrailingData(t *testing.T) { conn := newFakeConn("BYE \"XXX\" XXX\r\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // capabilities with atom instead of key string func TestCapabilitiesInvalidKey(t *testing.T) { conn := newFakeConn("IMPLEMENTATION \"Example\"\r\nOK\r\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // capabilities with atom instead of value string func TestCapabilitiesInvalidValue(t *testing.T) { conn := newFakeConn("\"IMPLEMENTATION\" Example\r\nOK\r\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } var unexpectedResponseServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" ` // unexpected EOF func TestUnexpectedEOF(t *testing.T) { conn := newFakeConn(unexpectedResponseServer) _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if err != io.ErrUnexpectedEOF { t.Fatalf("expected io.ErrUnexpectedEOF, got %T (%q)", err, err) } } // unexpected NO func TestUnexpectedNo(t *testing.T) { conn := newFakeConn(unexpectedResponseServer + "NO\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(*managesieve.ServerError); !ok { t.Fatalf("expected *managesieve.ServerError, got %T (%q)", err, err) } } // unexpected BYE func TestUnexpectedBye(t *testing.T) { conn := newFakeConn(unexpectedResponseServer + "BYE\n") _, err := managesieve.NewClient(conn, "imap.example.net") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(*managesieve.ConnClosedError); !ok { t.Fatalf("expected *managesieve.ConnClosedError, got %T (%q)", err, err) } } var invalidMaxRedirectsServer = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "MAxREDIRECTS" "-1" "VERSION" "1.0" OK ` func TestInvalidMaxRedirects(t *testing.T) { conn := newFakeConn(invalidMaxRedirectsServer) c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } if redir := c.MaxRedirects(); redir != 0 { t.Fatalf("invalid MAXREDIRECTS, expected 0, got %d", redir) } } var minimalServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK ` var haveSpaceServer string = minimalServer + `NO NO (QUOTA) NO (QUOTA/MAXSIZE) "Quota exceeded" ` // handling of unknown errors and quota exceeded errors func TestHaveSpace(t *testing.T) { conn := newFakeConn(haveSpaceServer) c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } var ok bool // unknown error response ok, err = c.HaveSpace("foo", 999999) if ok { t.Fatalf("expected failure due to unknown error response to HAVESPACE but succeeded") } if err == nil { t.Fatalf("expected error due to unknown error response to HAVESPACE") } if _, ok = err.(*managesieve.ServerError); !ok { t.Fatalf("expected *managesieve.ServerError, got %T (%q)", err, err) } // invalid script size _, err = c.HaveSpace("foo", -999999) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok = err.(managesieve.ProtocolError); !ok { t.Fatalf("expected managesieve.ProtocolError, got %T (%q)", err, err) } // quota exceeded ok, err = c.HaveSpace("foo", 999999) if ok { t.Fatalf("expected script to exceed quota") } if err != nil { t.Fatalf("failed to determine whether script exceeds quota: %s", err) } } // handling of errors in response to PUTSCRIPT commands func TestPutScript(t *testing.T) { conn := newFakeConn(minimalServer + "BYE\n") c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } // invalid script name _, err = c.PutScript("def\u2028ault", validScript) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ProtocolError); !ok { t.Fatalf("expected managesieve.ProtocolError, got %T (%q)", err, err) } // EOF during upload _, err = c.PutScript("default", validScript) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(*managesieve.ConnClosedError); !ok { t.Fatalf("expected *managesieve.ConnClosedError, got %T (%q)", err, err) } } // handling of a literal string with an invalid length func TestInvalidScriptLength(t *testing.T) { conn := newFakeConn(minimalServer + "{-1}\r\n") c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } _, err = c.GetScript("default") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // missing script in GETSCRIPT response func TestEmptyGetScript(t *testing.T) { conn := newFakeConn(minimalServer + "OK\n") c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } _, err = c.GetScript("default") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // invalid script list item which is not a string func TestListScriptsNonString(t *testing.T) { conn := newFakeConn(minimalServer + "XXX\nOK\n") c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } _, _, err = c.ListScripts() if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // script list item with an invalid atom func TestListScriptsInvalidAtom(t *testing.T) { conn := newFakeConn(minimalServer + "\"default\" XXX\nOK\n") c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } _, _, err = c.ListScripts() if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // script list with multiple active scripts var multipleActiveServer string = minimalServer + `"default" ACTIVE "alternative" ACTIVE "foo" OK ` func TestListScriptsMultipleActive(t *testing.T) { conn := newFakeConn(multipleActiveServer) c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } _, active, err := c.ListScripts() if err != nil { t.Fatalf("failed to get list of scripts: %s", err) } // although not allowed, the last script marked as active will be // returned if active != "alternative" { t.Fatalf("expected active script \"alternative\", got \"%s\"", active) } } // script list with an empty line var listScriptsEmptyLineServer string = minimalServer + `"default" ACTIVE "alternative" OK ` func TestListScriptsEmptyLine(t *testing.T) { conn := newFakeConn(listScriptsEmptyLineServer) c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } _, _, err = c.ListScripts() if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // script list item with trailing string func TestListScriptsTrailingData(t *testing.T) { conn := newFakeConn(minimalServer + "\"default\" ACTIVE \"alternative\"\nOK\n") c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } _, _, err = c.ListScripts() if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // renaming to an invalid name func TestRenameInvalidName(t *testing.T) { conn := newFakeConn(minimalServer + "NO\n") c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } err = c.RenameScript("default", "a\u2028bc") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ProtocolError); !ok { t.Fatalf("expected managesieve.ProtocolError, got %T (%q)", err, err) } } // legacy server which does not implement RFC 5804 var legacyServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" OK NO NO NO ` func TestLegacyServer(t *testing.T) { conn := newFakeConn(legacyServer) c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } err = c.Noop() if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.NotSupportedError); !ok { t.Fatalf("expected managesieve.NotSupportedError, got %T (%q)", err, err) } err = c.RenameScript("default", "alternative") if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.NotSupportedError); !ok { t.Fatalf("expected managesieve.NotSupportedError, got %T (%q)", err, err) } _, err = c.CheckScript(validScript) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.NotSupportedError); !ok { t.Fatalf("expected managesieve.NotSupportedError, got %T (%q)", err, err) } } // PLAIN authentication without TLS on non-localhost server func TestPlainAuthNoTLS(t *testing.T) { conn := newFakeConn(minimalServer) c, err := managesieve.NewClient(conn, "imap.example.net") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "imap.example.net") err = c.Authenticate(auth) if err == nil { t.Fatalf("error expected due to SASL PLAIN authentication without a TLS connection") } if err != managesieve.ErrPlainAuthTLSRequired { t.Fatalf("expected managesieve.ErrPlainAuthTLSRequired, got %T (%q)", err, err) } } // mismatch between actual and expected hostname func TestPlainAuthHostnameMismatch(t *testing.T) { conn := newFakeConn(minimalServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "imap.example.net") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(*managesieve.HostNameVerificationError); !ok { t.Fatalf("expected *managesieve.HostNameVerificationError, got %T (%q)", err, err) } } // authentication failure due to invalid credentials func TestPlainAuthDenied(t *testing.T) { conn := newFakeConn(minimalServer + "NO \"invalid username or password\"\n") c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") if err = c.Authenticate(auth); err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.AuthenticationError); !ok { t.Fatalf("expected managesieve.AuthenticationError, got %T (%q)", err, err) } } // missing SASL PLAIN authentication method var plainAuthMissingServer string = `"IMPLEMENTATION" "Example" "SASL" "TEST" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK ` func TestPlainMissingDenied(t *testing.T) { conn := newFakeConn(plainAuthMissingServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but authentication succeeded") } if err != managesieve.ErrPlainAuthNotSupported { t.Fatalf("expected managesieve.ErrPlainAuthNotSupported, got %T (%q)", err, err) } } // custom SASL authentication method handling with a challenge-response exchange var customAuthServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN TEST" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK "cXV1eCxnYXJwbHk=" "d2FsZG8sZnJlZA==" OK (SASL "eHl6enkgdGh1ZA==") "IMPLEMENTATION" "Example" "SASL" "PLAIN TEST" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK ` var ( ErrTestAuthNotSupported = errors.New("the server does not support TEST authentication") ErrTestAuthFailed = errors.New("client authentication failed") ) type testAuth struct { WantError bool i int } func (a *testAuth) Start(server *managesieve.ServerInfo) (string, []byte, error) { if !server.HaveAuth("TEST") { return "TEST", nil, ErrTestAuthNotSupported } return "TEST", []byte("baz,qux"), nil } func (a *testAuth) Next(challenge []byte, more bool) ([]byte, error) { if a.i == 0 && a.WantError { return nil, ErrTestAuthFailed } var resp []byte if !more { resp = []byte("plugh,xyzzy") } else { a.i++ resp = []byte(fmt.Sprintf("qux=%d", a.i)) } return resp, nil } // authentication using a custom authentication method func TestCustomAuth(t *testing.T) { conn := newFakeConn(customAuthServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := &testAuth{} if err = c.Authenticate(auth); err != nil { t.Fatalf("failed to authenticate using custom authentication method: %s", err) } } // requesting a non-existent SASL authentication method func TestNonexistentAuth(t *testing.T) { conn := newFakeConn(minimalServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := &testAuth{} err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if err != ErrTestAuthNotSupported { t.Fatalf("expected ErrAuthMethodNotSupported, got %T (%q)", err, err) } } // SASL authentication method aborted by the client var abortAuthServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN TEST" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK "cXV1eCxnYXJwbHk=" NO "aborted by client" ` // handle error raised by client-side custom authentication handler during // challenge-response exchange and abort authentication func TestAbortAuth(t *testing.T) { conn := newFakeConn(abortAuthServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := &testAuth{WantError: true} err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.AuthenticationError); !ok { t.Fatalf("expected managesieve.AuthenticationError, got %T (%q)", err, err) } } // custom SASL authentication method handling with a challenge which is not a // bas64-encoded string var corruptAuthServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN TEST" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK "*****" ` func TestCustomAuthCorrupt(t *testing.T) { conn := newFakeConn(corruptAuthServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := &testAuth{} err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // response to aborted SASL authentication is not NO var invalidAuthAbortServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN TEST" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK "cXV1eCxnYXJwbHk=" OK ` func TestInvalidAuthAbort(t *testing.T) { conn := newFakeConn(invalidAuthAbortServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := &testAuth{WantError: true} err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ProtocolError); !ok { t.Fatalf("expected managesieve.ProtocolError, got %T (%q)", err, err) } } // invalid response to SASL authentication attempt var invalidAuthServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK XXX ` func TestInvalidAuthResponse(t *testing.T) { conn := newFakeConn(invalidAuthServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // invalid trailing argument after first SASL response code argument var trailingSASLResponseArgServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK OK (SASL "Zm9v" XXX) ` func TestTrailingSASLResponseArg(t *testing.T) { conn := newFakeConn(trailingSASLResponseArgServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // trailing argument after first SASL response code argument var trailingSASLResonseArgServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK OK (SASL "Zm9v" "bar") ` func TestTrailingSASLResonseArg(t *testing.T) { conn := newFakeConn(trailingSASLResonseArgServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // SASL response code argument is not base64-encoded var invalidSASLResponseArgServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK OK (SASL "*****") ` func TestInvalidSASLResponseArg(t *testing.T) { conn := newFakeConn(invalidSASLResponseArgServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.ParserError); !ok { t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err) } } // argument passed with SASL response code rejected by client authentication // handler var saslResponseRejectedServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN TEST" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK OK (SASL "Zm9v") ` func TestSASLResponseRejected(t *testing.T) { conn := newFakeConn(saslResponseRejectedServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := &testAuth{WantError: true} if err = c.Authenticate(auth); err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(managesieve.AuthenticationError); !ok { t.Fatalf("expected managesieve.AuthenticationError, got %T (%q)", err, err) } } // CAPABILITIES command after authentication failed var authCapabilitiesFailedServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK OK NO ` func TestAuthCapabilitiesFailed(t *testing.T) { conn := newFakeConn(authCapabilitiesFailedServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(*managesieve.ServerError); !ok { t.Fatalf("expected *managesieve.ServerError, got %T (%q)", err, err) } } // BYE in response to SASL authentication attempt var authByeServer string = `"IMPLEMENTATION" "Example" "SASL" "PLAIN" "SIEVE" "fileinto vacation" "StARTTLS" "VERSION" "1.0" OK BYE "authentication denied" ` func TestAuthBye(t *testing.T) { conn := newFakeConn(authByeServer) c, err := managesieve.NewClient(conn, "localhost") if err != nil { t.Fatalf("failed to create client: %s", err) } auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost") err = c.Authenticate(auth) if err == nil { t.Fatalf("expected error but succeeded") } if _, ok := err.(*managesieve.ConnClosedError); !ok { t.Fatalf("expected *managesieve.ConnClosedError, got %T (%q)", err, err) } }