git » chasquid » main » tree

[main] / internal / smtpsrv / fuzz_test.go

// Fuzz testing for package smtpsrv.  Based on server_test.
package smtpsrv

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"fmt"
	"net"
	"net/textproto"
	"strings"
	"testing"
)

func fuzzConnection(t *testing.T, modeI int, data []byte) {
	var mode SocketMode
	addr := ""
	switch modeI {
	case 0:
		mode = ModeSMTP
		addr = smtpAddr
	case 1:
		mode = ModeSubmission
		addr = submissionAddr
	case 2:
		mode = ModeSubmissionTLS
		addr = submissionTLSAddr
	default:
		mode = ModeSMTP
		addr = smtpAddr
	}

	var err error
	var conn net.Conn
	if mode.TLS {
		conn, err = tls.Dial("tcp", addr, tlsConfig)
	} else {
		conn, err = net.Dial("tcp", addr)
	}
	if err != nil {
		panic(fmt.Errorf("failed to dial: %v", err))
	}
	defer conn.Close()

	tconn := textproto.NewConn(conn)
	defer tconn.Close()

	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 cmd == "STARTTLS" && !mode.TLS {
			continue
		}

		if err = tconn.PrintfLine(line); err != nil {
			break
		}

		if _, _, err = tconn.ReadResponse(-1); err != nil {
			break
		}

		if cmd == "DATA" {
			// We just sent DATA and got a response; send the contents.
			err = exchangeData(scanner, tconn)
			if err != nil {
				break
			}
		}
	}
}

func FuzzConnection(f *testing.F) {
	f.Fuzz(fuzzConnection)
}

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
}