这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 77 additions & 53 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,52 +61,52 @@ func (c *Conn) init() {
}

func (c *Conn) unrecognizedCommand(cmd string) {
c.WriteResponse(500, fmt.Sprintf("Syntax error, %v command unrecognized", cmd))
c.WriteResponse(500, EnhancedCode{5, 5, 2}, fmt.Sprintf("Syntax error, %v command unrecognized", cmd))

c.nbrErrors++
if c.nbrErrors > 3 {
c.WriteResponse(500, "Too many unrecognized commands")
c.WriteResponse(500, EnhancedCode{5, 5, 2}, "Too many unrecognized commands")
c.Close()
}
}

// Commands are dispatched to the appropriate handler functions.
func (c *Conn) handle(cmd string, arg string) {
if cmd == "" {
c.WriteResponse(500, "Speak up")
c.WriteResponse(500, EnhancedCode{5, 5, 2}, "Speak up")
return
}

cmd = strings.ToUpper(cmd)
switch cmd {
case "SEND", "SOML", "SAML", "EXPN", "HELP", "TURN":
// These commands are not implemented in any state
c.WriteResponse(502, fmt.Sprintf("%v command not implemented", cmd))
c.WriteResponse(502, EnhancedCode{5, 5, 1}, fmt.Sprintf("%v command not implemented", cmd))
case "HELO", "EHLO", "LHLO":
lmtp := cmd == "LHLO"
enhanced := lmtp || cmd == "EHLO"
if c.server.LMTP && !lmtp {
c.WriteResponse(500, "This is a LMTP server, use LHLO")
c.WriteResponse(500, EnhancedCode{5, 5, 1}, "This is a LMTP server, use LHLO")
}
if !c.server.LMTP && lmtp {
c.WriteResponse(500, "This is not a LMTP server")
c.WriteResponse(500, EnhancedCode{5, 5, 1}, "This is not a LMTP server")
}
c.handleGreet(enhanced, arg)
case "MAIL":
c.handleMail(arg)
case "RCPT":
c.handleRcpt(arg)
case "VRFY":
c.WriteResponse(252, "Cannot VRFY user, but will accept message")
c.WriteResponse(252, EnhancedCode{2, 5, 0}, "Cannot VRFY user, but will accept message")
case "NOOP":
c.WriteResponse(250, "I have sucessfully done nothing")
c.WriteResponse(250, EnhancedCode{2, 0, 0}, "I have sucessfully done nothing")
case "RSET": // Reset session
c.reset()
c.WriteResponse(250, "Session reset")
c.WriteResponse(250, EnhancedCode{2, 0, 0}, "Session reset")
case "DATA":
c.handleData(arg)
case "QUIT":
c.WriteResponse(221, "Goodnight and good luck")
c.WriteResponse(221, EnhancedCode{2, 0, 0}, "Goodnight and good luck")
c.Close()
case "AUTH":
if c.server.AuthDisabled {
Expand Down Expand Up @@ -179,16 +179,16 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
if !enhanced {
domain, err := parseHelloArgument(arg)
if err != nil {
c.WriteResponse(501, "Domain/address argument required for HELO")
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Domain/address argument required for HELO")
return
}
c.helo = domain

c.WriteResponse(250, fmt.Sprintf("Hello %s", domain))
c.WriteResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Hello %s", domain))
} else {
domain, err := parseHelloArgument(arg)
if err != nil {
c.WriteResponse(501, "Domain/address argument required for EHLO")
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Domain/address argument required for EHLO")
return
}

Expand All @@ -213,42 +213,46 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {

args := []string{"Hello " + domain}
args = append(args, caps...)
c.WriteResponse(250, args...)
c.WriteResponse(250, NoEnhancedCode, args...)
}
}

// READY state -> waiting for MAIL
func (c *Conn) handleMail(arg string) {
if c.helo == "" {
c.WriteResponse(502, "Please introduce yourself first.")
c.WriteResponse(502, EnhancedCode{2, 5, 1}, "Please introduce yourself first.")
return
}

if c.Session() == nil {
state := c.State()
session, err := c.server.Backend.AnonymousLogin(&state)
if err != nil {
c.WriteResponse(502, err.Error())
if smtpErr, ok := err.(*SMTPError); ok {
c.WriteResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
} else {
c.WriteResponse(502, EnhancedCode{5, 7, 0}, err.Error())
}
return
}

c.SetSession(session)
}

if len(arg) < 6 || strings.ToUpper(arg[0:5]) != "FROM:" {
c.WriteResponse(501, "Was expecting MAIL arg syntax of FROM:<address>")
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Was expecting MAIL arg syntax of FROM:<address>")
return
}
fromArgs := strings.Split(strings.Trim(arg[5:], " "), " ")
if c.server.Strict {
if !strings.HasPrefix(fromArgs[0], "<") || !strings.HasSuffix(fromArgs[0], ">") {
c.WriteResponse(501, "Was expecting MAIL arg syntax of FROM:<address>")
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Was expecting MAIL arg syntax of FROM:<address>")
return
}
}
from := strings.Trim(fromArgs[0], "<> ")
if from == "" {
c.WriteResponse(501, "Was expecting MAIL arg syntax of FROM:<address>")
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Was expecting MAIL arg syntax of FROM:<address>")
return
}

Expand All @@ -257,77 +261,77 @@ func (c *Conn) handleMail(arg string) {
if len(fromArgs) > 1 {
args, err := parseArgs(fromArgs[1:])
if err != nil {
c.WriteResponse(501, "Unable to parse MAIL ESMTP parameters")
c.WriteResponse(501, EnhancedCode{5, 5, 4}, "Unable to parse MAIL ESMTP parameters")
return
}

if args["SIZE"] != "" {
size, err := strconv.ParseInt(args["SIZE"], 10, 32)
if err != nil {
c.WriteResponse(501, "Unable to parse SIZE as an integer")
c.WriteResponse(501, EnhancedCode{5, 5, 4}, "Unable to parse SIZE as an integer")
return
}

if c.server.MaxMessageBytes > 0 && int(size) > c.server.MaxMessageBytes {
c.WriteResponse(552, "Max message size exceeded")
c.WriteResponse(552, EnhancedCode{5, 3, 4}, "Max message size exceeded")
return
}
}
}

if err := c.Session().Mail(from); err != nil {
if smtpErr, ok := err.(*SMTPError); ok {
c.WriteResponse(smtpErr.Code, smtpErr.Message)
c.WriteResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
return
}
c.WriteResponse(451, err.Error())
c.WriteResponse(451, EnhancedCode{4, 0, 0}, err.Error())
return
}

c.WriteResponse(250, fmt.Sprintf("Roger, accepting mail from <%v>", from))
c.WriteResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Roger, accepting mail from <%v>", from))
c.fromReceived = true
}

// MAIL state -> waiting for RCPTs followed by DATA
func (c *Conn) handleRcpt(arg string) {
if !c.fromReceived {
c.WriteResponse(502, "Missing MAIL FROM command.")
c.WriteResponse(502, EnhancedCode{5, 5, 1}, "Missing MAIL FROM command.")
return
}

if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") {
c.WriteResponse(501, "Was expecting RCPT arg syntax of TO:<address>")
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Was expecting RCPT arg syntax of TO:<address>")
return
}

// TODO: This trim is probably too forgiving
recipient := strings.Trim(arg[3:], "<> ")

if c.server.MaxRecipients > 0 && len(c.recipients) >= c.server.MaxRecipients {
c.WriteResponse(552, fmt.Sprintf("Maximum limit of %v recipients reached", c.server.MaxRecipients))
c.WriteResponse(552, EnhancedCode{5, 5, 3}, fmt.Sprintf("Maximum limit of %v recipients reached", c.server.MaxRecipients))
return
}

if err := c.Session().Rcpt(recipient); err != nil {
if smtpErr, ok := err.(*SMTPError); ok {
c.WriteResponse(smtpErr.Code, smtpErr.Message)
c.WriteResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
return
}
c.WriteResponse(451, err.Error())
c.WriteResponse(451, EnhancedCode{4, 0, 0}, err.Error())
return
}
c.recipients = append(c.recipients, recipient)
c.WriteResponse(250, fmt.Sprintf("I'll make sure <%v> gets this", recipient))
c.WriteResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("I'll make sure <%v> gets this", recipient))
}

func (c *Conn) handleAuth(arg string) {
if c.helo == "" {
c.WriteResponse(502, "Please introduce yourself first.")
c.WriteResponse(502, EnhancedCode{5, 5, 1}, "Please introduce yourself first.")
return
}

if arg == "" {
c.WriteResponse(502, "Missing parameter")
c.WriteResponse(502, EnhancedCode{5, 5, 4}, "Missing parameter")
return
}

Expand All @@ -346,7 +350,7 @@ func (c *Conn) handleAuth(arg string) {

newSasl, ok := c.server.auths[mechanism]
if !ok {
c.WriteResponse(504, "Unsupported authentication mechanism")
c.WriteResponse(504, EnhancedCode{5, 7, 4}, "Unsupported authentication mechanism")
return
}

Expand All @@ -356,7 +360,7 @@ func (c *Conn) handleAuth(arg string) {
for {
challenge, done, err := sasl.Next(response)
if err != nil {
c.WriteResponse(454, err.Error())
c.WriteResponse(454, EnhancedCode{4, 7, 0}, err.Error())
return
}

Expand All @@ -368,7 +372,7 @@ func (c *Conn) handleAuth(arg string) {
if len(challenge) > 0 {
encoded = base64.StdEncoding.EncodeToString(challenge)
}
c.WriteResponse(334, encoded)
c.WriteResponse(334, NoEnhancedCode, encoded)

encoded, err = c.ReadLine()
if err != nil {
Expand All @@ -377,35 +381,35 @@ func (c *Conn) handleAuth(arg string) {

response, err = base64.StdEncoding.DecodeString(encoded)
if err != nil {
c.WriteResponse(454, "Invalid base64 data")
c.WriteResponse(454, EnhancedCode{4, 7, 0}, "Invalid base64 data")
return
}
}

if c.Session() != nil {
c.WriteResponse(235, "Authentication succeeded")
c.WriteResponse(235, EnhancedCode{2, 0, 0}, "Authentication succeeded")
}
}

func (c *Conn) handleStartTLS() {
if _, isTLS := c.TLSConnectionState(); isTLS {
c.WriteResponse(502, "Already running in TLS")
c.WriteResponse(502, EnhancedCode{5, 5, 1}, "Already running in TLS")
return
}

if c.server.TLSConfig == nil {
c.WriteResponse(502, "TLS not supported")
c.WriteResponse(502, EnhancedCode{5, 5, 1}, "TLS not supported")
return
}

c.WriteResponse(220, "Ready to start TLS")
c.WriteResponse(220, EnhancedCode{2, 0, 0}, "Ready to start TLS")

// Upgrade to TLS
var tlsConn *tls.Conn
tlsConn = tls.Server(c.conn, c.server.TLSConfig)

if err := tlsConn.Handshake(); err != nil {
c.WriteResponse(550, "Handshake error")
c.WriteResponse(550, EnhancedCode{5, 0, 0}, "Handshake error")
}

c.conn = tlsConn
Expand All @@ -418,69 +422,89 @@ func (c *Conn) handleStartTLS() {
// DATA
func (c *Conn) handleData(arg string) {
if arg != "" {
c.WriteResponse(501, "DATA command should not have any arguments")
c.WriteResponse(501, EnhancedCode{5, 5, 4}, "DATA command should not have any arguments")
return
}

if !c.fromReceived || len(c.recipients) == 0 {
c.WriteResponse(502, "Missing RCPT TO command.")
c.WriteResponse(502, EnhancedCode{5, 5, 1}, "Missing RCPT TO command.")
return
}

// We have recipients, go to accept data
c.WriteResponse(354, "Go ahead. End your data with <CR><LF>.<CR><LF>")
c.WriteResponse(354, EnhancedCode{2, 0, 0}, "Go ahead. End your data with <CR><LF>.<CR><LF>")

var (
code int
msg string
code int
enhancedCode EnhancedCode
msg string
)
r := newDataReader(c)
err := c.Session().Data(r)
io.Copy(ioutil.Discard, r) // Make sure all the data has been consumed
if err != nil {
if smtperr, ok := err.(*SMTPError); ok {
code = smtperr.Code
enhancedCode = smtperr.EnhancedCode
msg = smtperr.Message
} else {
code = 554
enhancedCode = EnhancedCode{5, 0, 0}
msg = "Error: transaction failed, blame it on the weather: " + err.Error()
}
} else {
code = 250
enhancedCode = EnhancedCode{2, 0, 0}
msg = "OK: queued"
}

if c.server.LMTP {
// TODO: support per-recipient responses
for _, rcpt := range c.recipients {
c.WriteResponse(code, "<"+rcpt+"> "+msg)
c.WriteResponse(code, enhancedCode, "<"+rcpt+"> "+msg)
}
} else {
c.WriteResponse(code, msg)
c.WriteResponse(code, enhancedCode, msg)
}

c.resetMessage()
}

func (c *Conn) Reject() {
c.WriteResponse(421, "Too busy. Try again later.")
c.WriteResponse(421, EnhancedCode{4, 4, 5}, "Too busy. Try again later.")
c.Close()
}

func (c *Conn) greet() {
c.WriteResponse(220, fmt.Sprintf("%v ESMTP Service Ready", c.server.Domain))
c.WriteResponse(220, NoEnhancedCode, fmt.Sprintf("%v ESMTP Service Ready", c.server.Domain))
}

func (c *Conn) WriteResponse(code int, text ...string) {
func (c *Conn) WriteResponse(code int, enhCode EnhancedCode, text ...string) {
// TODO: error handling
if c.server.WriteTimeout != 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.server.WriteTimeout))
}

// All responses must include an enhanced code, if it is missing - use
// a generic code X.0.0.
if enhCode == EnhancedCodeNotSet {
cat := code / 100
switch cat {
case 2, 4, 5:
enhCode = EnhancedCode{cat, 0, 0}
default:
enhCode = NoEnhancedCode
}
}

for i := 0; i < len(text)-1; i++ {
c.text.PrintfLine("%v-%v", code, text[i])
}
c.text.PrintfLine("%v %v", code, text[len(text)-1])
if enhCode == NoEnhancedCode {
c.text.PrintfLine("%v %v", code, text[len(text)-1])
} else {
c.text.PrintfLine("%v %v.%v.%v %v", code, enhCode[0], enhCode[1], enhCode[2], text[len(text)-1])
}
}

// Reads a line of input
Expand Down
Loading