1//go:build integration2// +build integration34/*5Maddy Mail Server - Composable all-in-one email server.6Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors78This program is free software: you can redistribute it and/or modify9it under the terms of the GNU General Public License as published by10the Free Software Foundation, either version 3 of the License, or11(at your option) any later version.1213This program is distributed in the hope that it will be useful,14but WITHOUT ANY WARRANTY; without even the implied warranty of15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the16GNU General Public License for more details.1718You should have received a copy of the GNU General Public License19along with this program. If not, see <https://www.gnu.org/licenses/>.20*/2122package tests_test2324import (25 "bufio"26 "errors"27 "flag"28 "io/ioutil"29 "os"30 "os/exec"31 "path/filepath"32 "strings"33 "syscall"34 "testing"35 "time"3637 "github.com/foxcpp/maddy/tests"38)3940var ChasquidExecutable string4142func init() {43 flag.StringVar(&ChasquidExecutable, "integration.chasquid", "chasquid", "path to chasquid executable for interop tests")44}4546const chasquidConf = `smtp_address: "127.0.0.2:44444"47submission_address: "127.0.0.1:44443"4849data_dir: "$ROOT"50mail_log_path: "/dev/null"5152dovecot_auth: true53dovecot_userdb_path: "$AUTH_CLIENT" # needs any Unix socket, not actually used54dovecot_client_path: "$AUTH_CLIENT"55`5657// RSA 1024, valid for *.example.invalid, 127.0.0.1, 127.0.0.2,, 127.0.0.358// until Nov 18 17:13:45 2029 GMT.59const testServerCert = `-----BEGIN CERTIFICATE-----60MIICDzCCAXigAwIBAgIRAJ1x+qCW7L+Hs6sRU8BHmWkwDQYJKoZIhvcNAQELBQAw61EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xOTExMTgxNzEzNDVaFw0yOTExMTUxNzEz62NDVaMBIxEDAOBgNVBAoTB0FjbWUgQ28wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ63AoGBAPINKMyuu3AvzndLDS2/BroA+DRUcAhWPBxMxG1b1BkkHisAZWteKajKmwdO64O13N8HHBRPPOD56AAPLZGNxYLHn6nel7AiH8k40/xC5tDOthqA82+00fwJHDFCnW65oDLOLcO17HulPvfCSWfefc+uee4kajPa+47hutqZH2bGMTXhAgMBAAGjZTBjMA4G66A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAA67MC4GA1UdEQQnMCWCESouZXhhbXBsZS5pbnZhbGlkhwR/AAABhwR/AAAChwR/AAAD68MA0GCSqGSIb3DQEBCwUAA4GBAGRn3C2NbwR4cyQmTRm5jcaqi1kAYyEu6U8Q9PJW69Q15BXMKUTx2lw//QScK9MH2JpKxDuzWDSvaxZMnTxgri2uiplqpe8ydsWj6Wl0q9702XMGJ9LIxTZk5+cyZP2uOolvmSP/q8VFTyk9Udl6KUZPQyoiiDq4rBFUIxUyb+bX71pHkR72-----END CERTIFICATE-----`7374const testServerKey = `-----BEGIN PRIVATE KEY-----75MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAPINKMyuu3AvzndL76DS2/BroA+DRUcAhWPBxMxG1b1BkkHisAZWteKajKmwdOO13N8HHBRPPOD56AAPLZ77GNxYLHn6nel7AiH8k40/xC5tDOthqA82+00fwJHDFCnWoDLOLcO17HulPvfCSWfe78fc+uee4kajPa+47hutqZH2bGMTXhAgMBAAECgYEAgPjSDH3uEdDnSlkLJJzskJ+D79oR58s3R/gvTElSCg2uSLzo3ffF4oBHAwOqxMpabdvz8j5mSdne7Gkp9qx72TtEG280wt6uX1tZhm2UTAkInH8IQDthj98P8vAWQsS6HHEIMErsrW2CyUrAt/+o1BRg/hWW81zixA3CLTthhZTJkaUCECQQD5EM16UcTAKfhr3IZppgq+ZsAOMkeCl3XVV9gHo32i82DL6UFAb27BAYyjfcZB1fPou4RszX0Ryu9yU0P5qm6N47AkEA+MpdAPkaPziY0ok483e9Tcee6P0mIR+/AHk9GliVX2P74DDoOHyMXOSRBwdb+z2tYjrdjkNEL1Txe+sHny84k/EukwJBAOBqlmqPwNNRPeiaRHZvSSD0XjqsbSirJl48D4gadPoNt66fOQNGAt8D85Xj/z6U9HgQdiq/IOFmVEhT5FzSh1jL8CQQD3Myth8iGQO84tM0c6U3CWfuHMqsEv860XnV+HNAmHdLMqOa4joi1dh4ZKs5dDdi828UJ/PnsbhI1FEWzLSpJvWdAkAkVWqf87AC/TvWvEZLA6Z5CllyNzZJ7XvtIaNOosxHDolyZ1HMWMlfEb2K2ZXWLy5foKPeoY88Xi3olS9rB0J+Rvjz89-----END PRIVATE KEY-----`9091func runChasquid(t *testing.T, authClientPath string) (string, *exec.Cmd) {92 tempDir := t.TempDir()93 t.Log("Using", tempDir)9495 chasquidConf := strings.NewReplacer(96 "$ROOT", tempDir,97 "$AUTH_CLIENT", authClientPath).Replace(chasquidConf)98 err := ioutil.WriteFile(filepath.Join(tempDir, "chasquid.conf"), []byte(chasquidConf), os.ModePerm)99 if err != nil {100 t.Fatal(err)101 }102 if err := os.MkdirAll(filepath.Join(tempDir, "certs", "example.org"), os.ModePerm); err != nil {103 t.Fatal(err)104 }105 err = ioutil.WriteFile(filepath.Join(tempDir, "certs", "example.org", "fullchain.pem"), []byte(testServerCert), os.ModePerm)106 if err != nil {107 t.Fatal(err)108 }109 err = ioutil.WriteFile(filepath.Join(tempDir, "certs", "example.org", "privkey.pem"), []byte(testServerKey), os.ModePerm)110 if err != nil {111 t.Fatal(err)112 }113 if err := os.MkdirAll(filepath.Join(tempDir, "domains", "example.org"), os.ModePerm); err != nil {114 t.Fatal(err)115 }116117 err = ioutil.WriteFile(filepath.Join(tempDir, "chasquid.conf"), []byte(chasquidConf), os.ModePerm)118 if err != nil {119 t.Fatal(err)120 }121122 cmd := exec.Command(ChasquidExecutable, "-v=2", "-config_dir", tempDir)123 t.Log("Launching", cmd.String())124 stderr, err := cmd.StderrPipe()125 if err != nil {126 t.Fatal(err)127 }128129 if err := cmd.Start(); err != nil {130 t.Fatal(err)131 }132133 ready := make(chan struct{}, 1)134135 go func() {136 scnr := bufio.NewScanner(stderr)137 for scnr.Scan() {138 line := scnr.Text()139140 // One of messages printed near completing initialization.141 if strings.Contains(line, "Loading certificates") {142 time.Sleep(1 * time.Second)143 ready <- struct{}{}144 }145146 t.Log("chasquid:", line)147 }148 if err := scnr.Err(); err != nil {149 t.Log("stderr I/O error:", err)150 }151 }()152153 <-ready154155 return tempDir, cmd156}157158func cleanChasquid(t *testing.T, tempDir string, cmd *exec.Cmd) {159 cmd.Process.Signal(syscall.SIGTERM)160 os.RemoveAll(tempDir)161}162163func TestSASLServerWithChasquid(tt *testing.T) {164 tt.Parallel()165166 _, err := exec.LookPath(ChasquidExecutable)167 if err != nil {168 if errors.Is(err, exec.ErrNotFound) {169 tt.Skip("No chasquid executable found, skipping interop. tests")170 }171 tt.Fatal(err)172 }173174 t := tests.NewT(tt)175 t.DNS(nil)176 t.Port("smtp")177 t.Config(`178 dovecot_sasld unix://{env:TEST_STATE_DIR}/auth.sock {179 auth pass_table static {180 # tester@example.org:123456181 entry tester@example.org "bcrypt:$2a$04$0SaXE/WOMBOfk5jyaKjo.OHkioRljdhMznLnYCg1nrksu9iLd51Ri"182 }183 }`)184 t.Run(1)185 defer t.Close()186187 chasquidDir, cmd := runChasquid(tt, filepath.Join(t.StateDir(), "auth.sock"))188 defer cleanChasquid(tt, chasquidDir, cmd)189190 c := t.ConnUnnamed(44443)191 defer c.Close()192 c.SMTPNegotation("localhost", nil, nil)193 c.Writeln("STARTTLS")194 c.ExpectPattern("220 *")195 c.TLS()196 c.Writeln("AUTH PLAIN AHRlc3RAZXhhbXBsZS5vcmcAMTIzNDU2") // 0x00 test@example.org 0x00 123456 (invalid user)197 c.ExpectPattern("535 *")198 c.Writeln("AUTH PLAIN AHRlc3RlckBleGFtcGxlLm9yZwAxMjM0NQ==") // 0x00 tester 0x00 12345 (invalid password)199 c.ExpectPattern("535 *")200 c.Writeln("AUTH PLAIN AHRlc3RlckBleGFtcGxlLm9yZwAxMjM0NTY=") // 0x00 tester 0x00 123456201 c.ExpectPattern("235 *")202}