mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 17:47:03 +00:00
Implement SIZE ESMTP extension
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/jhillyerd/inbucket/app/inbucket"
|
"github.com/jhillyerd/inbucket/app/inbucket"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -127,7 +128,9 @@ func (s *Server) startSession(id int, conn net.Conn) {
|
|||||||
continue
|
continue
|
||||||
case "RSET":
|
case "RSET":
|
||||||
// Reset session
|
// Reset session
|
||||||
|
ss.trace("Resetting session state on RSET request")
|
||||||
ss.reset()
|
ss.reset()
|
||||||
|
ss.send("250 Session reset")
|
||||||
continue
|
continue
|
||||||
case "QUIT":
|
case "QUIT":
|
||||||
ss.send("221 Goodnight and good luck")
|
ss.send("221 Goodnight and good luck")
|
||||||
@@ -179,7 +182,8 @@ func (ss *Session) greetHandler(cmd string, arg string) {
|
|||||||
ss.enterState(READY)
|
ss.enterState(READY)
|
||||||
case "EHLO":
|
case "EHLO":
|
||||||
ss.send("250-Great, let's get this show on the road")
|
ss.send("250-Great, let's get this show on the road")
|
||||||
ss.send("250 8BITMIME")
|
ss.send("250-8BITMIME")
|
||||||
|
ss.send(fmt.Sprintf("250 SIZE %v", ss.server.maxMessageBytes))
|
||||||
ss.enterState(READY)
|
ss.enterState(READY)
|
||||||
default:
|
default:
|
||||||
ss.ooSeq(cmd)
|
ss.ooSeq(cmd)
|
||||||
@@ -190,7 +194,7 @@ func (ss *Session) greetHandler(cmd string, arg string) {
|
|||||||
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
|
// (?i) makes the regex case insensitive
|
||||||
re := regexp.MustCompile("(?i)^FROM:<([^>]+)>( (\\w+)=(\\w+))?$")
|
re := regexp.MustCompile("(?i)^FROM:<([^>]+)>( [\\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>")
|
||||||
@@ -200,9 +204,27 @@ func (ss *Session) readyHandler(cmd string, arg string) {
|
|||||||
from := m[1]
|
from := m[1]
|
||||||
// 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.
|
// ready the DATA as bytes, so it does not effect our processing.
|
||||||
pkey := m[3]
|
if m[2] != "" {
|
||||||
pval := m[4]
|
args, ok := ss.parseArgs(m[2])
|
||||||
ss.trace("MAIL param: %v = %v", pkey, pval)
|
if !ok {
|
||||||
|
ss.send("501 Unable to parse MAIL ESMTP parameters")
|
||||||
|
ss.warn("Bad MAIL argument: \"%v\"", arg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if args["SIZE"] != "" {
|
||||||
|
size, err := strconv.ParseInt(args["SIZE"], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
ss.send("501 Unable to parse SIZE as an integer")
|
||||||
|
ss.error("Unable to parse SIZE '%v' as an integer", args["SIZE"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if int(size) > ss.server.maxMessageBytes {
|
||||||
|
ss.send("552 Max message size exceeded")
|
||||||
|
ss.warn("Client wanted to send oversized message: %v", args["SIZE"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ss.from = from
|
ss.from = from
|
||||||
ss.recipients = list.New()
|
ss.recipients = list.New()
|
||||||
ss.info("Mail from: %v", from)
|
ss.info("Mail from: %v", from)
|
||||||
@@ -254,7 +276,7 @@ func (ss *Session) mailHandler(cmd string, arg string) {
|
|||||||
|
|
||||||
// DATA
|
// DATA
|
||||||
func (ss *Session) dataHandler() {
|
func (ss *Session) dataHandler() {
|
||||||
msgSize := uint64(0)
|
msgSize := 0
|
||||||
|
|
||||||
// Get a Mailbox and a new Message for each recipient
|
// Get a Mailbox and a new Message for each recipient
|
||||||
mailboxes := make([]*inbucket.Mailbox, ss.recipients.Len())
|
mailboxes := make([]*inbucket.Mailbox, ss.recipients.Len())
|
||||||
@@ -266,7 +288,7 @@ func (ss *Session) dataHandler() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
ss.error("Failed to open mailbox for %v", recip)
|
ss.error("Failed to open mailbox for %v", recip)
|
||||||
ss.send(fmt.Sprintf("451 Failed to open mailbox for %v", recip))
|
ss.send(fmt.Sprintf("451 Failed to open mailbox for %v", recip))
|
||||||
ss.enterState(READY)
|
ss.reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mailboxes[i] = mb
|
mailboxes[i] = mb
|
||||||
@@ -297,20 +319,28 @@ func (ss *Session) dataHandler() {
|
|||||||
}
|
}
|
||||||
ss.send("250 Mail accepted for delivery")
|
ss.send("250 Mail accepted for delivery")
|
||||||
ss.info("Message size %v bytes", msgSize)
|
ss.info("Message size %v bytes", msgSize)
|
||||||
ss.enterState(READY)
|
ss.reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// SMTP RFC says remove leading periods from input
|
// SMTP RFC says remove leading periods from input
|
||||||
if len(line) > 0 && line[0] == '.' {
|
if len(line) > 0 && line[0] == '.' {
|
||||||
line = line[1:]
|
line = line[1:]
|
||||||
}
|
}
|
||||||
msgSize += uint64(len(line))
|
msgSize += len(line)
|
||||||
|
if msgSize > ss.server.maxMessageBytes {
|
||||||
|
// Max message size exceeded
|
||||||
|
ss.send("552 Maximum message size exceeded")
|
||||||
|
ss.error("Max message size exceeded while in DATA")
|
||||||
|
ss.reset()
|
||||||
|
// TODO: Should really cleanup the crap on filesystem...
|
||||||
|
return
|
||||||
|
}
|
||||||
// Append to message objects
|
// Append to message objects
|
||||||
for i, m := range messages {
|
for i, m := range messages {
|
||||||
if err := m.Append(line); err != nil {
|
if err := m.Append(line); err != nil {
|
||||||
ss.error("Failed to append to mailbox %v: %v", mailboxes[i], err)
|
ss.error("Failed to append to mailbox %v: %v", mailboxes[i], err)
|
||||||
ss.send("554 Something went wrong")
|
ss.send("554 Something went wrong")
|
||||||
ss.enterState(READY)
|
ss.reset()
|
||||||
// TODO: Should really cleanup the crap on filesystem...
|
// TODO: Should really cleanup the crap on filesystem...
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -413,8 +443,27 @@ func (ss *Session) parseCmd(line string) (cmd string, arg string, ok bool) {
|
|||||||
return strings.ToUpper(line[0:4]), strings.Trim(line[5:], " "), true
|
return strings.ToUpper(line[0:4]), strings.Trim(line[5:], " "), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseArgs takes the arguments proceeding a command and files them
|
||||||
|
// into a map[string]string after uppercasing each key. Sample arg
|
||||||
|
// string:
|
||||||
|
// " BODY=8BITMIME SIZE=1024"
|
||||||
|
// The leading space is mandatory.
|
||||||
|
func (ss *Session) parseArgs(arg string) (args map[string]string, ok bool) {
|
||||||
|
args = make(map[string]string)
|
||||||
|
re := regexp.MustCompile(" (\\w+)=(\\w+)")
|
||||||
|
pm := re.FindAllStringSubmatch(arg, -1)
|
||||||
|
if pm == nil {
|
||||||
|
ss.error("Failed to parse arg string: '%v'")
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
for _, m := range pm {
|
||||||
|
args[strings.ToUpper(m[1])] = m[2]
|
||||||
|
}
|
||||||
|
ss.trace("ESMTP params: %v", args)
|
||||||
|
return args, true
|
||||||
|
}
|
||||||
|
|
||||||
func (ss *Session) reset() {
|
func (ss *Session) reset() {
|
||||||
ss.info("Resetting session state on RSET request")
|
|
||||||
ss.enterState(READY)
|
ss.enterState(READY)
|
||||||
ss.from = ""
|
ss.from = ""
|
||||||
ss.recipients = nil
|
ss.recipients = nil
|
||||||
|
|||||||
@@ -9,18 +9,19 @@ import (
|
|||||||
|
|
||||||
// Real server code starts here
|
// Real server code starts here
|
||||||
type Server struct {
|
type Server struct {
|
||||||
domain string
|
domain string
|
||||||
port int
|
port int
|
||||||
maxRecips int
|
maxRecips int
|
||||||
maxIdleSeconds int
|
maxIdleSeconds int
|
||||||
dataStore *inbucket.DataStore
|
maxMessageBytes int
|
||||||
|
dataStore *inbucket.DataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init a new Server object
|
// Init a new Server object
|
||||||
func New(domain string, port int) *Server {
|
func New(domain string, port int) *Server {
|
||||||
ds := inbucket.NewDataStore()
|
ds := inbucket.NewDataStore()
|
||||||
return &Server{domain: domain, port: port, maxRecips: 100, maxIdleSeconds: 300,
|
return &Server{domain: domain, port: port, maxRecips: 100, maxIdleSeconds: 300,
|
||||||
dataStore: ds}
|
dataStore: ds, maxMessageBytes: 2048000}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loggers
|
// Loggers
|
||||||
|
|||||||
Reference in New Issue
Block a user