Sfoglia il codice sorgente

Fixes #35 (#36)

- added limits to RCPT TO and MAIL FROM commands as per SMTP RFC
- added a whole heap of tests to cover all limits & some edge behaviours
- Behaviour change: "rcpt to" command will return error if not allowed host (relay not allowed)
- Behaviour change: HELO resets the transaction, according to SMTP rfc
- Bug fix: DATA state will now print error to client if maximum exceeded
Guerrilla Mail 8 anni fa
parent
commit
71d07c8862
6 ha cambiato i file con 530 aggiunte e 75 eliminazioni
  1. 1 1
      backends/guerrilla_db_redis.go
  2. 17 4
      client.go
  3. 0 3
      pool.go
  4. 52 44
      server.go
  5. 451 22
      tests/guerrilla_test.go
  6. 9 1
      util.go

+ 1 - 1
backends/guerrilla_db_redis.go

@@ -92,7 +92,7 @@ func (g *GuerrillaDBAndRedisBackend) Process(mail *guerrilla.Envelope) guerrilla
 	// place on the channel so that one of the save mail workers can pick it up
 	// TODO: support multiple recipients
 	savedNotify := make(chan *saveStatus)
-	g.saveMailChan <- &savePayload{mail, from, to[0], savedNotify}
+	g.saveMailChan <- &savePayload{mail, from, &to[0], savedNotify}
 	// wait for the save to complete
 	// or timeout
 	select {

+ 17 - 4
client.go

@@ -50,7 +50,7 @@ type Envelope struct {
 	// Sender
 	MailFrom *EmailAddress
 	// Recipients
-	RcptTo  []*EmailAddress
+	RcptTo  []EmailAddress
 	Data    string
 	Subject string
 	TLS     bool
@@ -73,9 +73,19 @@ func (c *client) responseAdd(r string) {
 	c.response = c.response + r + "\r\n"
 }
 
-func (c *client) reset() {
+func (c *client) resetTransaction() {
 	c.MailFrom = &EmailAddress{}
-	c.RcptTo = []*EmailAddress{}
+	c.RcptTo = []EmailAddress{}
+	c.Data = ""
+	c.Subject = ""
+}
+
+func (c *client) isInTransaction() bool {
+	isMailFromEmpty := *c.MailFrom == (EmailAddress{})
+	if isMailFromEmpty {
+		return false
+	}
+	return true
 }
 
 func (c *client) kill() {
@@ -106,7 +116,10 @@ func (c *client) scanSubject(reply string) {
 func (c *client) setTimeout(t time.Duration) {
 	defer c.timeoutMu.Unlock()
 	c.timeoutMu.Lock()
-	c.conn.SetDeadline(time.Now().Add(t * time.Second))
+	if c.conn != nil {
+		c.conn.SetDeadline(time.Now().Add(t * time.Second))
+	}
+
 }
 
 func (c *client) init(conn net.Conn, clientID uint64) {

+ 0 - 3
pool.go

@@ -18,8 +18,6 @@ type Poolable interface {
 	setTimeout(t time.Duration)
 	// set a new connection and client id
 	init(c net.Conn, clientID uint64)
-	// reset any internal state
-	reset()
 	// get a unique id
 	getID() uint64
 }
@@ -140,7 +138,6 @@ func (p *Pool) Borrow(conn net.Conn, clientID uint64) (Poolable, error) {
 func (p *Pool) Return(c Poolable) {
 	select {
 	case p.pool <- c:
-		c.reset()
 	default:
 		// hasta la vista, baby...
 	}

+ 52 - 44
server.go

@@ -9,8 +9,6 @@ import (
 	"strings"
 	"time"
 
-	"errors"
-
 	"runtime"
 
 	log "github.com/Sirupsen/logrus"
@@ -23,6 +21,14 @@ const (
 	CommandLineMaxLength = 1024
 	// Number of allowed unrecognized commands before we terminate the connection
 	MaxUnrecognizedCommands = 5
+	// The maximum total length of a reverse-path or forward-path is 256
+	RFC2821LimitPath = 256
+	// The maximum total length of a user name or other local-part is 64
+	RFC2832LimitLocalPart = 64
+	//The maximum total length of a domain name or number is 255
+	RFC2821LimitDomain = 255
+	// The minimum total number of recipients that must be buffered is 100
+	RFC2821LimitRecipients = 100
 )
 
 // Server listens for SMTP clients on the port specified in its config
@@ -177,12 +183,14 @@ func (server *server) read(client *client) (string, error) {
 		client.setTimeout(server.timeout)
 		reply, err = client.bufin.ReadString('\n')
 		input = input + reply
-		if client.state == ClientData && reply != "" {
-			// Extract the subject while we're at it
-			client.scanSubject(reply)
-		}
-		if int64(len(input)) > server.config.MaxSize {
-			return input, fmt.Errorf("Maximum DATA size exceeded (%d)", server.config.MaxSize)
+		if err == nil && client.state == ClientData {
+			if reply != "" {
+				// Extract the subject while we're at it
+				client.scanSubject(reply)
+			}
+			if int64(len(input)) > server.config.MaxSize {
+				return input, fmt.Errorf("Maximum DATA size exceeded (%d)", server.config.MaxSize)
+			}
 		}
 		if err != nil {
 			break
@@ -284,36 +292,49 @@ func (server *server) handleClient(client *client) {
 			switch {
 			case strings.Index(cmd, "HELO") == 0:
 				client.Helo = strings.Trim(input[4:], " ")
+				client.resetTransaction()
 				client.responseAdd(helo)
 
 			case strings.Index(cmd, "EHLO") == 0:
 				client.Helo = strings.Trim(input[4:], " ")
+				client.resetTransaction()
 				client.responseAdd(ehlo + messageSize + pipelining + advertiseTLS + help)
 
 			case strings.Index(cmd, "HELP") == 0:
 				client.responseAdd("214 OK\r\n" + messageSize + pipelining + advertiseTLS + help)
 
 			case strings.Index(cmd, "MAIL FROM:") == 0:
-				client.reset()
+				if client.isInTransaction() {
+					client.responseAdd("503 Error: nested MAIL command")
+					break
+				}
 				from, err := extractEmail(input[10:])
 				if err != nil {
-					client.responseAdd("550 Error: Invalid Sender")
+					client.responseAdd(err.Error())
 				} else {
 					client.MailFrom = from
 					client.responseAdd("250 OK")
 				}
 
 			case strings.Index(cmd, "RCPT TO:") == 0:
+				if len(client.RcptTo) > RFC2821LimitRecipients {
+					client.responseAdd("452 Too many recipients")
+					break
+				}
 				to, err := extractEmail(input[8:])
 				if err != nil {
-					client.responseAdd("550 Error: Invalid Recipient")
+					client.responseAdd(err.Error())
 				} else {
-					client.RcptTo = append(client.RcptTo, to)
-					client.responseAdd("250 OK")
+					if !server.allowsHost(to.Host) {
+						client.responseAdd("454 Error: Relay access denied: " + to.Host)
+					} else {
+						client.RcptTo = append(client.RcptTo, *to)
+						client.responseAdd("250 OK")
+					}
 				}
 
 			case strings.Index(cmd, "RSET") == 0:
-				client.reset()
+				client.resetTransaction()
 				client.responseAdd("250 OK")
 
 			case strings.Index(cmd, "VRFY") == 0:
@@ -327,6 +348,14 @@ func (server *server) handleClient(client *client) {
 				client.kill()
 
 			case strings.Index(cmd, "DATA") == 0:
+				if client.MailFrom.isEmpty() {
+					client.responseAdd("503 Error: No sender")
+					break
+				}
+				if len(client.RcptTo) == 0 {
+					client.responseAdd("503 Error: No recipients")
+					break
+				}
 				client.responseAdd("354 Enter message, ending with '.' on a line by itself")
 				client.state = ClientData
 
@@ -360,37 +389,25 @@ func (server *server) handleClient(client *client) {
 					client.responseAdd("451 Error: " + err.Error())
 				}
 				log.WithError(err).Warn("Error reading data")
-				continue
+				break
 			}
+
+			res := server.backend.Process(client.Envelope)
+			if res.Code() < 300 {
+				client.messagesSent++
+			}
+			client.responseAdd(res.String())
 			client.state = ClientCmd
 			if server.isShuttingDown() {
 				client.state = ClientShutdown
 			}
-
-			if client.MailFrom.isEmpty() {
-				client.responseAdd("550 Error: No sender")
-				continue
-			}
-			if len(client.RcptTo) == 0 {
-				client.responseAdd("550 Error: No recipients")
-				continue
-			}
-
-			if rcptErr := server.checkRcpt(client.RcptTo); rcptErr == nil {
-				res := server.backend.Process(client.Envelope)
-				if res.Code() < 300 {
-					client.messagesSent++
-				}
-				client.responseAdd(res.String())
-			} else {
-				client.responseAdd("550 Error: " + rcptErr.Error())
-			}
+			client.resetTransaction()
 
 		case ClientStartTLS:
 			if !client.TLS && server.config.StartTLSOn {
 				if server.upgradeToTLS(client) {
 					advertiseTLS = ""
-					client.reset()
+					client.resetTransaction()
 				}
 			}
 			// change to command state
@@ -412,12 +429,3 @@ func (server *server) handleClient(client *client) {
 
 	}
 }
-
-func (s *server) checkRcpt(RcptTo []*EmailAddress) error {
-	for _, rcpt := range RcptTo {
-		if !s.allowsHost(rcpt.Host) {
-			return errors.New("550 Error: Host not allowed: " + rcpt.Host)
-		}
-	}
-	return nil
-}

+ 451 - 22
tests/guerrilla_test.go

@@ -129,6 +129,32 @@ func setupCerts(c *TestConfig) {
 	}
 }
 
+func connect(serverIndex int, deadline time.Duration) (net.Conn, *bufio.Reader, error) {
+	var bufin *bufio.Reader
+
+	conn, err := net.Dial("tcp", config.Servers[serverIndex].ListenInterface)
+	if err != nil {
+		// handle error
+		//t.Error("Cannot dial server", config.Servers[0].ListenInterface)
+		return conn, bufin, errors.New("Cannot dial server: " + config.Servers[serverIndex].ListenInterface + "," + err.Error())
+	}
+	bufin = bufio.NewReader(conn)
+
+	// should be ample time to complete the test
+	conn.SetDeadline(time.Now().Add(time.Duration(time.Second * deadline)))
+	// read greeting, ignore it
+	_, err = bufin.ReadString('\n')
+	return conn, bufin, err
+}
+
+func command(conn net.Conn, bufin *bufio.Reader, command string) (reply string, err error) {
+	_, err = fmt.Fprintln(conn, command+"\r")
+	if err == nil {
+		return bufin.ReadString('\n')
+	}
+	return "", err
+}
+
 // Testing start and stop of server
 func TestStart(t *testing.T) {
 	if initErr != nil {
@@ -181,6 +207,7 @@ func TestStart(t *testing.T) {
 		}
 
 	}
+	// don't forget to reset
 	logBuffer.Reset()
 	logIn.Reset(&logBuffer)
 
@@ -258,6 +285,7 @@ func TestGreeting(t *testing.T) {
 			t.Error("Server did not handle any clients")
 		}
 	}
+	// don't forget to reset
 	logBuffer.Reset()
 	logIn.Reset(&logBuffer)
 
@@ -273,45 +301,30 @@ func TestShutDown(t *testing.T) {
 		t.FailNow()
 	}
 	if startErrors := app.Start(); startErrors == nil {
-		conn, err := net.Dial("tcp", config.Servers[0].ListenInterface)
+		conn, bufin, err := connect(0, 20)
 		if err != nil {
 			// handle error
-			t.Error("Cannot dial server", config.Servers[0].ListenInterface)
-		}
-		bufin := bufio.NewReader(conn)
-
-		// should be ample time to complete the test
-		conn.SetDeadline(time.Now().Add(time.Duration(time.Second * 20)))
-		// read greeting, ignore it
-		_, err = bufin.ReadString('\n')
-
-		if err != nil {
-			t.Error(err)
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
 			t.FailNow()
 		} else {
 			// client goes into command state
-			n, err := fmt.Fprintln(conn, "HELO localtester\r")
-			if err != nil {
-				log.WithError(err).Info("n was %d", n)
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
 			}
-			_, err = bufin.ReadString('\n')
 
 			// do a shutdown while the client is connected & in client state
 			go app.Shutdown()
 
 			// issue a command while shutting down
-			n, err = fmt.Fprintln(conn, "HELP\r")
+			response, err := command(conn, bufin, "HELP")
 			if err != nil {
-				log.WithError(err).Info("n was %d", n)
+				t.Error("Help command failed", err.Error())
 			}
-			response, err := bufin.ReadString('\n')
-			//fmt.Println(response)
 			expected := "421 Server is shutting down. Please try again later. Sayonara!"
 			if strings.Index(response, expected) != 0 {
-				t.Error("Server did not shut down with", expected)
+				t.Error("Server did not shut down with", expected, ", it said:"+response)
 			}
 			time.Sleep(time.Millisecond * 250) // let server to close
-
 		}
 
 		conn.Close()
@@ -325,6 +338,7 @@ func TestShutDown(t *testing.T) {
 			t.FailNow()
 		}
 	}
+	// assuming server has shutdown by now
 	logOut.Flush()
 	if read, err := ioutil.ReadAll(logIn); err == nil {
 		logOutput := string(read)
@@ -333,7 +347,422 @@ func TestShutDown(t *testing.T) {
 			t.Error("Server did not handle any clients")
 		}
 	}
+	// don't forget to reset
+	logBuffer.Reset()
+	logIn.Reset(&logBuffer)
+
+}
+
+// add more than 100 recipients, it should fail at 101
+func TestRFC2821LimitRecipients(t *testing.T) {
+	if initErr != nil {
+		t.Error(initErr)
+		t.FailNow()
+	}
+	if startErrors := app.Start(); startErrors == nil {
+		conn, bufin, err := connect(0, 20)
+		if err != nil {
+			// handle error
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
+			t.FailNow()
+		} else {
+			// client goes into command state
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+
+			for i := 0; i < 101; i++ {
+				if _, err := command(conn, bufin, fmt.Sprintf("RCPT TO:test%[email protected]", i)); err != nil {
+					t.Error("RCPT TO", err.Error())
+					break
+				}
+			}
+			response, err := command(conn, bufin, "RCPT TO:[email protected]")
+			if err != nil {
+				t.Error("rcpt command failed", err.Error())
+			}
+			expected := "452 Too many recipients"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+		}
+
+		conn.Close()
+		app.Shutdown()
+
+	} else {
+		if startErrors := app.Start(); startErrors != nil {
+			for _, err := range startErrors {
+				t.Error(err)
+			}
+			app.Shutdown()
+			t.FailNow()
+		}
+	}
+
+	logOut.Flush()
+
+	// don't forget to reset
+	logBuffer.Reset()
+	logIn.Reset(&logBuffer)
+}
+
+// RCPT TO & MAIL FROM with 64 chars in local part, it should fail at 65
+func TestRFC2832LimitLocalPart(t *testing.T) {
+	if initErr != nil {
+		t.Error(initErr)
+		t.FailNow()
+	}
+
+	if startErrors := app.Start(); startErrors == nil {
+		conn, bufin, err := connect(0, 20)
+		if err != nil {
+			// handle error
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
+			t.FailNow()
+		} else {
+			// client goes into command state
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+			// repeat > 64 characters in local part
+			response, err := command(conn, bufin, fmt.Sprintf("RCPT TO:%[email protected]", strings.Repeat("a", 65)))
+			if err != nil {
+				t.Error("rcpt command failed", err.Error())
+			}
+			expected := "501 Local part too long"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+			// what about if it's exactly 64?
+			// repeat > 64 characters in local part
+			response, err = command(conn, bufin, fmt.Sprintf("RCPT TO:%[email protected]", strings.Repeat("a", 64)))
+			if err != nil {
+				t.Error("rcpt command failed", err.Error())
+			}
+			expected = "250 OK"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+		}
+
+		conn.Close()
+		app.Shutdown()
+
+	} else {
+		if startErrors := app.Start(); startErrors != nil {
+			for _, err := range startErrors {
+				t.Error(err)
+			}
+			app.Shutdown()
+			t.FailNow()
+		}
+	}
+
+	logOut.Flush()
+
+	// don't forget to reset
+	logBuffer.Reset()
+	logIn.Reset(&logBuffer)
+}
+
+//RFC2821LimitPath fail if path > 256 but different error if below
+
+func TestRFC2821LimitPath(t *testing.T) {
+	if initErr != nil {
+		t.Error(initErr)
+		t.FailNow()
+	}
+	if startErrors := app.Start(); startErrors == nil {
+		conn, bufin, err := connect(0, 20)
+		if err != nil {
+			// handle error
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
+			t.FailNow()
+		} else {
+			// client goes into command state
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+			// repeat > 256 characters in local part
+			response, err := command(conn, bufin, fmt.Sprintf("RCPT TO:%[email protected]", strings.Repeat("a", 257-7)))
+			if err != nil {
+				t.Error("rcpt command failed", err.Error())
+			}
+			expected := "501 Path too long"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+			// what about if it's exactly 256?
+			response, err = command(conn, bufin,
+				fmt.Sprintf("RCPT TO:%s@%s.la", strings.Repeat("a", 64), strings.Repeat("b", 257-5-64)))
+			if err != nil {
+				t.Error("rcpt command failed", err.Error())
+			}
+			expected = "454 Error: Relay access denied"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+		}
+		conn.Close()
+		app.Shutdown()
+	} else {
+		if startErrors := app.Start(); startErrors != nil {
+			for _, err := range startErrors {
+				t.Error(err)
+			}
+			app.Shutdown()
+			t.FailNow()
+		}
+	}
+	logOut.Flush()
+	// don't forget to reset
+	logBuffer.Reset()
+	logIn.Reset(&logBuffer)
+}
+
+// RFC2821LimitDomain 501 Domain cannot exceed 255 characters
+func TestRFC2821LimitDomain(t *testing.T) {
+	if initErr != nil {
+		t.Error(initErr)
+		t.FailNow()
+	}
+	if startErrors := app.Start(); startErrors == nil {
+		conn, bufin, err := connect(0, 20)
+		if err != nil {
+			// handle error
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
+			t.FailNow()
+		} else {
+			// client goes into command state
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+			// repeat > 64 characters in local part
+			response, err := command(conn, bufin, fmt.Sprintf("RCPT TO:a@%s.l", strings.Repeat("a", 255-2)))
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			expected := "501 Path too long"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+			// what about if it's exactly 255?
+			response, err = command(conn, bufin,
+				fmt.Sprintf("RCPT TO:a@%s.la", strings.Repeat("b", 255-4)))
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			expected = "454 Error: Relay access denied"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+		}
+		conn.Close()
+		app.Shutdown()
+	} else {
+		if startErrors := app.Start(); startErrors != nil {
+			for _, err := range startErrors {
+				t.Error(err)
+			}
+			app.Shutdown()
+			t.FailNow()
+		}
+	}
+	logOut.Flush()
+	// don't forget to reset
 	logBuffer.Reset()
 	logIn.Reset(&logBuffer)
+}
 
+// It should error when MAIL FROM was given twice
+func TestNestedMailCmd(t *testing.T) {
+	if initErr != nil {
+		t.Error(initErr)
+		t.FailNow()
+	}
+	if startErrors := app.Start(); startErrors == nil {
+		conn, bufin, err := connect(0, 20)
+		if err != nil {
+			// handle error
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
+			t.FailNow()
+		} else {
+			// client goes into command state
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+			// repeat > 64 characters in local part
+			response, err := command(conn, bufin, "MAIL FROM:[email protected]")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			response, err = command(conn, bufin, "MAIL FROM:[email protected]")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			expected := "503 Error: nested MAIL command"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+			// Plot twist: if you EHLO , it should allow MAIL FROM again
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+			response, err = command(conn, bufin, "MAIL FROM:[email protected]")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			expected = "250 OK"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+			// Plot twist: if you RSET , it should allow MAIL FROM again
+			response, err = command(conn, bufin, "RSET")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			expected = "250 OK"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+
+			response, err = command(conn, bufin, "MAIL FROM:[email protected]")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			expected = "250 OK"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+
+		}
+		conn.Close()
+		app.Shutdown()
+	} else {
+		if startErrors := app.Start(); startErrors != nil {
+			for _, err := range startErrors {
+				t.Error(err)
+			}
+			app.Shutdown()
+			t.FailNow()
+		}
+	}
+	logOut.Flush()
+	// don't forget to reset
+	logBuffer.Reset()
+	logIn.Reset(&logBuffer)
 }
+
+// It should error on a very long command line, exceeding CommandLineMaxLength 1024
+func TestCommandLineMaxLength(t *testing.T) {
+	if initErr != nil {
+		t.Error(initErr)
+		t.FailNow()
+	}
+
+	if startErrors := app.Start(); startErrors == nil {
+		conn, bufin, err := connect(0, 20)
+		if err != nil {
+			// handle error
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
+			t.FailNow()
+		} else {
+			// client goes into command state
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+			// repeat > 1024 characters
+			response, err := command(conn, bufin, strings.Repeat("s", guerrilla.CommandLineMaxLength+1))
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+
+			expected := "500 Line too long"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response)
+			}
+
+		}
+		conn.Close()
+		app.Shutdown()
+	} else {
+		if startErrors := app.Start(); startErrors != nil {
+			for _, err := range startErrors {
+				t.Error(err)
+			}
+			app.Shutdown()
+			t.FailNow()
+		}
+	}
+	logOut.Flush()
+	// don't forget to reset
+	logBuffer.Reset()
+	logIn.Reset(&logBuffer)
+}
+
+// It should error on a very long message, exceeding servers config value
+func TestDataMaxLength(t *testing.T) {
+	if initErr != nil {
+		t.Error(initErr)
+		t.FailNow()
+	}
+
+	if startErrors := app.Start(); startErrors == nil {
+		conn, bufin, err := connect(0, 20)
+		if err != nil {
+			// handle error
+			t.Error(err.Error(), config.Servers[0].ListenInterface)
+			t.FailNow()
+		} else {
+			// client goes into command state
+			if _, err := command(conn, bufin, "HELO localtester"); err != nil {
+				t.Error("Hello command failed", err.Error())
+			}
+
+			response, err := command(conn, bufin, "MAIL FROM:[email protected]")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			//fmt.Println(response)
+			response, err = command(conn, bufin, "RCPT TO:[email protected]")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+			//fmt.Println(response)
+			response, err = command(conn, bufin, "DATA")
+			if err != nil {
+				t.Error("command failed", err.Error())
+			}
+
+			response, err = command(
+				conn,
+				bufin,
+				fmt.Sprintf("Subject:test\r\n\r\nHello %s\r\n.\r\n",
+					strings.Repeat("n", int(config.Servers[0].MaxSize-26))))
+
+			//expected := "500 Line too long"
+			expected := "550 Error: Maximum line length exceeded"
+			if strings.Index(response, expected) != 0 {
+				t.Error("Server did not respond with", expected, ", it said:"+response, err)
+			}
+
+		}
+		conn.Close()
+		app.Shutdown()
+	} else {
+		if startErrors := app.Start(); startErrors != nil {
+			for _, err := range startErrors {
+				t.Error(err)
+			}
+			app.Shutdown()
+			t.FailNow()
+		}
+	}
+	logOut.Flush()
+	// don't forget to reset
+	logBuffer.Reset()
+	logIn.Reset(&logBuffer)
+}
+
+//

+ 9 - 1
util.go

@@ -11,6 +11,9 @@ var extractEmailRegex, _ = regexp.Compile(`<(.+?)@(.+?)>`) // go home regex, you
 func extractEmail(str string) (*EmailAddress, error) {
 	email := &EmailAddress{}
 	var err error
+	if len(str) > RFC2821LimitPath {
+		return email, errors.New("501 Path too long")
+	}
 	if matched := extractEmailRegex.FindStringSubmatch(str); len(matched) > 2 {
 		email.User = matched[1]
 		email.Host = validHost(matched[2])
@@ -18,8 +21,13 @@ func extractEmail(str string) (*EmailAddress, error) {
 		email.User = res[0]
 		email.Host = validHost(res[1])
 	}
+	err = nil
 	if email.User == "" || email.Host == "" {
-		err = errors.New("Invalid address, [" + email.User + "@" + email.Host + "] address:" + str)
+		err = errors.New("501 Invalid address")
+	} else if len(email.User) > RFC2832LimitLocalPart {
+		err = errors.New("501 Local part too long, cannot exceed 64 characters")
+	} else if len(email.Host) > RFC2821LimitDomain {
+		err = errors.New("501 Domain cannot exceed 255 characters")
 	}
 	return email, err
 }