From 9c2a484d6bf1bf9c7c622f98a70620609d2981b1 Mon Sep 17 00:00:00 2001 From: Ian Kent Date: Sat, 20 Dec 2014 20:05:01 +0000 Subject: [PATCH] Fix #12 - support invalid MAIL/RCPT syntax --- smtp/protocol/protocol.go | 31 +++++++++++++++++----- smtp/protocol/protocol_test.go | 48 +++++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/smtp/protocol/protocol.go b/smtp/protocol/protocol.go index 5dcbe8a..56afa38 100644 --- a/smtp/protocol/protocol.go +++ b/smtp/protocol/protocol.go @@ -58,6 +58,15 @@ type Protocol struct { // parameters are valid, otherwise false. If nil, all authentication // attempts will be accepted. ValidateAuthenticationHandler func(mechanism string, args ...string) (errorReply *Reply, ok bool) + + // RejectBrokenRCPTSyntax controls whether the protocol accepts technically + // invalid syntax for the RCPT command. Set to true, the RCPT syntax requires + // no space between `TO:` and the opening `<` + RejectBrokenRCPTSyntax bool + // RejectBrokenMAILSyntax controls whether the protocol accepts technically + // invalid syntax for the MAIL command. Set to true, the MAIL syntax requires + // no space between `FROM:` and the opening `<` + RejectBrokenMAILSyntax bool } // NewProtocol returns a new SMTP state machine in INVALID state @@ -256,7 +265,7 @@ func (proto *Protocol) Command(command *Command) (reply *Reply) { } case "MAIL": proto.logf("Got MAIL command, switching to RCPT state") - from, err := ParseMAIL(command.args) + from, err := proto.ParseMAIL(command.args) if err != nil { return ReplyError(err) } @@ -281,7 +290,7 @@ func (proto *Protocol) Command(command *Command) (reply *Reply) { switch command.verb { case "RCPT": proto.logf("Got RCPT command") - to, err := ParseRCPT(command.args) + to, err := proto.ParseRCPT(command.args) if err != nil { return ReplyError(err) } @@ -328,8 +337,13 @@ func (proto *Protocol) EHLO(args string) (reply *Reply) { } // ParseMAIL returns the forward-path from a MAIL command argument -func ParseMAIL(mail string) (string, error) { - r := regexp.MustCompile("(?i:From):<([^>]+)>") +func (proto *Protocol) ParseMAIL(mail string) (string, error) { + var r *regexp.Regexp + if proto.RejectBrokenMAILSyntax { + r = regexp.MustCompile("(?i:From):<([^>]+)>") + } else { + r = regexp.MustCompile("(?i:From):\\s*<([^>]+)>") + } match := r.FindStringSubmatch(mail) if len(match) != 2 { return "", errors.New("Invalid syntax in MAIL command") @@ -338,8 +352,13 @@ func ParseMAIL(mail string) (string, error) { } // ParseRCPT returns the return-path from a RCPT command argument -func ParseRCPT(rcpt string) (string, error) { - r := regexp.MustCompile("(?i:To):<([^>]+)>") +func (proto *Protocol) ParseRCPT(rcpt string) (string, error) { + var r *regexp.Regexp + if proto.RejectBrokenRCPTSyntax { + r = regexp.MustCompile("(?i:To):<([^>]+)>") + } else { + r = regexp.MustCompile("(?i:To):\\s*<([^>]+)>") + } match := r.FindStringSubmatch(rcpt) if len(match) != 2 { return "", errors.New("Invalid syntax in RCPT command") diff --git a/smtp/protocol/protocol_test.go b/smtp/protocol/protocol_test.go index d197fdd..567beb3 100644 --- a/smtp/protocol/protocol_test.go +++ b/smtp/protocol/protocol_test.go @@ -511,28 +511,40 @@ func TestQUIT(t *testing.T) { } func TestParseMAIL(t *testing.T) { + proto := NewProtocol() Convey("ParseMAIL should parse MAIL command arguments", t, func() { - m, err := ParseMAIL("FROM:") + m, err := proto.ParseMAIL("FROM:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink@mailhog.example") - m, err = ParseMAIL("FROM:") + m, err = proto.ParseMAIL("FROM:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink") }) Convey("ParseMAIL should return an error for invalid syntax", t, func() { - m, err := ParseMAIL("FROM:oink") + m, err := proto.ParseMAIL("FROM:oink") So(err, ShouldNotBeNil) So(err.Error(), ShouldEqual, "Invalid syntax in MAIL command") So(m, ShouldEqual, "") }) Convey("ParseMAIL should be case-insensitive", t, func() { - m, err := ParseMAIL("FROM:") + m, err := proto.ParseMAIL("FROM:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink") - m, err = ParseMAIL("from:") + m, err = proto.ParseMAIL("from:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink@mailhog.example") - m, err = ParseMAIL("FrOm:") + m, err = proto.ParseMAIL("FrOm:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@oink.mailhog.example") + }) + Convey("ParseMAIL should support broken sender syntax", t, func() { + m, err := proto.ParseMAIL("FROM: ") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink") + m, err = proto.ParseMAIL("from: ") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@mailhog.example") + m, err = proto.ParseMAIL("FrOm: ") So(err, ShouldBeNil) So(m, ShouldEqual, "oink@oink.mailhog.example") }) @@ -586,28 +598,40 @@ func TestParseMAIL(t *testing.T) { } func TestParseRCPT(t *testing.T) { + proto := NewProtocol() Convey("ParseRCPT should parse RCPT command arguments", t, func() { - m, err := ParseRCPT("TO:") + m, err := proto.ParseRCPT("TO:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink@mailhog.example") - m, err = ParseRCPT("TO:") + m, err = proto.ParseRCPT("TO:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink") }) Convey("ParseRCPT should return an error for invalid syntax", t, func() { - m, err := ParseRCPT("TO:oink") + m, err := proto.ParseRCPT("TO:oink") So(err, ShouldNotBeNil) So(err.Error(), ShouldEqual, "Invalid syntax in RCPT command") So(m, ShouldEqual, "") }) Convey("ParseRCPT should be case-insensitive", t, func() { - m, err := ParseRCPT("TO:") + m, err := proto.ParseRCPT("TO:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink") - m, err = ParseRCPT("to:") + m, err = proto.ParseRCPT("to:") So(err, ShouldBeNil) So(m, ShouldEqual, "oink@mailhog.example") - m, err = ParseRCPT("To:") + m, err = proto.ParseRCPT("To:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@oink.mailhog.example") + }) + Convey("ParseRCPT should support broken recipient syntax", t, func() { + m, err := proto.ParseRCPT("TO: ") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink") + m, err = proto.ParseRCPT("to: ") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@mailhog.example") + m, err = proto.ParseRCPT("To: ") So(err, ShouldBeNil) So(m, ShouldEqual, "oink@oink.mailhog.example") })