Mercurial > projects > managesieve
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 } |