package smtpclient import ( "bufio" "context" "encoding/base64" "net" "strings" "testing" "time" ) func TestSendUsesConfiguredEnvelopeFrom(t *testing.T) { server, client := net.Pipe() defer client.Close() commands := make(chan string, 32) done := make(chan struct{}) go func() { defer close(done) defer close(commands) defer server.Close() r := bufio.NewReader(server) w := bufio.NewWriter(server) _, _ = w.WriteString("220 test\r\n") _ = w.Flush() inData := false for { line, err := r.ReadString('\n') if err != nil { return } commands <- line switch { case inData && line != ".\r\n": continue case strings.HasPrefix(line, "EHLO "): _, _ = w.WriteString("250 test\r\n") case strings.HasPrefix(line, "MAIL FROM:"): _, _ = w.WriteString("250 ok\r\n") case strings.HasPrefix(line, "RCPT TO:"): _, _ = w.WriteString("250 ok\r\n") case strings.HasPrefix(line, "DATA"): inData = true _, _ = w.WriteString("354 go ahead\r\n") case line == ".\r\n": inData = false _, _ = w.WriteString("250 queued\r\n") case strings.HasPrefix(line, "QUIT"): _, _ = w.WriteString("221 bye\r\n") _ = w.Flush() return default: continue } _ = w.Flush() } }() mailer := New(Config{ Host: "mail2news.tcpreset.net", Port: 25, Recipient: "mail2news@mail2news.tcpreset.net", EnvelopeFrom: "n2usenet@virebent.art", HELO: "n2usenet.virebent.art", RequireTLS: false, Timeout: 5 * time.Second, }, func(ctx context.Context, network, address string) (net.Conn, error) { return client, nil }) if err := mailer.Send(context.Background(), Message{ EnvelopeFrom: "user@example.invalid", Raw: "From: User \r\nTo: mail2news@mail2news.tcpreset.net\r\nSubject: Test\r\n\r\nhello", }); err != nil { t.Fatalf("Send returned error: %v", err) } var sawConfiguredFrom bool for line := range commands { if line == "MAIL FROM:\r\n" { sawConfiguredFrom = true break } } <-done if !sawConfiguredFrom { t.Fatal("configured envelope sender was not used") } } func TestSessionAuthPlain(t *testing.T) { server, client := net.Pipe() defer client.Close() command := make(chan string, 1) go func() { defer server.Close() r := bufio.NewReader(server) line, err := r.ReadString('\n') if err != nil { command <- "" return } command <- line _, _ = server.Write([]byte("235 2.7.0 authentication successful\r\n")) }() session := newSession(client) if err := session.authPlain("n2usenet@virebent.art", "test-password"); err != nil { t.Fatalf("authPlain returned error: %v", err) } line := <-command const prefix = "AUTH PLAIN " if !strings.HasPrefix(line, prefix) { t.Fatalf("unexpected AUTH command: %q", line) } token := strings.TrimSpace(strings.TrimPrefix(line, prefix)) decoded, err := base64.StdEncoding.DecodeString(token) if err != nil { t.Fatalf("decode AUTH payload: %v", err) } if got, want := string(decoded), "\x00n2usenet@virebent.art\x00test-password"; got != want { t.Fatalf("unexpected AUTH payload: got %q want %q", got, want) } } func TestSessionAuthPlainRejectsNUL(t *testing.T) { session := &session{} if err := session.authPlain("n2usenet\x00admin", "test-password"); err == nil { t.Fatal("authPlain accepted a username containing NUL") } if err := session.authPlain("n2usenet", "test\x00password"); err == nil { t.Fatal("authPlain accepted a password containing NUL") } } func TestCheckDoesNotStartMailTransaction(t *testing.T) { server, client := net.Pipe() defer client.Close() commands := make(chan string, 8) go func() { defer close(commands) defer server.Close() r := bufio.NewReader(server) w := bufio.NewWriter(server) _, _ = w.WriteString("220 test\r\n") _ = w.Flush() for { line, err := r.ReadString('\n') if err != nil { return } commands <- line switch { case strings.HasPrefix(line, "EHLO "): _, _ = w.WriteString("250 test\r\n") case strings.HasPrefix(line, "QUIT"): _, _ = w.WriteString("221 bye\r\n") _ = w.Flush() return default: _, _ = w.WriteString("500 unexpected\r\n") } _ = w.Flush() } }() mailer := New(Config{ Host: "mail.virebent.art", Port: 25, HELO: "n2usenet.virebent.art", RequireTLS: false, Timeout: 5 * time.Second, }, func(context.Context, string, string) (net.Conn, error) { return client, nil }) if err := mailer.Check(context.Background()); err != nil { t.Fatalf("Check returned error: %v", err) } for command := range commands { if strings.HasPrefix(command, "MAIL ") || strings.HasPrefix(command, "RCPT ") || strings.HasPrefix(command, "DATA") { t.Fatalf("health check started a mail transaction: %q", command) } } }