1//go:build integration && (darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris)2// +build integration3// +build darwin dragonfly freebsd linux netbsd openbsd solaris45/*6Maddy Mail Server - Composable all-in-one email server.7Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors89This program is free software: you can redistribute it and/or modify10it under the terms of the GNU General Public License as published by11the Free Software Foundation, either version 3 of the License, or12(at your option) any later version.1314This program is distributed in the hope that it will be useful,15but WITHOUT ANY WARRANTY; without even the implied warranty of16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the17GNU General Public License for more details.1819You should have received a copy of the GNU General Public License20along with this program. If not, see <https://www.gnu.org/licenses/>.21*/2223// only posix systems ^2425package tests_test2627import (28 "bufio"29 "errors"30 "flag"31 "io/ioutil"32 "os"33 "os/exec"34 "os/user"35 "path/filepath"36 "strings"37 "syscall"38 "testing"39 "time"4041 "github.com/foxcpp/maddy/tests"42)4344var DovecotExecutable string4546func init() {47 flag.StringVar(&DovecotExecutable, "integration.dovecot", "dovecot", "path to dovecot executable for interop tests")48}4950const dovecotConf = `base_dir = $ROOT/run/51state_dir = $ROOT/lib/52log_path = /dev/stderr53ssl = no5455default_internal_user = $USER56default_internal_group = $GROUP57default_login_user = $USER5859passdb {60 driver = passwd-file61 args = $ROOT/passwd62}6364userdb {65 driver = passwd-file66 args = $ROOT/passwd67}6869service auth {70 unix_listener auth {71 mode = 066672 }73}7475# Dovecot refuses to start without protocols, so we need to give it one.76protocols = imap7778service imap-login {79 chroot =80 inet_listener imap {81 address = 127.0.0.182 port = 083 }84}8586service anvil {87 chroot =88}8990# Turn on debugging information, to help troubleshooting issues.91auth_verbose = yes92auth_debug = yes93auth_debug_passwords = yes94auth_verbose_passwords = yes95mail_debug = yes96`9798const dovecotPasswd = `tester:{plain}123456:1000:1000::/home/user`99100func runDovecot(t *testing.T) (string, *exec.Cmd) {101 dovecotExec, err := exec.LookPath(DovecotExecutable)102 if err != nil {103 if errors.Is(err, exec.ErrNotFound) {104 t.Skip("No Dovecot executable found, skipping interop. tests")105 }106 t.Fatal(err)107 }108109 tempDir := t.TempDir()110111 curUser, err := user.Current()112 if err != nil {113 t.Fatal(err)114 }115 curGroup, err := user.LookupGroupId(curUser.Gid)116 if err != nil {117 t.Fatal(err)118 }119120 dovecotConf := strings.NewReplacer(121 "$ROOT", tempDir,122 "$USER", curUser.Username,123 "$GROUP", curGroup.Name).Replace(dovecotConf)124 err = ioutil.WriteFile(filepath.Join(tempDir, "dovecot.conf"), []byte(dovecotConf), os.ModePerm)125 if err != nil {126 t.Fatal(err)127 }128 err = ioutil.WriteFile(filepath.Join(tempDir, "passwd"), []byte(dovecotPasswd), os.ModePerm)129 if err != nil {130 t.Fatal(err)131 }132133 cmd := exec.Command(dovecotExec, "-F", "-c", filepath.Join(tempDir, "dovecot.conf"))134 stderr, err := cmd.StderrPipe()135 if err != nil {136 t.Fatal(err)137 }138139 if err := cmd.Start(); err != nil {140 t.Fatal(err)141 }142143 ready := make(chan struct{}, 1)144145 go func() {146 scnr := bufio.NewScanner(stderr)147 for scnr.Scan() {148 line := scnr.Text()149150 // One of messages printed near completing initialization.151 if strings.Contains(line, "starting up for imap") {152 time.Sleep(500*time.Millisecond)153 ready <- struct{}{}154 }155156 t.Log("dovecot:", line)157 }158 if err := scnr.Err(); err != nil {159 t.Log("stderr I/O error:", err)160 }161 }()162163 <-ready164165 return tempDir, cmd166}167168func cleanDovecot(t *testing.T, tempDir string, cmd *exec.Cmd) {169 cmd.Process.Signal(syscall.SIGTERM)170 if !t.Failed() {171 os.RemoveAll(tempDir)172 } else {173 t.Log("Dovecot directory is not deleted:", tempDir)174 }175}176177func TestDovecotSASLClient(tt *testing.T) {178 tt.Parallel()179180 dovecotDir, cmd := runDovecot(tt)181 defer cleanDovecot(tt, dovecotDir, cmd)182183 t := tests.NewT(tt)184 t.DNS(nil)185 t.Port("smtp")186 t.Env("DOVECOT_SASL_SOCK=" + filepath.Join(dovecotDir, "run", "auth-client"))187 t.Config(`188 smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {189 hostname mx.maddy.test190 tls off191 auth dovecot_sasl unix://{env:DOVECOT_SASL_SOCK}192 deliver_to dummy193 }`)194 t.Run(1)195 defer t.Close()196197 c := t.Conn("smtp")198 defer c.Close()199 c.SMTPNegotation("localhost", nil, nil)200 c.Writeln("AUTH PLAIN AHRlc3QAMTIzNDU2") // 0x00 test 0x00 123456 (invalid user)201 c.ExpectPattern("535 *")202 c.Writeln("AUTH PLAIN AHRlc3RlcgAxMjM0NQ==") // 0x00 tester 0x00 12345 (invalid password)203 c.ExpectPattern("535 *")204 c.Writeln("AUTH PLAIN AHRlc3RlcgAxMjM0NTY=") // 0x00 tester 0x00 123456205 c.ExpectPattern("235 *")206}