123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- package guerrilla
- import (
- "testing"
- "bufio"
- "net/textproto"
- "strings"
- "sync"
- "crypto/tls"
- "fmt"
- "github.com/flashmob/go-guerrilla/backends"
- "github.com/flashmob/go-guerrilla/log"
- "github.com/flashmob/go-guerrilla/mail"
- "github.com/flashmob/go-guerrilla/mocks"
- "io/ioutil"
- "net"
- "os"
- )
- // getMockServerConfig gets a mock ServerConfig struct used for creating a new server
- func getMockServerConfig() *ServerConfig {
- sc := &ServerConfig{
- IsEnabled: true, // not tested here
- Hostname: "saggydimes.test.com",
- MaxSize: 1024, // smtp message max size
- TLS: ServerTLSConfig{
- PrivateKeyFile: "./tests/mail.guerrillamail.com.key.pem",
- PublicKeyFile: "./tests/mail.guerrillamail.com.cert.pem",
- StartTLSOn: true,
- AlwaysOn: false,
- },
- Timeout: 5,
- ListenInterface: "127.0.0.1:2529",
- MaxClients: 30, // not tested here
- LogFile: "./tests/testlog",
- }
- return sc
- }
- // getMockServerConn gets a new server using sc. Server will be using a mocked TCP connection
- // using the dummy backend
- // RCP TO command only allows test.com host
- func getMockServerConn(sc *ServerConfig, t *testing.T) (*mocks.Conn, *server) {
- var logOpenError error
- var mainlog log.Logger
- mainlog, logOpenError = log.GetLogger(sc.LogFile, "debug")
- if logOpenError != nil {
- mainlog.WithError(logOpenError).Errorf("Failed creating a logger for mock conn [%s]", sc.ListenInterface)
- }
- backend, err := backends.New(
- backends.BackendConfig{"log_received_mails": true, "save_workers_size": 1},
- mainlog)
- if err != nil {
- t.Error("new dummy backend failed because:", err)
- }
- server, err := newServer(sc, backend, mainlog)
- if err != nil {
- //t.Error("new server failed because:", err)
- } else {
- server.setAllowedHosts([]string{"test.com"})
- }
- conn := mocks.NewConn()
- return conn, server
- }
- // test the RootCAs tls config setting
- var rootCAPK = `-----BEGIN CERTIFICATE-----
- MIIDqjCCApKgAwIBAgIJALh2TrsBR5MiMA0GCSqGSIb3DQEBCwUAMGkxCzAJBgNV
- BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEhMB8G
- A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhv
- c3QwIBcNMTgwNTE4MDYzOTU2WhgPMjExODA0MjQwNjM5NTZaMGkxCzAJBgNVBAYT
- AlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEhMB8GA1UE
- CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3Qw
- ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCcb0ulYT1o5ysor5UtWYW
- q/ZY3PyK3/4YBZq5JoX4xk7GNQQ+3p/Km7QPoBXfgjFLZXEV2R0bE5hHMXfLa5Xb
- 64acb9VqCqDvPFXcaNP4rEdBKDVN2p0PEi917tcKBSrZn5Yl+iOhtcBpQDvhHgn/
- 9MdmIAKB3+yK+4l9YhT40XfDXCQqzfg4XcNaEgTzZHcDJz+KjWJuJChprcx27MTI
- Ndxs9nmFA2rK16rjgjtwjZ4t9dXsljdOcx59s6dIQ0GnEM8qdKxi/vEx4+M/hbGf
- v7H75LsuKRrVJINAmfy9fmc6VAXjFU0ZVxGK5eVnzsh/hY08TSSrlCCKAJpksjJz
- AgMBAAGjUzBRMB0GA1UdDgQWBBSZsYWs+8FYe4z4c6LLmFB4TeeV/jAfBgNVHSME
- GDAWgBSZsYWs+8FYe4z4c6LLmFB4TeeV/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
- SIb3DQEBCwUAA4IBAQAcXt/FaILkOCMj8bTUx42vi2N9ZTiEuRbYi24IyGOokbDR
- pSsIxiz+HDPUuX6/X/mHidl24aS9wdv5JTXMr44/BeGK1WC7gMueZBxAqONpaG1Q
- VU0e3q1YwXKcupKQ7kVWl0fuY3licv0+s4zBcTLKkmWAYqsb/n0KtCMyqewi+Rqa
- Zj5Z3OcWOq9Ad9fZWKcG8k/sgeTk9z0X1mZcEyWWxqsUmxvN+SdWLoug1xJVVbMN
- CipZ0vBIi9KOhQgzuIFhoTcd6myUtov52/EFqlX6UuFpY2gEWw/f/yu+SI08v4w9
- KwxgAKBkhx2JYZKtu1EsPIMDyS0aahcDnHqnrGAi
- -----END CERTIFICATE-----`
- var clientPrvKey = `-----BEGIN RSA PRIVATE KEY-----
- MIIEowIBAAKCAQEA5ZLmMBdKkVyVmN0VhDSFGvgKp24ejHPCv+wfuf3vlU9cwKfH
- R3vejleZAVRcidscfA0Jsub/Glsr0XwecagtpvTI+Fp1ik6sICOz+VW3958qaAi8
- TjbUMjcDHJeSLcjr725CH5uIvhRzR+daYaJQhAcL2MEt8M9WIF6AjtDZEH9R6oM8
- t5FkO0amImlnipYXNBFghmzkZzfGXXRQLw2A+u6keLcjCrn9h2BaofGIjQfYcu/3
- fH4cIFR4z/soGKameqnCUz7dWmbf4tAI+8QR0VXXBKhiHDm98tPSeH994hC52Uul
- rjEVcM5Uox5hazS2PK06oSc1YuFZONqeeGqj6wIDAQABAoIBADERzRHKaK3ZVEBw
- QQEZGLpC+kP/TZhHxgCvv7hJhsQrSnADbJzi5RcXsiSOm5j7tILvZntO1IgVpLAK
- D5fLkrZ069/pteXyGuhjuTw6DjBnXPEPrPAq2ABDse6SlzQiFgv/TTLkU74NMPbV
- hIQJ5ZvSxb12zRMDviz9Bg2ApmTX6k2iPjQBnEHgKzb64IdMcEb5HE1qNt0v0lRA
- sGBMZZKQWbt2m0pSbAbnB3S9GcpJkRgFFMdTaUScIWO6ICT2hBP2pw2/4M2Zrmlt
- bsyWu9uswBzhvu+/pg2E66V6mji0EzDMlXqjlO5jro6t7P33t1zkd/i/ykKmtDLp
- IpR94UECgYEA9Y4EIjOyaBWJ6TRQ6a/tehGPbwIOgvEiTYXRJqdU49qn/i4YZjSm
- F4iibJz+JeOIQXSwa9F7gRlaspIuHgIJoer7BrITMuhr+afqMLkxK0pijul/qAbm
- HdpFn8IxjpNu4/GoAENbEVy50SMST9yWh5ulEkHHftd4/NJKoJQ2PZ8CgYEA71bb
- lFVh1MFclxRKECmpyoqUAzwGlMoHJy/jaBYuWG4X7rzxqDRrgPH3as6gXpRiSZ+K
- 5fC+wcU7dKnHtJOkBDk6J5ev2+hbwg+yq3w4+l3bPDvf2TJyXjXjRDZo12pxFD58
- ybCOF6ItbIDXqT5pvo3PMjgMwu1Ycie+h6hA3jUCgYEAsq93XpQT/R2/T44cWxEE
- VFG2+GacvLhP5+26ttAJPA1/Nb3BT458Vp+84iCT6GpcWpVZU/wKTXVvxIYPPRLq
- g4MEzGiFBASRngiMqIv6ta/ZbHmJxXHPvmV5SLn9aezrQsA1KovZFxdMuF03FBpH
- B8NBKbnoO+r8Ra2ZVKTFm60CgYAZw8Dpi/N3IsWj4eRDLyj/C8H5Qyn2NHVmq4oQ
- d2rPzDI5Wg+tqs7z15hp4Ap1hAW8pTcfn7X5SBEpculzr/0VE1AGWRbuVmoiTuxN
- 95ZupVHnfw6O5BZZu/VWL4FDx0qbAksOrznso2G+b3RH3NcnUz69yjjddw1xZIPn
- OJ6bDQKBgDUcWYu/2amU18D5vJpppUgRq2084WPUeXsaniTbmWfOC8NAn8CKLY0N
- V4yGSu98apDuqEVqL0VFQEgqK+5KTvRdXXYi36XYRbbVUgV13xveq2YTvjNbPM60
- QWG9YmgH7hVYGusuh5nQeS0qiIpwyws2H5mBVrGXrQ1Xb0MLWj8/
- -----END RSA PRIVATE KEY-----`
- // signed using the Root (rootCAPK)
- var clientPubKey = `-----BEGIN CERTIFICATE-----
- MIIDWDCCAkACCQCHoh4OvUySOzANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJV
- UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxITAfBgNVBAoM
- GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MCAX
- DTE4MDUxODA2NDQ0NVoYDzMwMTcwOTE4MDY0NDQ1WjBxMQswCQYDVQQGEwJVUzET
- MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEhMB8G
- A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhv
- c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlkuYwF0qRXJWY3RWE
- NIUa+Aqnbh6Mc8K/7B+5/e+VT1zAp8dHe96OV5kBVFyJ2xx8DQmy5v8aWyvRfB5x
- qC2m9Mj4WnWKTqwgI7P5Vbf3nypoCLxONtQyNwMcl5ItyOvvbkIfm4i+FHNH51ph
- olCEBwvYwS3wz1YgXoCO0NkQf1Hqgzy3kWQ7RqYiaWeKlhc0EWCGbORnN8ZddFAv
- DYD67qR4tyMKuf2HYFqh8YiNB9hy7/d8fhwgVHjP+ygYpqZ6qcJTPt1aZt/i0Aj7
- xBHRVdcEqGIcOb3y09J4f33iELnZS6WuMRVwzlSjHmFrNLY8rTqhJzVi4Vk42p54
- aqPrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIQmlo8iCpyYggkbpfDmThBPHfy1
- cZcCi/tRFoFe1ccwn2ezLMIKmW38ZebiroawwqrZgU6AP+dMxVKLMjpyLPSrpFKa
- 3o/LbVF7qMfH8/y2q8t7javd6rxoENH9uxLyHhauzI1iWy0whoDWBNiZrPBTBCjq
- jDGZARZqGyrPeXi+RNe1cMvZCxAFy7gqEtWFLWWrp0gYNPvxkHhhQBrUcF+8T/Nf
- 9G4hKZSN/KAgC0CNBVuNrdyNc3l8H66BfwwL5X0+pesBYZM+MEfmBZOo+p7OWx2r
- ug8tR8eSL1vGleONtFRBUVG7NbtjhBf9FhvPZcSRR10od/vWHku9E01i4xg=
- -----END CERTIFICATE-----`
- func TestTLSConfig(t *testing.T) {
- if err := ioutil.WriteFile("rootca.test.pem", []byte(rootCAPK), 0644); err != nil {
- t.Fatal("couldn't create rootca.test.pem file.", err)
- return
- }
- if err := ioutil.WriteFile("client.test.key", []byte(clientPrvKey), 0644); err != nil {
- t.Fatal("couldn't create client.test.key file.", err)
- return
- }
- if err := ioutil.WriteFile("client.test.pem", []byte(clientPubKey), 0644); err != nil {
- t.Fatal("couldn't create client.test.pem file.", err)
- return
- }
- s := server{}
- s.setConfig(&ServerConfig{
- TLS: ServerTLSConfig{
- StartTLSOn: true,
- PrivateKeyFile: "client.test.key",
- PublicKeyFile: "client.test.pem",
- RootCAs: "rootca.test.pem",
- ClientAuthType: "NoClientCert",
- Curves: []string{"P521", "P384"},
- Ciphers: []string{"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"},
- Protocols: []string{"tls1.0", "tls1.2"},
- },
- })
- s.configureSSL()
- c := s.tlsConfigStore.Load().(*tls.Config)
- if len(c.CurvePreferences) != 2 {
- t.Error("c.CurvePreferences should have two elements")
- } else if c.CurvePreferences[0] != tls.CurveP521 && c.CurvePreferences[1] != tls.CurveP384 {
- t.Error("c.CurvePreferences curves not setup")
- }
- if strings.Index(string(c.RootCAs.Subjects()[0]), "Mountain View") == -1 {
- t.Error("c.RootCAs not correctly set")
- }
- if c.ClientAuth != tls.NoClientCert {
- t.Error("c.ClientAuth should be tls.NoClientCert")
- }
- if len(c.CipherSuites) != 2 {
- t.Error("c.CipherSuites length should be 2")
- }
- if c.CipherSuites[0] != tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 && c.CipherSuites[1] != tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA {
- t.Error("c.CipherSuites not correctly set ")
- }
- if c.MinVersion != tls.VersionTLS10 {
- t.Error("c.MinVersion should be tls.VersionTLS10")
- }
- if c.MaxVersion != tls.VersionTLS12 {
- t.Error("c.MinVersion should be tls.VersionTLS10")
- }
- if c.PreferServerCipherSuites != false {
- t.Error("PreferServerCipherSuites should be false")
- }
- os.Remove("rootca.test.pem")
- os.Remove("client.test.key")
- os.Remove("client.test.pem")
- }
- func TestHandleClient(t *testing.T) {
- var mainlog log.Logger
- var logOpenError error
- sc := getMockServerConfig()
- mainlog, logOpenError = log.GetLogger(sc.LogFile, "debug")
- if logOpenError != nil {
- mainlog.WithError(logOpenError).Errorf("Failed creating a logger for mock conn [%s]", sc.ListenInterface)
- }
- conn, server := getMockServerConn(sc, t)
- // call the serve.handleClient() func in a goroutine.
- client := NewClient(conn.Server, 1, mainlog, mail.NewPool(5))
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- server.handleClient(client)
- wg.Done()
- }()
- // Wait for the greeting from the server
- r := textproto.NewReader(bufio.NewReader(conn.Client))
- line, _ := r.ReadLine()
- // fmt.Println(line)
- w := textproto.NewWriter(bufio.NewWriter(conn.Client))
- w.PrintfLine("HELO test.test.com")
- line, _ = r.ReadLine()
- //fmt.Println(line)
- w.PrintfLine("QUIT")
- line, _ = r.ReadLine()
- //fmt.Println("line is:", line)
- expected := "221 2.0.0 Bye"
- if strings.Index(line, expected) != 0 {
- t.Error("expected", expected, "but got:", line)
- }
- wg.Wait() // wait for handleClient to exit
- }
- func TestXClient(t *testing.T) {
- var mainlog log.Logger
- var logOpenError error
- sc := getMockServerConfig()
- sc.XClientOn = true
- mainlog, logOpenError = log.GetLogger(sc.LogFile, "debug")
- if logOpenError != nil {
- mainlog.WithError(logOpenError).Errorf("Failed creating a logger for mock conn [%s]", sc.ListenInterface)
- }
- conn, server := getMockServerConn(sc, t)
- // call the serve.handleClient() func in a goroutine.
- client := NewClient(conn.Server, 1, mainlog, mail.NewPool(5))
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- server.handleClient(client)
- wg.Done()
- }()
- // Wait for the greeting from the server
- r := textproto.NewReader(bufio.NewReader(conn.Client))
- line, _ := r.ReadLine()
- // fmt.Println(line)
- w := textproto.NewWriter(bufio.NewWriter(conn.Client))
- w.PrintfLine("HELO test.test.com")
- line, _ = r.ReadLine()
- //fmt.Println(line)
- w.PrintfLine("XCLIENT ADDR=212.96.64.216 NAME=[UNAVAILABLE]")
- line, _ = r.ReadLine()
- if client.RemoteIP != "212.96.64.216" {
- t.Error("client.RemoteIP should be 212.96.64.216, but got:", client.RemoteIP)
- }
- expected := "250 2.1.0 OK"
- if strings.Index(line, expected) != 0 {
- t.Error("expected", expected, "but got:", line)
- }
- // try malformed input
- w.PrintfLine("XCLIENT c")
- line, _ = r.ReadLine()
- expected = "250 2.1.0 OK"
- if strings.Index(line, expected) != 0 {
- t.Error("expected", expected, "but got:", line)
- }
- w.PrintfLine("QUIT")
- line, _ = r.ReadLine()
- wg.Wait() // wait for handleClient to exit
- }
- // The backend gateway should time out after 1 second because it sleeps for 2 sec.
- // The transaction should wait until finished, and then test to see if we can do
- // a second transaction
- func TestGatewayTimeout(t *testing.T) {
- bcfg := backends.BackendConfig{
- "save_workers_size": 1,
- "save_process": "HeadersParser|Debugger",
- "log_received_mails": true,
- "primary_mail_host": "example.com",
- "gw_save_timeout": "1s",
- "gw_val_rcpt_timeout": "1s",
- "sleep_seconds": 2,
- }
- cfg := &AppConfig{
- LogFile: log.OutputOff.String(),
- AllowedHosts: []string{"grr.la"},
- }
- cfg.BackendConfig = bcfg
- d := Daemon{Config: cfg}
- err := d.Start()
- if err != nil {
- t.Error("server didn't start")
- } else {
- conn, err := net.Dial("tcp", "127.0.0.1:2525")
- if err != nil {
- return
- }
- in := bufio.NewReader(conn)
- str, err := in.ReadString('\n')
- fmt.Fprint(conn, "HELO host\r\n")
- str, err = in.ReadString('\n')
- // perform 2 transactions
- // both should panic.
- for i := 0; i < 2; i++ {
- fmt.Fprint(conn, "MAIL FROM:<[email protected]>r\r\n")
- str, err = in.ReadString('\n')
- fmt.Fprint(conn, "RCPT TO:[email protected]\r\n")
- str, err = in.ReadString('\n')
- fmt.Fprint(conn, "DATA\r\n")
- str, err = in.ReadString('\n')
- fmt.Fprint(conn, "Subject: Test subject\r\n")
- fmt.Fprint(conn, "\r\n")
- fmt.Fprint(conn, "A an email body\r\n")
- fmt.Fprint(conn, ".\r\n")
- str, err = in.ReadString('\n')
- expect := "transaction timeout"
- if strings.Index(str, expect) == -1 {
- t.Error("Expected the reply to have'", expect, "'but got", str)
- }
- }
- _ = str
- d.Shutdown()
- }
- }
- // The processor will panic and gateway should recover from it
- func TestGatewayPanic(t *testing.T) {
- bcfg := backends.BackendConfig{
- "save_workers_size": 1,
- "save_process": "HeadersParser|Debugger",
- "log_received_mails": true,
- "primary_mail_host": "example.com",
- "gw_save_timeout": "2s",
- "gw_val_rcpt_timeout": "2s",
- "sleep_seconds": 1,
- }
- cfg := &AppConfig{
- LogFile: log.OutputOff.String(),
- AllowedHosts: []string{"grr.la"},
- }
- cfg.BackendConfig = bcfg
- d := Daemon{Config: cfg}
- err := d.Start()
- if err != nil {
- t.Error("server didn't start")
- } else {
- conn, err := net.Dial("tcp", "127.0.0.1:2525")
- if err != nil {
- return
- }
- in := bufio.NewReader(conn)
- str, err := in.ReadString('\n')
- fmt.Fprint(conn, "HELO host\r\n")
- str, err = in.ReadString('\n')
- // perform 2 transactions
- // both should timeout. The reason why 2 is because we want to make
- // sure that the client waits until processing finishes, and the
- // timeout event is captured.
- for i := 0; i < 2; i++ {
- fmt.Fprint(conn, "MAIL FROM:<[email protected]>r\r\n")
- str, err = in.ReadString('\n')
- fmt.Fprint(conn, "RCPT TO:[email protected]\r\n")
- str, err = in.ReadString('\n')
- fmt.Fprint(conn, "DATA\r\n")
- str, err = in.ReadString('\n')
- fmt.Fprint(conn, "Subject: Test subject\r\n")
- fmt.Fprint(conn, "\r\n")
- fmt.Fprint(conn, "A an email body\r\n")
- fmt.Fprint(conn, ".\r\n")
- str, err = in.ReadString('\n')
- expect := "storage failed"
- if strings.Index(str, expect) == -1 {
- t.Error("Expected the reply to have'", expect, "'but got", str)
- }
- }
- _ = str
- d.Shutdown()
- }
- }
- func TestAllowsHosts(t *testing.T) {
- s := server{}
- allowedHosts := []string{
- "spam4.me",
- "grr.la",
- "newhost.com",
- "example.*",
- "*.test",
- "wild*.card",
- "multiple*wild*cards.*",
- }
- s.setAllowedHosts(allowedHosts)
- testTable := map[string]bool{
- "spam4.me": true,
- "dont.match": false,
- "example.com": true,
- "another.example.com": false,
- "anything.test": true,
- "wild.card": true,
- "wild.card.com": false,
- "multipleXwildXcards.com": true,
- }
- for host, allows := range testTable {
- if res := s.allowsHost(host); res != allows {
- t.Error(host, ": expected", allows, "but got", res)
- }
- }
- // only wildcard - should match anything
- s.setAllowedHosts([]string{"*"})
- if !s.allowsHost("match.me") {
- t.Error("match.me: expected true but got false")
- }
- // turns off
- s.setAllowedHosts([]string{"."})
- if !s.allowsHost("match.me") {
- t.Error("match.me: expected true but got false")
- }
- // no wilcards
- s.setAllowedHosts([]string{"grr.la", "example.com"})
- }
- // TODO
- // - test github issue #44 and #42
|