Browse Source

Support multiple recipients in backend interface

Reza Mohammadi 9 năm trước cách đây
mục cha
commit
a5cbe08650
5 tập tin đã thay đổi với 54 bổ sung50 xóa
  1. 2 2
      backends/dummy.go
  2. 12 9
      backends/guerrilla_db_redis.go
  3. 11 1
      models.go
  4. 17 9
      server/smtpd.go
  5. 12 29
      util/util.go

+ 2 - 2
backends/dummy.go

@@ -37,9 +37,9 @@ func (b *DummyBackend) Finalize() error {
 	return nil
 }
 
-func (b *DummyBackend) Process(client *guerrilla.Client, user, host string) string {
+func (b *DummyBackend) Process(client *guerrilla.Client, from *guerrilla.EmailParts, to []*guerrilla.EmailParts) string {
 	if b.config.LogReceivedMails {
-		log.Infof("Mail from: %s@%s", user, host)
+		log.Infof("Mail from: %s / to: %v", from, to)
 	}
 	return fmt.Sprintf("250 OK : queued as %s", client.Hash)
 }

+ 12 - 9
backends/guerrilla_db_redis.go

@@ -102,10 +102,15 @@ func (g *GuerrillaDBAndRedisBackend) Finalize() error {
 	return nil
 }
 
-func (g *GuerrillaDBAndRedisBackend) Process(client *guerrilla.Client, user, host string) string {
+func (g *GuerrillaDBAndRedisBackend) Process(client *guerrilla.Client, from *guerrilla.EmailParts, to []*guerrilla.EmailParts) string {
+	if len(to) == 0 {
+		return "554 Error: no recipient"
+	}
+
 	// to do: timeout when adding to SaveMailChan
 	// place on the channel so that one of the save mail workers can pick it up
-	g.saveMailChan <- &savePayload{client: client, user: user, host: host}
+	// TODO: support multiple recipients
+	g.saveMailChan <- &savePayload{client: client, from: from, recipient: to[0]}
 	// wait for the save to complete
 	// or timeout
 	select {
@@ -121,9 +126,9 @@ func (g *GuerrillaDBAndRedisBackend) Process(client *guerrilla.Client, user, hos
 }
 
 type savePayload struct {
-	client *guerrilla.Client
-	user   string
-	host   string
+	client    *guerrilla.Client
+	from      *guerrilla.EmailParts
+	recipient *guerrilla.EmailParts
 }
 
 type redisClient struct {
@@ -168,9 +173,7 @@ func (g *GuerrillaDBAndRedisBackend) saveMail() {
 			g.wg.Done()
 			return
 		}
-
-		recipient = payload.user + "@" + payload.host
-		to = payload.user + "@" + g.config.PrimaryHost
+		to = payload.recipient.User + "@" + g.config.PrimaryHost
 		length = len(payload.client.Data)
 		ts := fmt.Sprintf("%d", time.Now().UnixNano())
 		payload.client.Subject = util.MimeHeaderDecode(payload.client.Subject)
@@ -183,7 +186,7 @@ func (g *GuerrillaDBAndRedisBackend) saveMail() {
 		var addHead string
 		addHead += "Delivered-To: " + to + "\r\n"
 		addHead += "Received: from " + payload.client.Helo + " (" + payload.client.Helo + "  [" + payload.client.Address + "])\r\n"
-		addHead += "	by " + payload.host + " with SMTP id " + payload.client.Hash + "@" + payload.host + ";\r\n"
+		addHead += "	by " + payload.recipient.Host + " with SMTP id " + payload.client.Hash + "@" + payload.recipient.Host + ";\r\n"
 		addHead += "	" + time.Now().Format(time.RFC1123Z) + "\r\n"
 		// compress to save space
 		payload.client.Data = util.Compress(&addHead, &payload.client.Data)

+ 11 - 1
backend.go → models.go

@@ -3,14 +3,24 @@ package guerrilla
 import (
 	"bufio"
 	"errors"
+	"fmt"
 	"io"
 	"net"
 )
 
+type EmailParts struct {
+	User string
+	Host string
+}
+
+func (ep *EmailParts) String() string {
+	return fmt.Sprintf("%s@%s", ep.User, ep.Host)
+}
+
 // Backend accepts the recieved messages, and store/deliver/process them
 type Backend interface {
 	Initialize(BackendConfig) error
-	Process(client *Client, user, host string) string
+	Process(client *Client, from *EmailParts, to []*EmailParts) string
 	Finalize() error
 }
 

+ 17 - 9
server/smtpd.go

@@ -155,17 +155,25 @@ func (server *SmtpdServer) handleClient(client *guerrilla.Client, backend guerri
 			client.Bufin.SetLimit(int64(server.config.MaxSize) + 1024000) // This is a hard limit.
 			client.Data, err = server.readSmtp(client)
 			if err == nil {
-				if from, to, mailErr := util.ValidateEmailData(client.MailFrom, client.RcptTo); mailErr == nil {
-					client.MailFrom = fmt.Sprintf("%s@%s", from.User, from.Host)
-					client.RcptTo = fmt.Sprintf("%s@%s", to.User, to.Host)
-					if !server.mainConfig.IsAllowed(to.Host) {
-						responseAdd(client, "550 Error: not allowed")
+				from, mailErr := util.ExtractEmail(client.MailFrom)
+				if mailErr != nil {
+					responseAdd(client, fmt.Sprintf("550 Error: invalid from: ", mailErr.Error()))
+				} else {
+					// TODO: support multiple RcptTo
+					to, mailErr := util.ExtractEmail(client.RcptTo)
+					if mailErr != nil {
+						responseAdd(client, fmt.Sprintf("550 Error: invalid from: ", mailErr.Error()))
 					} else {
-						resp := backend.Process(client, to.User, to.Host)
-						responseAdd(client, resp)
+						client.MailFrom = from.String()
+						client.RcptTo = to.String()
+						if !server.mainConfig.IsAllowed(to.Host) {
+							responseAdd(client, "550 Error: not allowed")
+						} else {
+							toArray := []*guerrilla.EmailParts{to}
+							resp := backend.Process(client, from, toArray)
+							responseAdd(client, resp)
+						}
 					}
-				} else {
-					responseAdd(client, "550 Error: "+mailErr.Error())
 				}
 
 			} else {

+ 12 - 29
util/util.go

@@ -12,47 +12,29 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/sloonz/go-qprintable"
 	"gopkg.in/iconv.v1"
 
-	"github.com/sloonz/go-qprintable"
+	guerrilla "github.com/flashmob/go-guerrilla"
 )
 
-type EmailParts struct {
-	User string
-	Host string
-}
-
-func ValidateEmailData(mailFrom, rcptTo string) (*EmailParts, *EmailParts, error) {
-	var user, host string
-	var addrErr error
-
-	if user, host, addrErr = extractEmail(mailFrom); addrErr != nil {
-		return nil, nil, addrErr
-	}
-	from := &EmailParts{User: user, Host: host}
-	if user, host, addrErr = extractEmail(rcptTo); addrErr != nil {
-		return nil, nil, addrErr
-	}
-	to := &EmailParts{User: user, Host: host}
-	return from, to, nil
-}
-
 var extractEmailRegex, _ = regexp.Compile(`<(.+?)@(.+?)>`) // go home regex, you're drunk!
 
-func extractEmail(str string) (name string, host string, err error) {
+func ExtractEmail(str string) (email *guerrilla.EmailParts, err error) {
+	email = &guerrilla.EmailParts{}
 	if matched := extractEmailRegex.FindStringSubmatch(str); len(matched) > 2 {
-		host = validHost(matched[2])
-		name = matched[1]
+		email.User = matched[1]
+		email.Host = validHost(matched[2])
 	} else {
 		if res := strings.Split(str, "@"); len(res) > 1 {
-			name = res[0]
-			host = validHost(res[1])
+			email.User = res[0]
+			email.Host = validHost(res[1])
 		}
 	}
-	if host == "" || name == "" {
-		err = errors.New("Invalid address, [" + name + "@" + host + "] address:" + str)
+	if email.User == "" || email.Host == "" {
+		err = errors.New("Invalid address, [" + email.User + "@" + email.Host + "] address:" + str)
 	}
-	return name, host, err
+	return
 }
 
 var mimeRegex, _ = regexp.Compile(`=\?(.+?)\?([QBqp])\?(.+?)\?=`)
@@ -115,6 +97,7 @@ func MailTransportDecode(str string, encodingType string, charset string) string
 
 	if charset != "UTF-8" {
 		charset = fixCharset(charset)
+		// TODO: remove dependency to os-dependent iconv library
 		if cd, err := iconv.Open("UTF-8", charset); err == nil {
 			defer func() {
 				cd.Close()