maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package smtp
 20
 21import (
 22	"flag"
 23	"math/rand"
 24	"net"
 25	"os"
 26	"strconv"
 27	"strings"
 28	"testing"
 29	"time"
 30
 31	"github.com/emersion/go-sasl"
 32	"github.com/emersion/go-smtp"
 33	"github.com/foxcpp/go-mockdns"
 34	"github.com/foxcpp/maddy/framework/config"
 35	"github.com/foxcpp/maddy/framework/exterrors"
 36	"github.com/foxcpp/maddy/framework/module"
 37	"github.com/foxcpp/maddy/internal/auth"
 38	"github.com/foxcpp/maddy/internal/msgpipeline"
 39	"github.com/foxcpp/maddy/internal/testutils"
 40)
 41
 42var testPort string
 43
 44const testMsg = "From: <sender@example.org>\r\n" +
 45	"Subject: Hello there!\r\n" +
 46	"\r\n" +
 47	"foobar\r\n"
 48
 49func testEndpoint(t *testing.T, modName string, authMod module.PlainAuth, tgt module.DeliveryTarget, checks []module.Check, cfg []config.Node) *Endpoint {
 50	t.Helper()
 51
 52	mod, err := New(modName, []string{"tcp://127.0.0.1:" + testPort})
 53	if err != nil {
 54		t.Fatal(err)
 55	}
 56	endp := mod.(*Endpoint)
 57
 58	endp.resolver = &mockdns.Resolver{
 59		Zones: map[string]mockdns.Zone{
 60			"mx.example.org.": {
 61				A: []string{"127.0.0.1"},
 62			},
 63			"1.0.0.127.in-addr.arpa.": {
 64				PTR: []string{"mx.example.org"},
 65			},
 66		},
 67	}
 68	endp.Log = testutils.Logger(t, "smtp")
 69
 70	cfg = append(cfg,
 71		config.Node{
 72			Name: "hostname",
 73			Args: []string{"mx.example.com"},
 74		},
 75		config.Node{
 76			Name: "tls",
 77			Args: []string{"off"},
 78		},
 79		config.Node{ // To make it succeed, pipeline is actually replaced below.
 80			Name: "deliver_to",
 81			Args: []string{"dummy"},
 82		},
 83	)
 84
 85	if authMod != nil {
 86		cfg = append(cfg, config.Node{
 87			Name: "auth",
 88			Args: []string{"dummy"},
 89		})
 90	}
 91
 92	err = endp.Init(config.NewMap(nil, config.Node{
 93		Children: cfg,
 94	}))
 95	if err != nil {
 96		t.Fatal(err)
 97	}
 98
 99	endp.saslAuth = auth.SASLAuth{
100		Log:   testutils.Logger(t, "smtp/saslauth"),
101		Plain: []module.PlainAuth{authMod},
102	}
103
104	endp.pipeline = msgpipeline.Mock(tgt, checks)
105	endp.pipeline.Hostname = "mx.example.com"
106	endp.pipeline.Resolver = endp.resolver
107	endp.pipeline.FirstPipeline = true
108	endp.pipeline.Log = testutils.Logger(t, "smtp/pipeline")
109
110	return endp
111}
112
113func submitMsg(t *testing.T, cl *smtp.Client, from string, rcpts []string, msg string) error {
114	return submitMsgOpts(t, cl, from, rcpts, nil, msg)
115}
116
117func submitMsgOpts(t *testing.T, cl *smtp.Client, from string, rcpts []string, opts *smtp.MailOptions, msg string) error {
118	t.Helper()
119
120	// Error for this one is ignored because it fails if EHLO was already sent
121	// and submitMsg can happen multiple times.
122	_ = cl.Hello("mx.example.org")
123	if err := cl.Mail(from, opts); err != nil {
124		return err
125	}
126	for _, rcpt := range rcpts {
127		if err := cl.Rcpt(rcpt, &smtp.RcptOptions{}); err != nil {
128			return err
129		}
130	}
131	data, err := cl.Data()
132	if err != nil {
133		return err
134	}
135	if _, err := data.Write([]byte(msg)); err != nil {
136		return err
137	}
138
139	return data.Close()
140}
141
142func TestSMTPDelivery(t *testing.T) {
143	tgt := testutils.Target{}
144	endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil)
145	defer endp.Close()
146
147	cl, err := smtp.Dial("127.0.0.1:" + testPort)
148	if err != nil {
149		t.Fatal(err)
150	}
151	defer cl.Close()
152
153	err = submitMsg(t, cl, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg)
154	if err != nil {
155		t.Fatal(err)
156	}
157
158	if len(tgt.Messages) != 1 {
159		t.Fatal("Expected a message, got", len(tgt.Messages))
160	}
161	msg := tgt.Messages[0]
162	msgID := testutils.CheckMsgID(t, &msg, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, "")
163
164	receivedPrefix := `from mx.example.org (mx.example.org [127.0.0.1]) by mx.example.com (envelope-sender <sender@example.org>) with ESMTP id ` + msgID
165
166	if !strings.HasPrefix(msg.Header.Get("Received"), receivedPrefix) {
167		t.Error("Wrong Received contents:", msg.Header.Get("Received"))
168	}
169
170	if msg.MsgMeta.Conn.Proto != "ESMTP" {
171		t.Error("Wrong SrcProto:", msg.MsgMeta.Conn.Proto)
172	}
173
174	rdnsName, _ := msg.MsgMeta.Conn.RDNSName.Get()
175	if rdnsName, _ := rdnsName.(string); rdnsName != "mx.example.org" {
176		t.Error("Wrong rDNS name:", rdnsName)
177	}
178}
179
180func TestSMTPDelivery_rDNSError(t *testing.T) {
181	tgt := testutils.Target{}
182	endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil)
183	defer endp.Close()
184
185	endp.resolver.(*mockdns.Resolver).Zones["1.0.0.127.in-addr.arpa."] = mockdns.Zone{
186		Err: &net.DNSError{
187			Name:       "1.0.0.127.in-addr.arpa.",
188			Server:     "127.0.0.1:53",
189			Err:        "bad",
190			IsNotFound: false,
191		},
192	}
193
194	cl, err := smtp.Dial("127.0.0.1:" + testPort)
195	if err != nil {
196		t.Fatal(err)
197	}
198	defer cl.Close()
199
200	err = submitMsg(t, cl, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg)
201	if err != nil {
202		t.Fatal(err)
203	}
204
205	if len(tgt.Messages) != 1 {
206		t.Fatal("Expected a message, got", len(tgt.Messages))
207	}
208	msg := tgt.Messages[0]
209	testutils.CheckMsgID(t, &msg, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, "")
210
211	rdnsName, err := msg.MsgMeta.Conn.RDNSName.Get()
212	if rdnsName != nil || err == nil {
213		t.Errorf("Wrong rDNS result: %#+v (%v)", rdnsName, err)
214	}
215}
216
217func TestSMTPDelivery_EarlyCheck_Fail(t *testing.T) {
218	tgt := testutils.Target{}
219	endp := testEndpoint(t, "smtp", nil, &tgt, []module.Check{
220		&testutils.Check{
221			EarlyErr: &exterrors.SMTPError{
222				Code:    523,
223				Message: "Hey",
224			},
225		},
226	}, nil)
227	defer endp.Close()
228
229	cl, err := smtp.Dial("127.0.0.1:" + testPort)
230	if err != nil {
231		t.Fatal(err)
232	}
233	defer cl.Close()
234
235	err = cl.Mail("sender@example.org", nil)
236	if err == nil {
237		t.Fatal("Expected an error, got none")
238	}
239
240	smtpErr, ok := err.(*smtp.SMTPError)
241	if !ok {
242		t.Fatal("Non-SMTPError returned")
243	}
244
245	if smtpErr.Code != 523 {
246		t.Fatal("Wrong SMTP code:", smtpErr.Code)
247	}
248	if smtpErr.Message != "Hey" {
249		t.Fatal("Wrong SMTP message:", smtpErr.Message)
250	}
251}
252
253func TestSMTPDeliver_CheckError(t *testing.T) {
254	tgt := testutils.Target{}
255	endp := testEndpoint(t, "smtp", nil, &tgt, []module.Check{
256		&testutils.Check{
257			ConnRes: module.CheckResult{
258				Reason: &exterrors.SMTPError{
259					Code:    523,
260					Message: "Hey",
261				},
262				Reject: true,
263			},
264		},
265	}, nil)
266	endp.deferServerReject = false
267	defer endp.Close()
268
269	cl, err := smtp.Dial("127.0.0.1:" + testPort)
270	if err != nil {
271		t.Fatal(err)
272	}
273	defer cl.Close()
274
275	err = cl.Mail("sender@example.org", nil)
276	if err == nil {
277		t.Fatal("Expected an error, got none")
278	}
279	smtpErr, ok := err.(*smtp.SMTPError)
280	if !ok {
281		t.Fatal("Non-SMTPError returned")
282	}
283
284	if smtpErr.Code != 523 {
285		t.Fatal("Wrong SMTP code:", smtpErr.Code)
286	}
287	if !strings.HasPrefix(smtpErr.Message, "Hey") {
288		t.Fatal("Wrong SMTP message:", smtpErr.Message)
289	}
290}
291
292func TestSMTPDeliver_CheckError_Deferred(t *testing.T) {
293	tgt := testutils.Target{}
294	endp := testEndpoint(t, "smtp", nil, &tgt, []module.Check{
295		&testutils.Check{
296			ConnRes: module.CheckResult{
297				Reason: &exterrors.SMTPError{
298					Code:    523,
299					Message: "Hey",
300				},
301				Reject: true,
302			},
303		},
304	}, nil)
305	endp.deferServerReject = true
306	defer endp.Close()
307
308	cl, err := smtp.Dial("127.0.0.1:" + testPort)
309	if err != nil {
310		t.Fatal(err)
311	}
312	defer cl.Close()
313
314	err = cl.Mail("sender@example.org", nil)
315	if err != nil {
316		t.Fatal(err)
317	}
318
319	checkErr := func(err error) {
320		if err == nil {
321			t.Fatal("Expected an error, got none")
322		}
323		smtpErr, ok := err.(*smtp.SMTPError)
324		if !ok {
325			t.Error("Non-SMTPError returned")
326			return
327		}
328
329		if smtpErr.Code != 523 {
330			t.Error("Wrong SMTP code:", smtpErr.Code)
331		}
332		if !strings.HasPrefix(smtpErr.Message, "Hey") {
333			t.Error("Wrong SMTP message:", smtpErr.Message)
334		}
335	}
336
337	checkErr(cl.Rcpt("test1@example.org", &smtp.RcptOptions{}))
338	checkErr(cl.Rcpt("test1@example.org", &smtp.RcptOptions{}))
339	checkErr(cl.Rcpt("test2@example.org", &smtp.RcptOptions{}))
340}
341
342func TestSMTPDelivery_Multi(t *testing.T) {
343	tgt := testutils.Target{}
344	endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil)
345	defer endp.Close()
346
347	cl, err := smtp.Dial("127.0.0.1:" + testPort)
348	if err != nil {
349		t.Fatal(err)
350	}
351	defer cl.Close()
352
353	err = submitMsg(t, cl, "sender1@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg)
354	if err != nil {
355		t.Fatal(err)
356	}
357	err = submitMsg(t, cl, "sender2@example.org", []string{"rcpt3@example.com", "rcpt4@example.com"}, testMsg)
358	if err != nil {
359		t.Fatal(err)
360	}
361
362	if len(tgt.Messages) != 2 {
363		t.Fatal("Expected two messages, got", len(tgt.Messages))
364	}
365	msg := tgt.Messages[0]
366	msgID := testutils.CheckMsgID(t, &msg, "sender1@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, "")
367	receivedPrefix := `from mx.example.org (mx.example.org [127.0.0.1]) by mx.example.com (envelope-sender <sender1@example.org>) with ESMTP id ` + msgID
368	if !strings.HasPrefix(msg.Header.Get("Received"), receivedPrefix) {
369		t.Error("Wrong Received contents:", msg.Header.Get("Received"))
370	}
371
372	msg = tgt.Messages[1]
373	msgID = testutils.CheckMsgID(t, &msg, "sender2@example.org", []string{"rcpt3@example.com", "rcpt4@example.com"}, "")
374	receivedPrefix = `from mx.example.org (mx.example.org [127.0.0.1]) by mx.example.com (envelope-sender <sender2@example.org>) with ESMTP id ` + msgID
375	if !strings.HasPrefix(msg.Header.Get("Received"), receivedPrefix) {
376		t.Error("Wrong Received contents:", msg.Header.Get("Received"))
377	}
378}
379
380func TestSMTPDelivery_AbortData(t *testing.T) {
381	tgt := testutils.Target{}
382	endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil)
383	defer endp.Close()
384
385	cl, err := smtp.Dial("127.0.0.1:" + testPort)
386	if err != nil {
387		t.Fatal(err)
388	}
389	defer cl.Close()
390
391	if err := cl.Hello("mx.example.org"); err != nil {
392		t.Fatal(err)
393	}
394	if err := cl.Mail("sender@example.org", nil); err != nil {
395		t.Fatal(err)
396	}
397	if err := cl.Rcpt("test@example.com", &smtp.RcptOptions{}); err != nil {
398		t.Fatal(err)
399	}
400	data, err := cl.Data()
401	if err != nil {
402		t.Fatal(err)
403	}
404	if _, err := data.Write([]byte(testMsg)); err != nil {
405		t.Fatal(err)
406	}
407
408	// Then.. Suddenly, close the connection without sending the final dot.
409	cl.Close()
410
411	time.Sleep(250 * time.Millisecond)
412
413	if len(tgt.Messages) != 0 {
414		t.Fatal("Expected no messages, got", len(tgt.Messages))
415	}
416}
417
418func TestSMTPDelivery_EmptyMessage(t *testing.T) {
419	tgt := testutils.Target{}
420	endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil)
421	defer endp.Close()
422
423	cl, err := smtp.Dial("127.0.0.1:" + testPort)
424	if err != nil {
425		t.Fatal(err)
426	}
427	defer cl.Close()
428
429	if err := cl.Hello("mx.example.org"); err != nil {
430		t.Fatal(err)
431	}
432	if err := cl.Mail("sender@example.org", nil); err != nil {
433		t.Fatal(err)
434	}
435	if err := cl.Rcpt("test@example.com", &smtp.RcptOptions{}); err != nil {
436		t.Fatal(err)
437	}
438	data, err := cl.Data()
439	if err != nil {
440		t.Fatal(err)
441	}
442	if err := data.Close(); err != nil {
443		t.Fatal(err)
444	}
445
446	time.Sleep(250 * time.Millisecond)
447
448	if len(tgt.Messages) != 1 {
449		t.Fatal("Expected 1 message, got", len(tgt.Messages))
450	}
451	msg := tgt.Messages[0]
452	if len(msg.Body) != 0 {
453		t.Fatal("Expected an empty body, got", len(msg.Body))
454	}
455}
456
457func TestSMTPDelivery_AbortLogout(t *testing.T) {
458	tgt := testutils.Target{}
459	endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil)
460	defer endp.Close()
461
462	cl, err := smtp.Dial("127.0.0.1:" + testPort)
463	if err != nil {
464		t.Fatal(err)
465	}
466	defer cl.Close()
467
468	if err := cl.Hello("mx.example.org"); err != nil {
469		t.Fatal(err)
470	}
471	if err := cl.Mail("sender@example.org", nil); err != nil {
472		t.Fatal(err)
473	}
474	if err := cl.Rcpt("test@example.com", &smtp.RcptOptions{}); err != nil {
475		t.Fatal(err)
476	}
477
478	// Then.. Suddenly, close the connection.
479	cl.Close()
480
481	time.Sleep(250 * time.Millisecond)
482
483	if len(tgt.Messages) != 0 {
484		t.Fatal("Expected no messages, got", len(tgt.Messages))
485	}
486}
487
488func TestSMTPDelivery_Reset(t *testing.T) {
489	tgt := testutils.Target{}
490	endp := testEndpoint(t, "smtp", nil, &tgt, nil, nil)
491	defer endp.Close()
492
493	cl, err := smtp.Dial("127.0.0.1:" + testPort)
494	if err != nil {
495		t.Fatal(err)
496	}
497	defer cl.Close()
498
499	if err := cl.Mail("from-garbage@example.org", nil); err != nil {
500		t.Fatal(err)
501	}
502	if err := cl.Rcpt("to-garbage@example.org", &smtp.RcptOptions{}); err != nil {
503		t.Fatal(err)
504	}
505	if err := cl.Reset(); err != nil {
506		t.Fatal(err)
507	}
508
509	// then submit the message as if nothing happened.
510
511	err = submitMsg(t, cl, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, testMsg)
512	if err != nil {
513		t.Fatal(err)
514	}
515
516	if len(tgt.Messages) != 1 {
517		t.Fatal("Expected a message, got", len(tgt.Messages))
518	}
519	msg := tgt.Messages[0]
520	testutils.CheckMsgID(t, &msg, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"}, "")
521}
522
523func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) {
524	tgt := testutils.Target{}
525	endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil)
526	defer endp.Close()
527
528	cl, err := smtp.Dial("127.0.0.1:" + testPort)
529	if err != nil {
530		t.Fatal(err)
531	}
532	defer cl.Close()
533
534	if err := cl.Mail("from-garbage@example.org", nil); err == nil {
535		t.Fatal("Expected an error, got none")
536	}
537}
538
539func TestSMTPDelivery_SubmissionAuthOK(t *testing.T) {
540	tgt := testutils.Target{}
541	endp := testEndpoint(t, "submission", &module.Dummy{}, &tgt, nil, nil)
542	defer endp.Close()
543
544	cl, err := smtp.Dial("127.0.0.1:" + testPort)
545	if err != nil {
546		t.Fatal(err)
547	}
548	defer cl.Close()
549
550	if err := cl.Auth(sasl.NewPlainClient("", "user", "password")); err != nil {
551		t.Fatal(err)
552	}
553
554	if err := submitMsg(t, cl, "sender@example.org", []string{"rcpt@example.org"}, testMsg); err != nil {
555		t.Fatal(err)
556	}
557
558	if len(tgt.Messages) != 1 {
559		t.Fatal("Expected a message, got", len(tgt.Messages))
560	}
561	msg := tgt.Messages[0]
562	msgID := testutils.CheckMsgID(t, &msg, "sender@example.org", []string{"rcpt@example.org"}, "")
563
564	if msg.MsgMeta.Conn.AuthUser != "user" {
565		t.Error("Wrong AuthUser:", msg.MsgMeta.Conn.AuthUser)
566	}
567	if msg.MsgMeta.Conn.AuthPassword != "password" {
568		t.Error("Wrong AuthPassword:", msg.MsgMeta.Conn.AuthPassword)
569	}
570
571	receivedPrefix := `by mx.example.com (envelope-sender <sender@example.org>) with ESMTP id ` + msgID
572	if !strings.HasPrefix(msg.Header.Get("Received"), receivedPrefix) {
573		t.Error("Wrong Received contents:", msg.Header.Get("Received"))
574	}
575
576	if msg.Header.Get("Message-ID") == "" {
577		t.Error("No submissionPrepare run")
578	}
579}
580
581func TestMain(m *testing.M) {
582	remoteSmtpPort := flag.String("test.smtpport", "random", "(maddy) SMTP port to use for connections in tests")
583	flag.Parse()
584
585	if *remoteSmtpPort == "random" {
586		*remoteSmtpPort = strconv.Itoa(rand.Intn(65536-10000) + 10000)
587	}
588
589	testPort = *remoteSmtpPort
590	os.Exit(m.Run())
591}