Fix #12 - support invalid MAIL/RCPT syntax

This commit is contained in:
Ian Kent 2014-12-20 20:05:01 +00:00
parent ba6490cc86
commit 9c2a484d6b
2 changed files with 61 additions and 18 deletions

View file

@ -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")

View file

@ -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:<oink@mailhog.example>")
m, err := proto.ParseMAIL("FROM:<oink@mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@mailhog.example")
m, err = ParseMAIL("FROM:<oink>")
m, err = proto.ParseMAIL("FROM:<oink>")
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:<oink>")
m, err := proto.ParseMAIL("FROM:<oink>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink")
m, err = ParseMAIL("from:<oink@mailhog.example>")
m, err = proto.ParseMAIL("from:<oink@mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@mailhog.example")
m, err = ParseMAIL("FrOm:<oink@oink.mailhog.example>")
m, err = proto.ParseMAIL("FrOm:<oink@oink.mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@oink.mailhog.example")
})
Convey("ParseMAIL should support broken sender syntax", t, func() {
m, err := proto.ParseMAIL("FROM: <oink>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink")
m, err = proto.ParseMAIL("from: <oink@mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@mailhog.example")
m, err = proto.ParseMAIL("FrOm: <oink@oink.mailhog.example>")
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:<oink@mailhog.example>")
m, err := proto.ParseRCPT("TO:<oink@mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@mailhog.example")
m, err = ParseRCPT("TO:<oink>")
m, err = proto.ParseRCPT("TO:<oink>")
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:<oink>")
m, err := proto.ParseRCPT("TO:<oink>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink")
m, err = ParseRCPT("to:<oink@mailhog.example>")
m, err = proto.ParseRCPT("to:<oink@mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@mailhog.example")
m, err = ParseRCPT("To:<oink@oink.mailhog.example>")
m, err = proto.ParseRCPT("To:<oink@oink.mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@oink.mailhog.example")
})
Convey("ParseRCPT should support broken recipient syntax", t, func() {
m, err := proto.ParseRCPT("TO: <oink>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink")
m, err = proto.ParseRCPT("to: <oink@mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@mailhog.example")
m, err = proto.ParseRCPT("To: <oink@oink.mailhog.example>")
So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@oink.mailhog.example")
})