XCLIENT support.

This commit is contained in:
Christian Joergensen 2014-07-20 21:51:39 +02:00
parent 0fa1acf706
commit 9695f7c734
3 changed files with 217 additions and 4 deletions

View file

@ -8,7 +8,9 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/textproto" "net/textproto"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -84,6 +86,10 @@ func (session *session) handle(line string) {
session.handleAUTH(cmd) session.handleAUTH(cmd)
return return
case "XCLIENT":
session.handleXCLIENT(cmd)
return
} }
session.reply(502, "Unsupported command.") session.reply(502, "Unsupported command.")
@ -111,6 +117,7 @@ func (session *session) handleHELO(cmd command) {
} }
session.peer.HeloName = cmd.fields[1] session.peer.HeloName = cmd.fields[1]
session.peer.Protocol = SMTP
session.reply(250, "Go ahead") session.reply(250, "Go ahead")
return return
@ -138,6 +145,7 @@ func (session *session) handleEHLO(cmd command) {
} }
session.peer.HeloName = cmd.fields[1] session.peer.HeloName = cmd.fields[1]
session.peer.Protocol = ESMTP
extensions := session.extensions() extensions := session.extensions()
@ -179,8 +187,10 @@ func (session *session) handleMAIL(cmd command) {
if session.server.SenderChecker != nil { if session.server.SenderChecker != nil {
err = session.server.SenderChecker(session.peer, addr) err = session.server.SenderChecker(session.peer, addr)
session.error(err) if err != nil {
return session.error(err)
return
}
} }
session.envelope = &Envelope{ session.envelope = &Envelope{
@ -214,8 +224,10 @@ func (session *session) handleRCPT(cmd command) {
if session.server.RecipientChecker != nil { if session.server.RecipientChecker != nil {
err = session.server.RecipientChecker(session.peer, addr) err = session.server.RecipientChecker(session.peer, addr)
session.error(err) if err != nil {
return session.error(err)
return
}
} }
session.envelope.Recipients = append(session.envelope.Recipients, addr) session.envelope.Recipients = append(session.envelope.Recipients, addr)
@ -445,3 +457,102 @@ func (session *session) handleAUTH(cmd command) {
session.reply(235, "OK, you are now authenticated") session.reply(235, "OK, you are now authenticated")
} }
func (session *session) handleXCLIENT(cmd command) {
if !session.server.EnableXCLIENT {
session.reply(550, "XCLIENT not enabled")
return
}
var (
newHeloName = ""
newAddr net.IP = nil
newTCPPort uint64 = 0
newUsername = ""
newProto Protocol = ""
)
for _, item := range cmd.fields[1:] {
parts := strings.Split(item, "=")
if len(parts) != 2 {
session.reply(502, "Couldn't decode the command.")
return
}
name := parts[0]
value := parts[1]
switch name {
case "NAME":
// Unused in smtpd package
continue
case "HELO":
newHeloName = value
continue
case "ADDR":
newAddr = net.ParseIP(value)
continue
case "PORT":
var err error
newTCPPort, err = strconv.ParseUint(value, 10, 16)
if err != nil {
session.reply(502, "Couldn't decode the command.")
return
}
continue
case "LOGIN":
newUsername = value
continue
case "PROTO":
if value == "SMTP" {
newProto = SMTP
} else if value == "ESMTP" {
newProto = ESMTP
}
continue
default:
session.reply(502, "Couldn't decode the command.")
return
}
}
tcpAddr, ok := session.peer.Addr.(*net.TCPAddr)
if !ok {
session.reply(502, "Unsupported network connection")
return
}
if newHeloName != "" {
session.peer.HeloName = newHeloName
}
if newAddr != nil {
tcpAddr.IP = newAddr
}
if newTCPPort != 0 {
tcpAddr.Port = int(newTCPPort)
}
if newUsername != "" {
session.peer.Username = newUsername
}
if newProto != "" {
session.peer.Protocol = newProto
}
session.welcome()
}

View file

@ -42,15 +42,26 @@ type Server struct {
// Can be left empty for no authentication support. // Can be left empty for no authentication support.
Authenticator func(peer Peer, username, password string) error Authenticator func(peer Peer, username, password string) error
EnableXCLIENT bool // Enable XCLIENT support (default: false)
TLSConfig *tls.Config // Enable STARTTLS support. TLSConfig *tls.Config // Enable STARTTLS support.
ForceTLS bool // Force STARTTLS usage. ForceTLS bool // Force STARTTLS usage.
} }
// Protocol represents the protocol used in the SMTP session
type Protocol string
const (
SMTP Protocol = "SMTP"
ESMTP = "ESMTP"
)
// Peer represents the client connecting to the server // Peer represents the client connecting to the server
type Peer struct { type Peer struct {
HeloName string // Server name used in HELO/EHLO command HeloName string // Server name used in HELO/EHLO command
Username string // Username from authentication, if authenticated Username string // Username from authentication, if authenticated
Password string // Password from authentication, if authenticated Password string // Password from authentication, if authenticated
Protocol Protocol // Protocol used, SMTP or ESMTP
Addr net.Addr // Network address Addr net.Addr // Network address
} }
@ -294,6 +305,10 @@ func (session *session) extensions() []string {
"PIPELINING", "PIPELINING",
} }
if session.server.EnableXCLIENT {
extensions = append(extensions, "XCLIENT")
}
if session.server.TLSConfig != nil && !session.tls { if session.server.TLSConfig != nil && !session.tls {
extensions = append(extensions, "STARTTLS") extensions = append(extensions, "STARTTLS")
} }

View file

@ -1058,3 +1058,90 @@ func TestLongLine(t *testing.T) {
} }
} }
func TestXCLIENT(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Listen failed: %v", err)
}
defer ln.Close()
server := &smtpd.Server{
EnableXCLIENT: true,
SenderChecker: func(peer smtpd.Peer, addr string) error {
if peer.HeloName != "new.example.net" {
t.Fatalf("Didn't override HELO name: %v", peer.HeloName)
}
if peer.Addr.String() != "42.42.42.42:4242" {
t.Fatalf("Didn't override IP/Port: %v", peer.Addr)
}
if peer.Username != "newusername" {
t.Fatalf("Didn't override username: %v", peer.Username)
}
if peer.Protocol != smtpd.SMTP {
t.Fatalf("Didn't override protocol: %v", peer.Protocol)
}
return nil
},
}
go func() {
server.Serve(ln)
}()
c, err := smtp.Dial(ln.Addr().String())
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
if supported, _ := c.Extension("XCLIENT"); !supported {
t.Fatal("XCLIENT not supported")
}
id, err := c.Text.Cmd("XCLIENT NAME=ignored ADDR=42.42.42.42 PORT=4242 PROTO=SMTP HELO=new.example.net LOGIN=newusername")
if err != nil {
t.Fatalf("Cmd failed: %v", err)
}
c.Text.StartResponse(id)
_, _, err = c.Text.ReadResponse(220)
c.Text.EndResponse(id)
if err != nil {
t.Fatalf("XCLIENT failed: %v", err)
}
if err := c.Mail("sender@example.org"); err != nil {
t.Fatalf("Mail failed: %v", err)
}
if err := c.Rcpt("recipient@example.net"); err != nil {
t.Fatalf("Rcpt failed: %v", err)
}
if err := c.Rcpt("recipient2@example.net"); err != nil {
t.Fatalf("Rcpt2 failed: %v", err)
}
wc, err := c.Data()
if err != nil {
t.Fatalf("Data failed: %v", err)
}
_, err = fmt.Fprintf(wc, "This is the email body")
if err != nil {
t.Fatalf("Data body failed: %v", err)
}
err = wc.Close()
if err != nil {
t.Fatalf("Data close failed: %v", err)
}
if err := c.Quit(); err != nil {
t.Fatalf("Quit failed: %v", err)
}
}