XCLIENT support.

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

View file

@ -8,7 +8,9 @@ import (
"fmt"
"io"
"io/ioutil"
"net"
"net/textproto"
"strconv"
"strings"
"time"
)
@ -84,6 +86,10 @@ func (session *session) handle(line string) {
session.handleAUTH(cmd)
return
case "XCLIENT":
session.handleXCLIENT(cmd)
return
}
session.reply(502, "Unsupported command.")
@ -111,6 +117,7 @@ func (session *session) handleHELO(cmd command) {
}
session.peer.HeloName = cmd.fields[1]
session.peer.Protocol = SMTP
session.reply(250, "Go ahead")
return
@ -138,6 +145,7 @@ func (session *session) handleEHLO(cmd command) {
}
session.peer.HeloName = cmd.fields[1]
session.peer.Protocol = ESMTP
extensions := session.extensions()
@ -179,8 +187,10 @@ func (session *session) handleMAIL(cmd command) {
if session.server.SenderChecker != nil {
err = session.server.SenderChecker(session.peer, addr)
session.error(err)
return
if err != nil {
session.error(err)
return
}
}
session.envelope = &Envelope{
@ -214,8 +224,10 @@ func (session *session) handleRCPT(cmd command) {
if session.server.RecipientChecker != nil {
err = session.server.RecipientChecker(session.peer, addr)
session.error(err)
return
if err != nil {
session.error(err)
return
}
}
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")
}
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.
Authenticator func(peer Peer, username, password string) error
EnableXCLIENT bool // Enable XCLIENT support (default: false)
TLSConfig *tls.Config // Enable STARTTLS support.
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
type Peer struct {
HeloName string // Server name used in HELO/EHLO command
Username string // Username from authentication, if authenticated
Password string // Password from authentication, if authenticated
Protocol Protocol // Protocol used, SMTP or ESMTP
Addr net.Addr // Network address
}
@ -294,6 +305,10 @@ func (session *session) extensions() []string {
"PIPELINING",
}
if session.server.EnableXCLIENT {
extensions = append(extensions, "XCLIENT")
}
if session.server.TLSConfig != nil && !session.tls {
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)
}
}