...
 
Commits (5)
......@@ -36,6 +36,12 @@ pepe: jose
flowers: [email protected], [email protected]
```
Destination addresses can be for a remote domain as well. In that case, the
email will be forwarded using
[sender rewriting](https://en.wikipedia.org/wiki/Sender_Rewriting_Scheme).
While the content of the message will not be changed, the envelope sender will
be the constructed from the alias user.
User names cannot contain spaces, ":" or commas, for parsing reasons. This is
a tradeoff between flexibility and keeping the file format easy to edit for
people. User names will be normalized internally to lower-case. UTF-8 is
......
......@@ -51,5 +51,23 @@ If chasquid can't find them, the paths can be set with the
`dovecot_userdb_path` and `dovecot_client_path` options.
## Troubleshooting
Dovecot authentication can be tricky to troubleshoot.
If you think it is not working as it should, or chasquid isn't correctly
talking with it, the easiest way to check is to [increase dovecot auth logging
verbosity](https://doc.dovecot.org/admin_manual/logging/?highlight=logging#logging-verbosity):
```
auth_verbose = yes
auth_debug = yes
```
One common gotcha is when dovecot is set up to use `user` instead of
`[email protected]`. In that case you can try setting `auth_username_format = %n` to
make it ignore the domain if present.
[dovecot]: https://dovecot.org
[chasquid]: https://blitiri.com.ar/p/chasquid
......@@ -280,7 +280,11 @@ loop:
}
if err != nil {
c.tr.Errorf("exiting with error: %v", err)
if err == io.EOF {
c.tr.Debugf("client closed the connection")
} else {
c.tr.Errorf("exiting with error: %v", err)
}
}
}
......
......@@ -85,34 +85,33 @@ func Fuzz(data []byte) int {
tconn := textproto.NewConn(conn)
defer tconn.Close()
in_data := false
scanner := bufio.NewScanner(bytes.NewBuffer(data))
for scanner.Scan() {
line := scanner.Text()
cmd := strings.TrimSpace(strings.ToUpper(line))
// Skip STARTTLS if it happens on a non-TLS connection - the jump is
// not going to happen via fuzzer, it will just cause a timeout (which
// is considered a crash).
if strings.TrimSpace(strings.ToUpper(line)) == "STARTTLS" && !mode.TLS {
if cmd == "STARTTLS" && !mode.TLS {
continue
}
if err = tconn.PrintfLine(line); err != nil {
break
}
if in_data {
if line == "." {
in_data = false
} else {
continue
}
}
if _, _, err = tconn.ReadResponse(-1); err != nil {
break
}
in_data = strings.HasPrefix(strings.ToUpper(line), "DATA")
if cmd == "DATA" {
// We just sent DATA and got a response; send the contents.
err = exchangeData(scanner, tconn)
if err != nil {
break
}
}
}
if (err != nil && err != io.EOF) || scanner.Err() != nil {
return 1
......@@ -121,6 +120,22 @@ func Fuzz(data []byte) int {
return 0
}
func exchangeData(scanner *bufio.Scanner, tconn *textproto.Conn) error {
for scanner.Scan() {
line := scanner.Text()
if err := tconn.PrintfLine(line); err != nil {
return err
}
if line == "." {
break
}
}
// Read the "." response.
_, _, err := tconn.ReadResponse(-1)
return err
}
//
// === Test environment ===
//
......@@ -216,7 +231,7 @@ func waitForServer(addr string) {
func init() {
flag.Parse()
log.Default.Level = log.Debug
log.Default.Level = log.Error
// Generate certificates in a temporary directory.
tmpDir, err := ioutil.TempDir("", "chasquid_smtpsrv_fuzz:")
......
......@@ -67,6 +67,7 @@ func GetFreePort() string {
return l.Addr().String()
}
// WaitFor f to return true (returns true), or d to pass (returns false).
func WaitFor(f func() bool, d time.Duration) bool {
start := time.Now()
for time.Since(start) < d {
......@@ -78,23 +79,24 @@ func WaitFor(f func() bool, d time.Duration) bool {
return false
}
type DeliverRequest struct {
type deliverRequest struct {
From string
To string
Data []byte
}
// Courier for test purposes. Never fails, and always remembers everything.
// TestCourier never fails, and always remembers everything.
type TestCourier struct {
wg sync.WaitGroup
Requests []*DeliverRequest
ReqFor map[string]*DeliverRequest
Requests []*deliverRequest
ReqFor map[string]*deliverRequest
sync.Mutex
}
// Deliver the given mail (saving it in tc.Requests).
func (tc *TestCourier) Deliver(from string, to string, data []byte) (error, bool) {
defer tc.wg.Done()
dr := &DeliverRequest{from, to, data}
dr := &deliverRequest{from, to, data}
tc.Lock()
tc.Requests = append(tc.Requests, dr)
tc.ReqFor[to] = dr
......@@ -102,10 +104,12 @@ func (tc *TestCourier) Deliver(from string, to string, data []byte) (error, bool
return nil, false
}
// Expect i mails to be delivered.
func (tc *TestCourier) Expect(i int) {
tc.wg.Add(i)
}
// Wait until all mails have been delivered.
func (tc *TestCourier) Wait() {
tc.wg.Wait()
}
......@@ -113,7 +117,7 @@ func (tc *TestCourier) Wait() {
// NewTestCourier returns a new, empty TestCourier instance.
func NewTestCourier() *TestCourier {
return &TestCourier{
ReqFor: map[string]*DeliverRequest{},
ReqFor: map[string]*deliverRequest{},
}
}
......@@ -123,5 +127,5 @@ func (c dumbCourier) Deliver(from string, to string, data []byte) (error, bool)
return nil, false
}
// Dumb courier, for when we just don't care about the result.
// DumbCourier always succeeds delivery, and ignores everything.
var DumbCourier = dumbCourier{}