comparison managesieve_test.go @ 0:6369453d47a3

Initial revision
author Guido Berhoerster <guido+managesieve@berhoerster.name>
date Thu, 15 Oct 2020 09:11:05 +0200
parents
children f9bb517e9447
comparison
equal deleted inserted replaced
-1:000000000000 0:6369453d47a3
1 // Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 package managesieve_test
23
24 import (
25 "bufio"
26 "bytes"
27 "errors"
28 "fmt"
29 "io"
30 "net"
31 "strings"
32 "testing"
33 "time"
34
35 "go.guido-berhoerster.org/managesieve"
36 )
37
38 func quoteClientString(s string) string {
39 return fmt.Sprintf("{%d+}\r\n%s", len(s), s)
40 }
41
42 func quoteServerString(s string) string {
43 return fmt.Sprintf("{%d}\r\n%s", len(s), s)
44 }
45
46 // turn LF into CRLF line endeings and substitute passed key-value string pairs
47 func fixClientOutput(s string, replacements ...string) string {
48 return strings.NewReplacer(append(replacements,
49 "\n", "\r\n")...).Replace(s)
50 }
51
52 type fakeConn struct {
53 io.ReadWriter
54 buf bytes.Buffer
55 w *bufio.Writer
56 }
57
58 func newFakeConn(s string) *fakeConn {
59 f := &fakeConn{}
60 r := bufio.NewReader(strings.NewReader(s))
61 f.w = bufio.NewWriter(&f.buf)
62 f.ReadWriter = bufio.NewReadWriter(r, f.w)
63 return f
64 }
65
66 func (f *fakeConn) Close() error {
67 return nil
68 }
69
70 func (f *fakeConn) LocalAddr() net.Addr {
71 return nil
72 }
73
74 func (f *fakeConn) RemoteAddr() net.Addr {
75 return nil
76 }
77
78 func (f *fakeConn) SetDeadline(time.Time) error {
79 return nil
80 }
81
82 func (f *fakeConn) SetReadDeadline(time.Time) error {
83 return nil
84 }
85
86 func (f *fakeConn) SetWriteDeadline(time.Time) error {
87 return nil
88 }
89
90 func (f *fakeConn) Written() []byte {
91 f.w.Flush()
92 return f.buf.Bytes()
93 }
94
95 // basic test of net-unicode validation
96 func TestNetUnicode(t *testing.T) {
97 if !managesieve.IsNetUnicode("abc\u00a9") {
98 t.Fatalf("expected valid net-unicode")
99 }
100 if managesieve.IsNetUnicode("a\tbc") {
101 t.Fatalf("expected invalid net-unicode")
102 }
103 if managesieve.IsNetUnicode("a\u0080bc") {
104 t.Fatalf("expected invalid net-unicode")
105 }
106 if managesieve.IsNetUnicode("a\u2028bc") {
107 t.Fatalf("expected invalid net-unicode")
108 }
109 }
110
111 var validScript string = `redirect "111@example.net";
112
113 if size :under 10k {
114 redirect "mobile@cell.example.com";
115 }
116
117 if envelope :contains "to" "tmartin+lists" {
118 redirect "lists@groups.example.com";
119 }
120 `
121
122 // basic functionality
123 var basicServer string = `"IMPlemENTATION" "Example1 ManageSieved v001"
124 "SASl" "PLAIN DIGEST-MD5 GSSAPI"
125 "SIeVE" "fileinto vacation"
126 "StaRTTLS"
127 "NOTIFY" "xmpp mailto"
128 "MAXREdIRECTS" "5"
129 "VERSION" "1.0"
130 OK
131 OK
132 "IMPlemENTATION" "Example1 ManageSieved v001"
133 "SASl" "PLAIN DIGEST-MD5 GSSAPI"
134 "SIeVE" "fileinto vacation"
135 "StaRTTLS"
136 "NOTIFY" "xmpp mailto"
137 "MAXREdIRECTS" "5"
138 "VERSION" "1.0"
139 OK
140 OK (WARNINGS) "line 8: server redirect action limit is 2, this redirect might be ignored"
141 OK
142 OK (WARNINGS) "line 8: server redirect action limit is 2, this redirect might be ignored"
143 OK
144 "default" ACTIVE
145 OK
146 ` + quoteServerString(validScript) + `
147 OK
148 OK
149 OK
150 OK
151 OK
152 OK
153 `
154
155 var basicExpectedOutput string = fixClientOutput(`AUTHENTICATE "PLAIN" "AGZvbwBTM2NSM1Q="
156 CAPABILITY
157 CHECKSCRIPT @quotedScript@
158 HAVESPACE {7+}
159 default @scriptLength@
160 PUTSCRIPT {7+}
161 default @quotedScript@
162 SETACTIVE {7+}
163 default
164 LISTSCRIPTS
165 GETSCRIPT {7+}
166 default
167 SETACTIVE {0+}
168
169 RENAMESCRIPT {7+}
170 default {4+}
171 test
172 DELETESCRIPT {4+}
173 test
174 NOOP
175 LOGOUT
176 `,
177 "@quotedScript@", quoteClientString(validScript),
178 "@scriptLength@", fmt.Sprint(len(validScript)))
179
180 func TestBasicFunctionality(t *testing.T) {
181 conn := newFakeConn(basicServer)
182 c, err := managesieve.NewClient(conn, "localhost")
183 if err != nil {
184 t.Fatalf("failed to create client: %s", err)
185 }
186
187 if !c.SupportsRFC5804() {
188 t.Fatal("RFC5804 support unexpectedly not detected")
189 }
190
191 if !c.SupportsTLS() {
192 t.Fatal("STARTTLS support unexpectedly not detected")
193 }
194
195 if ext := c.Extensions(); strings.Join(ext, " ") !=
196 "fileinto vacation" {
197 t.Fatalf("expected extensions: [fileinto vacation], got: %v",
198 ext)
199 }
200
201 if notify := c.NotifyMethods(); strings.Join(notify, " ") !=
202 "xmpp mailto" {
203 t.Fatalf("expected notify methods: [xmpp mailto], got: %v",
204 notify)
205 }
206
207 if redir := c.MaxRedirects(); redir != 5 {
208 t.Fatalf("expected max redirects: 5 got: %d", redir)
209 }
210
211 if strings.Join(c.SASLMechanisms(), " ") != "PLAIN DIGEST-MD5 GSSAPI" {
212 t.Fatal("failed check SASL methods")
213 }
214
215 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
216 if err := c.Authenticate(auth); err != nil {
217 t.Fatalf("plain authentication failed: %s", err)
218 }
219
220 if err = c.CheckScript(validScript); err != nil {
221 t.Fatalf("CHECKSCRIPT failed: %s", err)
222 }
223
224 if ok, err := c.HaveSpace("default", int64(len(validScript))); err != nil {
225 t.Fatalf("HAVESPACE failed: %s", err)
226 } else if !ok {
227 t.Fatal("HaveSpace unexpectedly returned false")
228 }
229
230 if err = c.PutScript("default", validScript); err != nil {
231 t.Fatalf("PUTSCRIPT failed: %s", err)
232 }
233
234 if err = c.ActivateScript("default"); err != nil {
235 t.Fatalf("SETACTIVE failed: %s", err)
236 }
237
238 if scripts, active, err := c.ListScripts(); err != nil {
239 t.Fatalf("LISTSCRIPTS failed: %s", err)
240 } else if active != "default" {
241 t.Fatalf("failed to get active script")
242 } else if len(scripts) != 1 || scripts[0] != "default" {
243 t.Fatal("failed to get scripts")
244 }
245
246 if recievedScript, err := c.GetScript("default"); err != nil {
247 t.Fatalf("GETSCRIPT failed: %s", err)
248 } else if recievedScript != validScript {
249 t.Fatalf("GETSCRIPT expected:\n%s\ngot:\n%s\n",
250 validScript, recievedScript)
251 }
252
253 if err = c.ActivateScript(""); err != nil {
254 t.Fatalf("SETACTIVE failed: %s", err)
255 }
256
257 if err = c.RenameScript("default", "test"); err != nil {
258 t.Fatalf("RENAMESCRIPT failed: %s", err)
259 }
260
261 if err = c.DeleteScript("test"); err != nil {
262 t.Fatalf("DELETESCRIPT failed: %s", err)
263 }
264
265 if err = c.Noop(); err != nil {
266 t.Fatalf("NOOP failed unexpectedly: %s", err)
267 }
268
269 if err = c.Logout(); err != nil {
270 t.Fatalf("failed to log out: %s", err)
271 }
272
273 clientOutput := string(conn.Written())
274 if clientOutput != basicExpectedOutput {
275 t.Fatalf("expected:\n%s\ngot:\n%s\n", basicExpectedOutput,
276 clientOutput)
277 }
278 }
279
280 // unexpected EOF after length in literal string
281 func TestUnexpectedEOFInLiteral(t *testing.T) {
282 conn := newFakeConn(`"IMPLEMENTATION" {10}`)
283 _, err := managesieve.NewClient(conn, "imap.example.net")
284 if err == nil {
285 t.Fatalf("expected error but succeeded")
286 }
287 if err != io.ErrUnexpectedEOF {
288 t.Fatalf("expected io.ErrUnexpectedEOF, got %T (%q)", err, err)
289 }
290 }
291
292 // CR without LF after length in literal string
293 func TestInvalidCRInLiteral(t *testing.T) {
294 conn := newFakeConn("{2}\rXX\nOK\n")
295 _, err := managesieve.NewClient(conn, "imap.example.net")
296 if err == nil {
297 t.Fatalf("expected error but succeeded")
298 }
299 if _, ok := err.(managesieve.ParserError); !ok {
300 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
301 err)
302 }
303 }
304
305 // overlong literal string
306 func TestOverlongLiteral(t *testing.T) {
307 conn := newFakeConn(fmt.Sprintf("{%d}\r\n", managesieve.ReadLimit+1))
308 _, err := managesieve.NewClient(conn, "imap.example.net")
309 if err == nil {
310 t.Fatalf("expected error but succeeded")
311 }
312 if _, ok := err.(managesieve.ParserError); !ok {
313 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
314 err)
315 }
316 }
317
318 // string in place of an response code atom
319 func TestInvalidResponseCode(t *testing.T) {
320 conn := newFakeConn("NO (\"XXX\")\r\n")
321 _, err := managesieve.NewClient(conn, "imap.example.net")
322 if err == nil {
323 t.Fatalf("expected error but succeeded")
324 }
325 if _, ok := err.(managesieve.ParserError); !ok {
326 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
327 err)
328 }
329 }
330
331 // EOF after response code
332 func TestResponseUnexpectedEOF(t *testing.T) {
333 conn := newFakeConn("NO (XXX")
334 _, err := managesieve.NewClient(conn, "imap.example.net")
335 if err == nil {
336 t.Fatalf("expected error but succeeded")
337 }
338 if err != io.ErrUnexpectedEOF {
339 t.Fatalf("expected io.ErrUnexpectedEOF, got %T (%q)", err, err)
340 }
341 }
342
343 // invalid atom in place of the response message string
344 func TestInvalidResponseMessage(t *testing.T) {
345 conn := newFakeConn("BYE XXX\r\n")
346 _, err := managesieve.NewClient(conn, "imap.example.net")
347 if err == nil {
348 t.Fatalf("expected error but succeeded")
349 }
350 if _, ok := err.(managesieve.ParserError); !ok {
351 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
352 err)
353 }
354 }
355
356 // trailing invalid atom after response message
357 func TestResponseTrailingData(t *testing.T) {
358 conn := newFakeConn("BYE \"XXX\" XXX\r\n")
359 _, err := managesieve.NewClient(conn, "imap.example.net")
360 if err == nil {
361 t.Fatalf("expected error but succeeded")
362 }
363 if _, ok := err.(managesieve.ParserError); !ok {
364 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
365 err)
366 }
367 }
368
369 // capabilities with atom instead of key string
370 func TestCapabilitiesInvalidKey(t *testing.T) {
371 conn := newFakeConn("IMPLEMENTATION \"Example\"\r\nOK\r\n")
372 _, err := managesieve.NewClient(conn, "imap.example.net")
373 if err == nil {
374 t.Fatalf("expected error but succeeded")
375 }
376 if _, ok := err.(managesieve.ParserError); !ok {
377 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
378 err)
379 }
380 }
381
382 // capabilities with atom instead of value string
383 func TestCapabilitiesInvalidValue(t *testing.T) {
384 conn := newFakeConn("\"IMPLEMENTATION\" Example\r\nOK\r\n")
385 _, err := managesieve.NewClient(conn, "imap.example.net")
386 if err == nil {
387 t.Fatalf("expected error but succeeded")
388 }
389 if _, ok := err.(managesieve.ParserError); !ok {
390 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
391 err)
392 }
393 }
394
395 var unexpectedResponseServer string = `"IMPLEMENTATION" "Example"
396 "SASL" "PLAIN"
397 "SIEVE" "fileinto vacation"
398 "StARTTLS"
399 "VERSION" "1.0"
400 `
401
402 // unexpected EOF
403 func TestUnexpectedEOF(t *testing.T) {
404 conn := newFakeConn(unexpectedResponseServer)
405 _, err := managesieve.NewClient(conn, "imap.example.net")
406 if err == nil {
407 t.Fatalf("expected error but succeeded")
408 }
409 if err != io.ErrUnexpectedEOF {
410 t.Fatalf("expected io.ErrUnexpectedEOF, got %T (%q)", err, err)
411 }
412 }
413
414 // unexpected NO
415 func TestUnexpectedNo(t *testing.T) {
416 conn := newFakeConn(unexpectedResponseServer + "NO\n")
417 _, err := managesieve.NewClient(conn, "imap.example.net")
418 if err == nil {
419 t.Fatalf("expected error but succeeded")
420 }
421 if _, ok := err.(*managesieve.ServerError); !ok {
422 t.Fatalf("expected *managesieve.ServerError, got %T (%q)", err,
423 err)
424 }
425 }
426
427 // unexpected BYE
428 func TestUnexpectedBye(t *testing.T) {
429 conn := newFakeConn(unexpectedResponseServer + "BYE\n")
430 _, err := managesieve.NewClient(conn, "imap.example.net")
431 if err == nil {
432 t.Fatalf("expected error but succeeded")
433 }
434 if _, ok := err.(*managesieve.ConnClosedError); !ok {
435 t.Fatalf("expected *managesieve.ConnClosedError, got %T (%q)",
436 err, err)
437 }
438 }
439
440 var invalidMaxRedirectsServer = `"IMPLEMENTATION" "Example"
441 "SASL" "PLAIN"
442 "SIEVE" "fileinto vacation"
443 "StARTTLS"
444 "MAxREDIRECTS" "-1"
445 "VERSION" "1.0"
446 OK
447 `
448
449 func TestInvalidMaxRedirects(t *testing.T) {
450 conn := newFakeConn(invalidMaxRedirectsServer)
451 c, err := managesieve.NewClient(conn, "imap.example.net")
452 if err != nil {
453 t.Fatalf("failed to create client: %s", err)
454 }
455
456 if redir := c.MaxRedirects(); redir != 0 {
457 t.Fatalf("invalid MAXREDIRECTS, expected 0, got %d", redir)
458 }
459 }
460
461 var minimalServer string = `"IMPLEMENTATION" "Example"
462 "SASL" "PLAIN"
463 "SIEVE" "fileinto vacation"
464 "StARTTLS"
465 "VERSION" "1.0"
466 OK
467 `
468
469 var haveSpaceServer string = minimalServer + `NO
470 NO (QUOTA)
471 NO (QUOTA/MAXSIZE) "Quota exceeded"
472 `
473
474 // handling of unknown errors and quota exceeded errors
475 func TestHaveSpace(t *testing.T) {
476 conn := newFakeConn(haveSpaceServer)
477 c, err := managesieve.NewClient(conn, "imap.example.net")
478 if err != nil {
479 t.Fatalf("failed to create client: %s", err)
480 }
481
482 var ok bool
483 // unknown error response
484 ok, err = c.HaveSpace("foo", 999999)
485 if ok {
486 t.Fatalf("expected failure due to unknown error response to HAVESPACE but succeeded")
487 }
488 if err == nil {
489 t.Fatalf("expected error due to unknown error response to HAVESPACE")
490 }
491 if _, ok = err.(*managesieve.ServerError); !ok {
492 t.Fatalf("expected *managesieve.ServerError, got %T (%q)", err,
493 err)
494 }
495
496 // invalid script size
497 _, err = c.HaveSpace("foo", -999999)
498 if err == nil {
499 t.Fatalf("expected error but succeeded")
500 }
501 if _, ok = err.(managesieve.ProtocolError); !ok {
502 t.Fatalf("expected managesieve.ProtocolError, got %T (%q)", err,
503 err)
504 }
505
506 // quota exceeded
507 ok, err = c.HaveSpace("foo", 999999)
508 if ok {
509 t.Fatalf("expected script to exceed quota")
510 }
511 if err != nil {
512 t.Fatalf("failed to determine whether script exceeds quota: %s",
513 err)
514 }
515 }
516
517 // handling of errors in response to PUTSCRIPT commands
518 func TestPutScript(t *testing.T) {
519 conn := newFakeConn(minimalServer + "BYE\n")
520 c, err := managesieve.NewClient(conn, "imap.example.net")
521 if err != nil {
522 t.Fatalf("failed to create client: %s", err)
523 }
524
525 // invalid script name
526 err = c.PutScript("def\u2028ault", validScript)
527 if err == nil {
528 t.Fatalf("expected error but succeeded")
529 }
530 if _, ok := err.(managesieve.ProtocolError); !ok {
531 t.Fatalf("expected managesieve.ProtocolError, got %T (%q)", err,
532 err)
533 }
534
535 // EOF during upload
536 err = c.PutScript("default", validScript)
537 if err == nil {
538 t.Fatalf("expected error but succeeded")
539 }
540 if _, ok := err.(*managesieve.ConnClosedError); !ok {
541 t.Fatalf("expected *managesieve.ConnClosedError, got %T (%q)",
542 err, err)
543 }
544 }
545
546 // handling of a literal string with an invalid length
547 func TestInvalidScriptLength(t *testing.T) {
548 conn := newFakeConn(minimalServer + "{-1}\r\n")
549 c, err := managesieve.NewClient(conn, "imap.example.net")
550 if err != nil {
551 t.Fatalf("failed to create client: %s", err)
552 }
553
554 _, err = c.GetScript("default")
555 if err == nil {
556 t.Fatalf("expected error but succeeded")
557 }
558 if _, ok := err.(managesieve.ParserError); !ok {
559 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
560 err)
561 }
562 }
563
564 // missing script in GETSCRIPT response
565 func TestEmptyGetScript(t *testing.T) {
566 conn := newFakeConn(minimalServer + "OK\n")
567 c, err := managesieve.NewClient(conn, "imap.example.net")
568 if err != nil {
569 t.Fatalf("failed to create client: %s", err)
570 }
571
572 _, err = c.GetScript("default")
573 if err == nil {
574 t.Fatalf("expected error but succeeded")
575 }
576 if _, ok := err.(managesieve.ParserError); !ok {
577 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
578 err)
579 }
580 }
581
582 // invalid script list item which is not a string
583 func TestListScriptsNonString(t *testing.T) {
584 conn := newFakeConn(minimalServer + "XXX\nOK\n")
585 c, err := managesieve.NewClient(conn, "imap.example.net")
586 if err != nil {
587 t.Fatalf("failed to create client: %s", err)
588 }
589
590 _, _, err = c.ListScripts()
591 if err == nil {
592 t.Fatalf("expected error but succeeded")
593 }
594 if _, ok := err.(managesieve.ParserError); !ok {
595 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
596 err)
597 }
598 }
599
600 // script list item with an invalid atom
601 func TestListScriptsInvalidAtom(t *testing.T) {
602 conn := newFakeConn(minimalServer + "\"default\" XXX\nOK\n")
603 c, err := managesieve.NewClient(conn, "imap.example.net")
604 if err != nil {
605 t.Fatalf("failed to create client: %s", err)
606 }
607
608 _, _, err = c.ListScripts()
609 if err == nil {
610 t.Fatalf("expected error but succeeded")
611 }
612 if _, ok := err.(managesieve.ParserError); !ok {
613 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
614 err)
615 }
616 }
617
618 // script list with multiple active scripts
619 var multipleActiveServer string = minimalServer + `"default" ACTIVE
620 "alternative" ACTIVE
621 "foo"
622 OK
623 `
624
625 func TestListScriptsMultipleActive(t *testing.T) {
626 conn := newFakeConn(multipleActiveServer)
627 c, err := managesieve.NewClient(conn, "imap.example.net")
628 if err != nil {
629 t.Fatalf("failed to create client: %s", err)
630 }
631
632 _, active, err := c.ListScripts()
633 if err != nil {
634 t.Fatalf("failed to get list of scripts: %s", err)
635 }
636 // although not allowed, the last script marked as active will be
637 // returned
638 if active != "alternative" {
639 t.Fatalf("expected active script \"alternative\", got \"%s\"",
640 active)
641 }
642 }
643
644 // script list with an empty line
645 var listScriptsEmptyLineServer string = minimalServer + `"default" ACTIVE
646 "alternative"
647
648 OK
649 `
650
651 func TestListScriptsEmptyLine(t *testing.T) {
652 conn := newFakeConn(listScriptsEmptyLineServer)
653 c, err := managesieve.NewClient(conn, "imap.example.net")
654 if err != nil {
655 t.Fatalf("failed to create client: %s", err)
656 }
657
658 _, _, err = c.ListScripts()
659 if err == nil {
660 t.Fatalf("expected error but succeeded")
661 }
662 if _, ok := err.(managesieve.ParserError); !ok {
663 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
664 err)
665 }
666 }
667
668 // script list item with trailing string
669 func TestListScriptsTrailingData(t *testing.T) {
670 conn := newFakeConn(minimalServer +
671 "\"default\" ACTIVE \"alternative\"\nOK\n")
672 c, err := managesieve.NewClient(conn, "imap.example.net")
673 if err != nil {
674 t.Fatalf("failed to create client: %s", err)
675 }
676
677 _, _, err = c.ListScripts()
678 if err == nil {
679 t.Fatalf("expected error but succeeded")
680 }
681 if _, ok := err.(managesieve.ParserError); !ok {
682 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err,
683 err)
684 }
685 }
686
687 // renaming to an invalid name
688 func TestRenameInvalidName(t *testing.T) {
689 conn := newFakeConn(minimalServer + "NO\n")
690 c, err := managesieve.NewClient(conn, "imap.example.net")
691 if err != nil {
692 t.Fatalf("failed to create client: %s", err)
693 }
694
695 err = c.RenameScript("default", "a\u2028bc")
696 if err == nil {
697 t.Fatalf("expected error but succeeded")
698 }
699 if _, ok := err.(managesieve.ProtocolError); !ok {
700 t.Fatalf("expected managesieve.ProtocolError, got %T (%q)",
701 err, err)
702 }
703 }
704
705 // legacy server which does not implement RFC 5804
706 var legacyServer string = `"IMPLEMENTATION" "Example"
707 "SASL" "PLAIN"
708 "SIEVE" "fileinto vacation"
709 "StARTTLS"
710 OK
711 NO
712 NO
713 NO
714 `
715
716 func TestLegacyServer(t *testing.T) {
717 conn := newFakeConn(legacyServer)
718 c, err := managesieve.NewClient(conn, "imap.example.net")
719 if err != nil {
720 t.Fatalf("failed to create client: %s", err)
721 }
722
723 err = c.Noop()
724 if err == nil {
725 t.Fatalf("expected error but succeeded")
726 }
727 if _, ok := err.(managesieve.NotSupportedError); !ok {
728 t.Fatalf("expected managesieve.NotSupportedError, got %T (%q)",
729 err, err)
730 }
731
732 err = c.RenameScript("default", "alternative")
733 if err == nil {
734 t.Fatalf("expected error but succeeded")
735 }
736 if _, ok := err.(managesieve.NotSupportedError); !ok {
737 t.Fatalf("expected managesieve.NotSupportedError, got %T (%q)",
738 err, err)
739 }
740
741 err = c.CheckScript(validScript)
742 if err == nil {
743 t.Fatalf("expected error but succeeded")
744 }
745 if _, ok := err.(managesieve.NotSupportedError); !ok {
746 t.Fatalf("expected managesieve.NotSupportedError, got %T (%q)",
747 err, err)
748 }
749 }
750
751 // PLAIN authentication without TLS on non-localhost server
752 func TestPlainAuthNoTLS(t *testing.T) {
753 conn := newFakeConn(minimalServer)
754 c, err := managesieve.NewClient(conn, "imap.example.net")
755 if err != nil {
756 t.Fatalf("failed to create client: %s", err)
757 }
758
759 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "imap.example.net")
760 err = c.Authenticate(auth)
761 if err == nil {
762 t.Fatalf("error expected due to SASL PLAIN authentication without a TLS connection")
763 }
764 if err != managesieve.ErrPlainAuthTLSRequired {
765 t.Fatalf("expected managesieve.ErrPlainAuthTLSRequired, got %T (%q)", err, err)
766 }
767 }
768
769 // mismatch between actual and expected hostname
770 func TestPlainAuthHostnameMismatch(t *testing.T) {
771 conn := newFakeConn(minimalServer)
772 c, err := managesieve.NewClient(conn, "localhost")
773 if err != nil {
774 t.Fatalf("failed to create client: %s", err)
775 }
776
777 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "imap.example.net")
778 err = c.Authenticate(auth)
779 if err == nil {
780 t.Fatalf("expected error but succeeded")
781 }
782 if _, ok := err.(*managesieve.HostNameVerificationError); !ok {
783 t.Fatalf("expected *managesieve.HostNameVerificationError, got %T (%q)", err, err)
784 }
785 }
786
787 // authentication failure due to invalid credentials
788 func TestPlainAuthDenied(t *testing.T) {
789 conn := newFakeConn(minimalServer +
790 "NO \"invalid username or password\"\n")
791 c, err := managesieve.NewClient(conn, "localhost")
792 if err != nil {
793 t.Fatalf("failed to create client: %s", err)
794 }
795
796 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
797 if err = c.Authenticate(auth); err == nil {
798 t.Fatalf("expected error but succeeded")
799 }
800 if _, ok := err.(managesieve.AuthenticationError); !ok {
801 t.Fatalf("expected managesieve.AuthenticationError, got %T (%q)", err, err)
802 }
803 }
804
805 // missing SASL PLAIN authentication method
806 var plainAuthMissingServer string = `"IMPLEMENTATION" "Example"
807 "SASL" "TEST"
808 "SIEVE" "fileinto vacation"
809 "StARTTLS"
810 "VERSION" "1.0"
811 OK
812 `
813
814 func TestPlainMissingDenied(t *testing.T) {
815 conn := newFakeConn(plainAuthMissingServer)
816 c, err := managesieve.NewClient(conn, "localhost")
817 if err != nil {
818 t.Fatalf("failed to create client: %s", err)
819 }
820
821 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
822 err = c.Authenticate(auth)
823 if err == nil {
824 t.Fatalf("expected error but authentication succeeded")
825 }
826 if err != managesieve.ErrPlainAuthNotSupported {
827 t.Fatalf("expected managesieve.ErrPlainAuthNotSupported, got %T (%q)", err, err)
828 }
829 }
830
831 // custom SASL authentication method handling with a challenge-response exchange
832 var customAuthServer string = `"IMPLEMENTATION" "Example"
833 "SASL" "PLAIN TEST"
834 "SIEVE" "fileinto vacation"
835 "StARTTLS"
836 "VERSION" "1.0"
837 OK
838 "cXV1eCxnYXJwbHk="
839 "d2FsZG8sZnJlZA=="
840 OK (SASL "eHl6enkgdGh1ZA==")
841 "IMPLEMENTATION" "Example"
842 "SASL" "PLAIN TEST"
843 "SIEVE" "fileinto vacation"
844 "StARTTLS"
845 "VERSION" "1.0"
846 OK
847 `
848
849 var (
850 ErrTestAuthNotSupported = errors.New("the server does not support TEST authentication")
851 ErrTestAuthFailed = errors.New("client authentication failed")
852 )
853
854 type testAuth struct {
855 WantError bool
856 i int
857 }
858
859 func (a *testAuth) Start(server *managesieve.ServerInfo) (string, []byte, error) {
860 if !server.HaveAuth("TEST") {
861 return "TEST", nil, ErrTestAuthNotSupported
862 }
863
864 return "TEST", []byte("baz,qux"), nil
865 }
866
867 func (a *testAuth) Next(challenge []byte, more bool) ([]byte, error) {
868 if a.i == 0 && a.WantError {
869 return nil, ErrTestAuthFailed
870 }
871
872 var resp []byte
873 if !more {
874 resp = []byte("plugh,xyzzy")
875 } else {
876 a.i++
877 resp = []byte(fmt.Sprintf("qux=%d", a.i))
878 }
879 return resp, nil
880 }
881
882 // authentication using a custom authentication method
883 func TestCustomAuth(t *testing.T) {
884 conn := newFakeConn(customAuthServer)
885 c, err := managesieve.NewClient(conn, "localhost")
886 if err != nil {
887 t.Fatalf("failed to create client: %s", err)
888 }
889
890 auth := &testAuth{}
891 if err = c.Authenticate(auth); err != nil {
892 t.Fatalf("failed to authenticate using custom authentication method: %s", err)
893 }
894 }
895
896 // requesting a non-existent SASL authentication method
897 func TestNonexistentAuth(t *testing.T) {
898 conn := newFakeConn(minimalServer)
899 c, err := managesieve.NewClient(conn, "localhost")
900 if err != nil {
901 t.Fatalf("failed to create client: %s", err)
902 }
903
904 auth := &testAuth{}
905 err = c.Authenticate(auth)
906 if err == nil {
907 t.Fatalf("expected error but succeeded")
908 }
909 if err != ErrTestAuthNotSupported {
910 t.Fatalf("expected ErrAuthMethodNotSupported, got %T (%q)",
911 err, err)
912 }
913 }
914
915 // SASL authentication method aborted by the client
916 var abortAuthServer string = `"IMPLEMENTATION" "Example"
917 "SASL" "PLAIN TEST"
918 "SIEVE" "fileinto vacation"
919 "StARTTLS"
920 "VERSION" "1.0"
921 OK
922 "cXV1eCxnYXJwbHk="
923 NO "aborted by client"
924 `
925
926 // handle error raised by client-side custom authentication handler during
927 // challenge-response exchange and abort authentication
928 func TestAbortAuth(t *testing.T) {
929 conn := newFakeConn(abortAuthServer)
930 c, err := managesieve.NewClient(conn, "localhost")
931 if err != nil {
932 t.Fatalf("failed to create client: %s", err)
933 }
934
935 auth := &testAuth{WantError: true}
936 err = c.Authenticate(auth)
937 if err == nil {
938 t.Fatalf("expected error but succeeded")
939 }
940 if _, ok := err.(managesieve.AuthenticationError); !ok {
941 t.Fatalf("expected managesieve.AuthenticationError, got %T (%q)", err, err)
942 }
943 }
944
945 // custom SASL authentication method handling with a challenge which is not a
946 // bas64-encoded string
947 var corruptAuthServer string = `"IMPLEMENTATION" "Example"
948 "SASL" "PLAIN TEST"
949 "SIEVE" "fileinto vacation"
950 "StARTTLS"
951 "VERSION" "1.0"
952 OK
953 "*****"
954 `
955
956 func TestCustomAuthCorrupt(t *testing.T) {
957 conn := newFakeConn(corruptAuthServer)
958 c, err := managesieve.NewClient(conn, "localhost")
959 if err != nil {
960 t.Fatalf("failed to create client: %s", err)
961 }
962
963 auth := &testAuth{}
964 err = c.Authenticate(auth)
965 if err == nil {
966 t.Fatalf("expected error but succeeded")
967 }
968 if _, ok := err.(managesieve.ParserError); !ok {
969 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err)
970 }
971 }
972
973 // response to aborted SASL authentication is not NO
974 var invalidAuthAbortServer string = `"IMPLEMENTATION" "Example"
975 "SASL" "PLAIN TEST"
976 "SIEVE" "fileinto vacation"
977 "StARTTLS"
978 "VERSION" "1.0"
979 OK
980 "cXV1eCxnYXJwbHk="
981 OK
982 `
983
984 func TestInvalidAuthAbort(t *testing.T) {
985 conn := newFakeConn(invalidAuthAbortServer)
986 c, err := managesieve.NewClient(conn, "localhost")
987 if err != nil {
988 t.Fatalf("failed to create client: %s", err)
989 }
990
991 auth := &testAuth{WantError: true}
992 err = c.Authenticate(auth)
993 if err == nil {
994 t.Fatalf("expected error but succeeded")
995 }
996 if _, ok := err.(managesieve.ProtocolError); !ok {
997 t.Fatalf("expected managesieve.ProtocolError, got %T (%q)", err, err)
998 }
999 }
1000
1001 // invalid response to SASL authentication attempt
1002 var invalidAuthServer string = `"IMPLEMENTATION" "Example"
1003 "SASL" "PLAIN"
1004 "SIEVE" "fileinto vacation"
1005 "StARTTLS"
1006 "VERSION" "1.0"
1007 OK
1008 XXX
1009 `
1010
1011 func TestInvalidAuthResponse(t *testing.T) {
1012 conn := newFakeConn(invalidAuthServer)
1013 c, err := managesieve.NewClient(conn, "localhost")
1014 if err != nil {
1015 t.Fatalf("failed to create client: %s", err)
1016 }
1017
1018 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
1019 err = c.Authenticate(auth)
1020 if err == nil {
1021 t.Fatalf("expected error but succeeded")
1022 }
1023 if _, ok := err.(managesieve.ParserError); !ok {
1024 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err)
1025 }
1026 }
1027
1028 // invalid trailing argument after first SASL response code argument
1029 var trailingSASLResponseArgServer string = `"IMPLEMENTATION" "Example"
1030 "SASL" "PLAIN"
1031 "SIEVE" "fileinto vacation"
1032 "StARTTLS"
1033 "VERSION" "1.0"
1034 OK
1035 OK (SASL "Zm9v" XXX)
1036 `
1037
1038 func TestTrailingSASLResponseArg(t *testing.T) {
1039 conn := newFakeConn(trailingSASLResponseArgServer)
1040 c, err := managesieve.NewClient(conn, "localhost")
1041 if err != nil {
1042 t.Fatalf("failed to create client: %s", err)
1043 }
1044
1045 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
1046 err = c.Authenticate(auth)
1047 if err == nil {
1048 t.Fatalf("expected error but succeeded")
1049 }
1050 if _, ok := err.(managesieve.ParserError); !ok {
1051 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err)
1052 }
1053 }
1054
1055 // trailing argument after first SASL response code argument
1056 var trailingSASLResonseArgServer string = `"IMPLEMENTATION" "Example"
1057 "SASL" "PLAIN"
1058 "SIEVE" "fileinto vacation"
1059 "StARTTLS"
1060 "VERSION" "1.0"
1061 OK
1062 OK (SASL "Zm9v" "bar")
1063 `
1064
1065 func TestTrailingSASLResonseArg(t *testing.T) {
1066 conn := newFakeConn(trailingSASLResonseArgServer)
1067 c, err := managesieve.NewClient(conn, "localhost")
1068 if err != nil {
1069 t.Fatalf("failed to create client: %s", err)
1070 }
1071
1072 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
1073 err = c.Authenticate(auth)
1074 if err == nil {
1075 t.Fatalf("expected error but succeeded")
1076 }
1077 if _, ok := err.(managesieve.ParserError); !ok {
1078 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err)
1079 }
1080 }
1081
1082 // SASL response code argument is not base64-encoded
1083 var invalidSASLResponseArgServer string = `"IMPLEMENTATION" "Example"
1084 "SASL" "PLAIN"
1085 "SIEVE" "fileinto vacation"
1086 "StARTTLS"
1087 "VERSION" "1.0"
1088 OK
1089 OK (SASL "*****")
1090 `
1091
1092 func TestInvalidSASLResponseArg(t *testing.T) {
1093 conn := newFakeConn(invalidSASLResponseArgServer)
1094 c, err := managesieve.NewClient(conn, "localhost")
1095 if err != nil {
1096 t.Fatalf("failed to create client: %s", err)
1097 }
1098
1099 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
1100 err = c.Authenticate(auth)
1101 if err == nil {
1102 t.Fatalf("expected error but succeeded")
1103 }
1104 if _, ok := err.(managesieve.ParserError); !ok {
1105 t.Fatalf("expected managesieve.ParserError, got %T (%q)", err, err)
1106 }
1107 }
1108
1109 // argument passed with SASL response code rejected by client authentication
1110 // handler
1111 var saslResponseRejectedServer string = `"IMPLEMENTATION" "Example"
1112 "SASL" "PLAIN TEST"
1113 "SIEVE" "fileinto vacation"
1114 "StARTTLS"
1115 "VERSION" "1.0"
1116 OK
1117 OK (SASL "Zm9v")
1118 `
1119
1120 func TestSASLResponseRejected(t *testing.T) {
1121 conn := newFakeConn(saslResponseRejectedServer)
1122 c, err := managesieve.NewClient(conn, "localhost")
1123 if err != nil {
1124 t.Fatalf("failed to create client: %s", err)
1125 }
1126
1127 auth := &testAuth{WantError: true}
1128 if err = c.Authenticate(auth); err == nil {
1129 t.Fatalf("expected error but succeeded")
1130 }
1131 if _, ok := err.(managesieve.AuthenticationError); !ok {
1132 t.Fatalf("expected managesieve.AuthenticationError, got %T (%q)", err, err)
1133 }
1134 }
1135
1136 // CAPABILITIES command after authentication failed
1137 var authCapabilitiesFailedServer string = `"IMPLEMENTATION" "Example"
1138 "SASL" "PLAIN"
1139 "SIEVE" "fileinto vacation"
1140 "StARTTLS"
1141 "VERSION" "1.0"
1142 OK
1143 OK
1144 NO
1145 `
1146
1147 func TestAuthCapabilitiesFailed(t *testing.T) {
1148 conn := newFakeConn(authCapabilitiesFailedServer)
1149 c, err := managesieve.NewClient(conn, "localhost")
1150 if err != nil {
1151 t.Fatalf("failed to create client: %s", err)
1152 }
1153
1154 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
1155 err = c.Authenticate(auth)
1156 if err == nil {
1157 t.Fatalf("expected error but succeeded")
1158 }
1159 if _, ok := err.(*managesieve.ServerError); !ok {
1160 t.Fatalf("expected *managesieve.ServerError, got %T (%q)", err, err)
1161 }
1162 }
1163
1164 // BYE in response to SASL authentication attempt
1165 var authByeServer string = `"IMPLEMENTATION" "Example"
1166 "SASL" "PLAIN"
1167 "SIEVE" "fileinto vacation"
1168 "StARTTLS"
1169 "VERSION" "1.0"
1170 OK
1171 BYE "authentication denied"
1172 `
1173
1174 func TestAuthBye(t *testing.T) {
1175 conn := newFakeConn(authByeServer)
1176 c, err := managesieve.NewClient(conn, "localhost")
1177 if err != nil {
1178 t.Fatalf("failed to create client: %s", err)
1179 }
1180
1181 auth := managesieve.PlainAuth("", "foo", "S3cR3T", "localhost")
1182 err = c.Authenticate(auth)
1183 if err == nil {
1184 t.Fatalf("expected error but succeeded")
1185 }
1186 if _, ok := err.(*managesieve.ConnClosedError); !ok {
1187 t.Fatalf("expected *managesieve.ConnClosedError, got %T (%q)", err, err)
1188 }
1189 }