diff --git a/README b/README index d3ae69d..59d36c2 100644 --- a/README +++ b/README @@ -1,10 +1,10 @@ -Smtpd is a Go package for handling the server side of the SMTP protocol. -It does not handle high level details like what addresses should be +Smtpd is a Go package for handling the server side of the LMTP & SMTP +protocol. It does not handle high level details like what addresses should be accepted or what should happen with email once it has been fully received; -those decisions are instead delegated to whatever is driving smtpd. -Smtpd's purpose is simply to handle the grunt work of a reasonably -RFC compliant SMTP server, taking care of things like proper command -sequencing, TLS, and basic correctness of some things. +those decisions are instead delegated to whatever is driving smtpd. Smtpd's +purpose is simply to handle the grunt work of a reasonably RFC compliant SMTP +server, taking care of things like proper command sequencing, TLS, and basic +correctness of some things. (The standard library net/smtp package handles only the client side of SMTP; it makes no attempt to provide the facilities you'd need to diff --git a/smtpd.go b/smtpd.go index 4af6504..d9adc5c 100644 --- a/smtpd.go +++ b/smtpd.go @@ -61,6 +61,7 @@ const ( HELP AUTH STARTTLS + LHLO ) // ParsedLine represents a parsed SMTP command line. Err is set if @@ -97,6 +98,7 @@ var smtpCommand = []struct { }{ {HELO, "HELO", canArg}, {EHLO, "EHLO", canArg}, + {LHLO, "LHLO", canArg}, {MAILFROM, "MAIL FROM", colonAddress}, {RCPTTO, "RCPT TO", colonAddress}, {DATA, "DATA", noArg}, @@ -311,6 +313,7 @@ var states = map[Command]struct { }{ HELO: {sInitial | sHelo, sHelo}, EHLO: {sInitial | sHelo, sHelo}, + LHLO: {sInitial | sHelo, sHelo}, AUTH: {sHelo, sHelo}, MAILFROM: {sHelo, sMail}, RCPTTO: {sMail | sRcpt, sRcpt}, @@ -579,7 +582,7 @@ func (c *Conn) Accept() { switch c.curcmd { case HELO: c.reply("250 %s Hello %v", c.Config.LocalName, c.conn.RemoteAddr()) - case EHLO: + case EHLO, LHLO: c.reply("250-%s Hello %v", c.Config.LocalName, c.conn.RemoteAddr()) // We advertise 8BITMIME per // http://cr.yp.to/smtp/8bitmime.html @@ -746,6 +749,58 @@ func (c *Conn) Tempfail() { c.replied = true } +// A variant of LmtpRejectMsg suitable for using in the responses to the DATA +// command in LMTP. It won't advance the state machine, so multiple commands +// can be issued under control of the caller. +func (c *Conn) LmtpRejectMsg(format string, elems ...interface{}) { + c.RejectMsg(format, elems...) + if c.state == sPostData { + c.state = sData + c.nstate = sPostData + } + c.replied = false +} + +// A variant of LmtpTempfailMsg suitable for using in the responses to the DATA +// command in LMTP. It won't advance the state machine, so multiple commands +// can be issued under control of the caller. +func (c *Conn) LmtpTempfailMsg(format string, elems ...interface{}) { + c.TempfailMsg(format, elems...) + if c.state == sPostData { + c.state = sData + c.nstate = sPostData + } + c.replied = false +} + +// A variant of LmtpAcceptMsg suitable for using in the responses to the DATA +// command in LMTP. It won't advance the state machine, so multiple commands +// can be issued under control of the caller. +func (c *Conn) LmtpAcceptMsg(format string, elems ...interface{}) { + if c.curcmd == DATA { + c.replyMulti(250, format, elems...) + } else { + c.AcceptMsg(format, elems) + } + + if c.state == sPostData { + c.state = sData + c.nstate = sPostData + } + + c.replied = false +} + +// Advance the protocol machine and prevent automatic responses, as with LMTP +// the caller is driving the conversation and should have already replied. +func (c *Conn) LmtpDataAccept() { + if c.replied { + return + } + c.state = c.nstate + c.replied = true +} + // mimeParam() returns true if the parameter argument of a MAIL FROM // is what we expect for a client exploiting our advertisement of // 8BITMIME.