mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-18 18:17:03 +00:00
Wire address validation into MAIL & RCPT handlers
This commit is contained in:
@@ -211,29 +211,35 @@ func (ss *Session) greetHandler(cmd string, arg string) {
|
|||||||
// READY state -> waiting for MAIL
|
// READY state -> waiting for MAIL
|
||||||
func (ss *Session) readyHandler(cmd string, arg string) {
|
func (ss *Session) readyHandler(cmd string, arg string) {
|
||||||
if cmd == "MAIL" {
|
if cmd == "MAIL" {
|
||||||
// (?i) makes the regex case insensitive
|
// Match FROM, while accepting '>' as quoted pair and in double quoted strings
|
||||||
re := regexp.MustCompile("(?i)^FROM:\\s*<([^>]+)>( [\\w= ]+)?$")
|
// (?i) makes the regex case insensitive, (?:) is non-grouping sub-match
|
||||||
|
re := regexp.MustCompile("(?i)^FROM:\\s*<((?:\\\\>|[^>])+|\"[^\"]+\"@[^>]+)>( [\\w= ]+)?$")
|
||||||
m := re.FindStringSubmatch(arg)
|
m := re.FindStringSubmatch(arg)
|
||||||
if m == nil {
|
if m == nil {
|
||||||
ss.send("501 Was expecting MAIL arg syntax of FROM:<address>")
|
ss.send("501 Was expecting MAIL arg syntax of FROM:<address>")
|
||||||
ss.logWarn("Bad MAIL argument: '%v'", arg)
|
ss.logWarn("Bad MAIL argument: %q", arg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
from := m[1]
|
from := m[1]
|
||||||
|
if _, _, err := ParseEmailAddress(from); err != nil {
|
||||||
|
ss.send("501 Bad sender address syntax")
|
||||||
|
ss.logWarn("Bad address as MAIL arg: %q, %s", from, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
// This is where the client may put BODY=8BITMIME, but we already
|
// This is where the client may put BODY=8BITMIME, but we already
|
||||||
// ready the DATA as bytes, so it does not effect our processing.
|
// read the DATA as bytes, so it does not effect our processing.
|
||||||
if m[2] != "" {
|
if m[2] != "" {
|
||||||
args, ok := ss.parseArgs(m[2])
|
args, ok := ss.parseArgs(m[2])
|
||||||
if !ok {
|
if !ok {
|
||||||
ss.send("501 Unable to parse MAIL ESMTP parameters")
|
ss.send("501 Unable to parse MAIL ESMTP parameters")
|
||||||
ss.logWarn("Bad MAIL argument: '%v'", arg)
|
ss.logWarn("Bad MAIL argument: %q", arg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if args["SIZE"] != "" {
|
if args["SIZE"] != "" {
|
||||||
size, err := strconv.ParseInt(args["SIZE"], 10, 32)
|
size, err := strconv.ParseInt(args["SIZE"], 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ss.send("501 Unable to parse SIZE as an integer")
|
ss.send("501 Unable to parse SIZE as an integer")
|
||||||
ss.logWarn("Unable to parse SIZE '%v' as an integer", args["SIZE"])
|
ss.logWarn("Unable to parse SIZE %q as an integer", args["SIZE"])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if int(size) > ss.server.maxMessageBytes {
|
if int(size) > ss.server.maxMessageBytes {
|
||||||
@@ -259,11 +265,16 @@ func (ss *Session) mailHandler(cmd string, arg string) {
|
|||||||
case "RCPT":
|
case "RCPT":
|
||||||
if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") {
|
if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") {
|
||||||
ss.send("501 Was expecting RCPT arg syntax of TO:<address>")
|
ss.send("501 Was expecting RCPT arg syntax of TO:<address>")
|
||||||
ss.logWarn("Bad RCPT argument: '%v'", arg)
|
ss.logWarn("Bad RCPT argument: %q", arg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// This trim is probably too forgiving
|
// This trim is probably too forgiving
|
||||||
recip := strings.Trim(arg[3:], "<> ")
|
recip := strings.Trim(arg[3:], "<> ")
|
||||||
|
if _, _, err := ParseEmailAddress(recip); err != nil {
|
||||||
|
ss.send("501 Bad recipient address syntax")
|
||||||
|
ss.logWarn("Bad address as RCPT arg: %q, %s", recip, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if ss.recipients.Len() >= ss.server.maxRecips {
|
if ss.recipients.Len() >= ss.server.maxRecips {
|
||||||
ss.logWarn("Maximum limit of %v recipients reached", ss.server.maxRecips)
|
ss.logWarn("Maximum limit of %v recipients reached", ss.server.maxRecips)
|
||||||
ss.send(fmt.Sprintf("552 Maximum limit of %v recipients reached", ss.server.maxRecips))
|
ss.send(fmt.Sprintf("552 Maximum limit of %v recipients reached", ss.server.maxRecips))
|
||||||
@@ -276,7 +287,7 @@ func (ss *Session) mailHandler(cmd string, arg string) {
|
|||||||
case "DATA":
|
case "DATA":
|
||||||
if arg != "" {
|
if arg != "" {
|
||||||
ss.send("501 DATA command should not have any arguments")
|
ss.send("501 DATA command should not have any arguments")
|
||||||
ss.logWarn("Got unexpected args on DATA: '%v'", arg)
|
ss.logWarn("Got unexpected args on DATA: %q", arg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ss.recipients.Len() > 0 {
|
if ss.recipients.Len() > 0 {
|
||||||
@@ -315,7 +326,7 @@ func (ss *Session) dataHandler() {
|
|||||||
mailboxes[i] = mb
|
mailboxes[i] = mb
|
||||||
messages[i] = mb.NewMessage()
|
messages[i] = mb.NewMessage()
|
||||||
} else {
|
} else {
|
||||||
log.LogTrace("Not storing message for '%v'", recip)
|
log.LogTrace("Not storing message for %q", recip)
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
@@ -409,7 +420,7 @@ func (ss *Session) send(msg string) {
|
|||||||
}
|
}
|
||||||
if _, err := fmt.Fprint(ss.conn, msg+"\r\n"); err != nil {
|
if _, err := fmt.Fprint(ss.conn, msg+"\r\n"); err != nil {
|
||||||
ss.sendError = err
|
ss.sendError = err
|
||||||
ss.logWarn("Failed to send: '%v'", msg)
|
ss.logWarn("Failed to send: %q", msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ss.logTrace(">> %v >>", msg)
|
ss.logTrace(">> %v >>", msg)
|
||||||
@@ -462,19 +473,19 @@ func (ss *Session) parseCmd(line string) (cmd string, arg string, ok bool) {
|
|||||||
case l == 0:
|
case l == 0:
|
||||||
return "", "", true
|
return "", "", true
|
||||||
case l < 4:
|
case l < 4:
|
||||||
ss.logWarn("Command too short: '%v'", line)
|
ss.logWarn("Command too short: %q", line)
|
||||||
return "", "", false
|
return "", "", false
|
||||||
case l == 4:
|
case l == 4:
|
||||||
return strings.ToUpper(line), "", true
|
return strings.ToUpper(line), "", true
|
||||||
case l == 5:
|
case l == 5:
|
||||||
// Too long to be only command, too short to have args
|
// Too long to be only command, too short to have args
|
||||||
ss.logWarn("Mangled command: '%v'", line)
|
ss.logWarn("Mangled command: %q", line)
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
// If we made it here, command is long enough to have args
|
// If we made it here, command is long enough to have args
|
||||||
if line[4] != ' ' {
|
if line[4] != ' ' {
|
||||||
// There wasn't a space after the command?
|
// There wasn't a space after the command?
|
||||||
ss.logWarn("Mangled command: '%v'", line)
|
ss.logWarn("Mangled command: %q", line)
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
// I'm not sure if we should trim the args or not, but we will for now
|
// I'm not sure if we should trim the args or not, but we will for now
|
||||||
@@ -491,7 +502,7 @@ func (ss *Session) parseArgs(arg string) (args map[string]string, ok bool) {
|
|||||||
re := regexp.MustCompile(" (\\w+)=(\\w+)")
|
re := regexp.MustCompile(" (\\w+)=(\\w+)")
|
||||||
pm := re.FindAllStringSubmatch(arg, -1)
|
pm := re.FindAllStringSubmatch(arg, -1)
|
||||||
if pm == nil {
|
if pm == nil {
|
||||||
ss.logWarn("Failed to parse arg string: '%v'")
|
ss.logWarn("Failed to parse arg string: %q")
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
for _, m := range pm {
|
for _, m := range pm {
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ func TestReadyState(t *testing.T) {
|
|||||||
{"MAIL FROM:john@gmail.com", 501},
|
{"MAIL FROM:john@gmail.com", 501},
|
||||||
{"MAIL FROM:<john@gmail.com> SIZE=147KB", 501},
|
{"MAIL FROM:<john@gmail.com> SIZE=147KB", 501},
|
||||||
{"MAIL FROM: <john@gmail.com> SIZE147", 501},
|
{"MAIL FROM: <john@gmail.com> SIZE147", 501},
|
||||||
|
{"MAIL FROM:<first@last@gmail.com>", 501},
|
||||||
|
{"MAIL FROM:<first last@gmail.com>", 501},
|
||||||
}
|
}
|
||||||
if err := playSession(t, server, script); err != nil {
|
if err := playSession(t, server, script); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -90,6 +92,18 @@ func TestReadyState(t *testing.T) {
|
|||||||
{"MAIL FROM: <john@gmail.com> BODY=8BITMIME", 250},
|
{"MAIL FROM: <john@gmail.com> BODY=8BITMIME", 250},
|
||||||
{"RSET", 250},
|
{"RSET", 250},
|
||||||
{"MAIL FROM:<john@gmail.com> SIZE=1024", 250},
|
{"MAIL FROM:<john@gmail.com> SIZE=1024", 250},
|
||||||
|
{"RSET", 250},
|
||||||
|
{"MAIL FROM:<host!host!user/data@foo.com>", 250},
|
||||||
|
{"RSET", 250},
|
||||||
|
{"MAIL FROM:<\"first last\"@space.com>", 250},
|
||||||
|
{"RSET", 250},
|
||||||
|
{"MAIL FROM:<user\\@internal@external.com>", 250},
|
||||||
|
{"RSET", 250},
|
||||||
|
{"MAIL FROM:<user\\>name@host.com>", 250},
|
||||||
|
{"RSET", 250},
|
||||||
|
{"MAIL FROM:<\"user>name\"@host.com>", 250},
|
||||||
|
{"RSET", 250},
|
||||||
|
{"MAIL FROM:<\"user@internal\"@external.com>", 250},
|
||||||
}
|
}
|
||||||
if err := playSession(t, server, script); err != nil {
|
if err := playSession(t, server, script); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -120,6 +134,8 @@ func TestMailState(t *testing.T) {
|
|||||||
{"RCPT", 501},
|
{"RCPT", 501},
|
||||||
{"RCPT TO", 501},
|
{"RCPT TO", 501},
|
||||||
{"RCPT TO james@gmail.com", 501},
|
{"RCPT TO james@gmail.com", 501},
|
||||||
|
{"RCPT TO:<first last@host.com>", 501},
|
||||||
|
{"RCPT TO:<fred@fish@host.com", 501},
|
||||||
}
|
}
|
||||||
if err := playSession(t, server, script); err != nil {
|
if err := playSession(t, server, script); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -133,6 +149,12 @@ func TestMailState(t *testing.T) {
|
|||||||
{"RCPT TO: <u2@gmail.com>", 250},
|
{"RCPT TO: <u2@gmail.com>", 250},
|
||||||
{"RCPT TO:u3@gmail.com", 250},
|
{"RCPT TO:u3@gmail.com", 250},
|
||||||
{"RCPT TO: u4@gmail.com", 250},
|
{"RCPT TO: u4@gmail.com", 250},
|
||||||
|
{"RSET", 250},
|
||||||
|
{"MAIL FROM:<john@gmail.com>", 250},
|
||||||
|
{"RCPT TO:<user\\@internal@external.com", 250},
|
||||||
|
{"RCPT TO:<\"first last\"@host.com", 250},
|
||||||
|
{"RCPT TO:<user\\>name@host.com>", 250},
|
||||||
|
{"RCPT TO:<\"user>name\"@host.com>", 250},
|
||||||
}
|
}
|
||||||
if err := playSession(t, server, script); err != nil {
|
if err := playSession(t, server, script); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user