smtpd/smtpd.go

325 lines
7.3 KiB
Go
Raw Normal View History

2014-07-20 19:53:47 +00:00
// Package smtpd implements an SMTP server with support for STARTTLS, authentication (PLAIN/LOGIN), XCLIENT and optional restrictions on the different stages of the SMTP session.
2014-07-13 21:24:13 +00:00
package smtpd
import (
"bufio"
"crypto/tls"
"fmt"
"log"
"net"
2014-07-14 12:20:36 +00:00
"time"
2014-07-13 21:24:13 +00:00
)
2014-07-14 12:51:31 +00:00
// Server defines the parameters for running the SMTP server
2014-07-13 21:24:13 +00:00
type Server struct {
Hostname string // Server hostname. (default: "localhost.localdomain")
2014-07-15 09:16:34 +00:00
WelcomeMessage string // Initial server banner. (default: "<hostname> ESMTP ready.")
2014-07-13 21:24:13 +00:00
2014-07-15 09:16:34 +00:00
ReadTimeout time.Duration // Socket timeout for read operations. (default: 60s)
WriteTimeout time.Duration // Socket timeout for write operations. (default: 60s)
DataTimeout time.Duration // Socket timeout for DATA command (default: 5m)
2014-07-13 21:24:13 +00:00
2014-07-15 09:16:34 +00:00
MaxConnections int // Max concurrent connections, use -1 to disable. (default: 100)
MaxMessageSize int // Max message size in bytes. (default: 10240000)
MaxRecipients int // Max RCPT TO calls for each envelope. (default: 100)
2014-07-13 21:24:13 +00:00
// New e-mails are handed off to this function.
2014-07-14 11:55:41 +00:00
// Can be left empty for a NOOP server.
// If an error is returned, it will be reported in the SMTP session.
2014-07-13 21:24:13 +00:00
Handler func(peer Peer, env Envelope) error
2014-07-14 11:55:41 +00:00
// Enable various checks during the SMTP session.
// Can be left empty for no restrictions.
// If an error is returned, it will be reported in the SMTP session.
2014-07-15 09:16:34 +00:00
// Use the Error struct for access to error codes.
ConnectionChecker func(peer Peer) error // Called upon new connection.
HeloChecker func(peer Peer, name string) error // Called after HELO/EHLO.
2014-07-15 09:16:34 +00:00
SenderChecker func(peer Peer, addr string) error // Called after MAIL FROM.
RecipientChecker func(peer Peer, addr string) error // Called after each RCPT TO.
2014-07-14 11:55:41 +00:00
// Enable PLAIN/LOGIN authentication, only available after STARTTLS.
// Can be left empty for no authentication support.
2014-07-13 21:24:13 +00:00
Authenticator func(peer Peer, username, password string) error
2014-07-20 19:51:39 +00:00
EnableXCLIENT bool // Enable XCLIENT support (default: false)
2014-07-15 09:16:34 +00:00
TLSConfig *tls.Config // Enable STARTTLS support.
ForceTLS bool // Force STARTTLS usage.
2014-07-13 21:24:13 +00:00
}
2014-07-20 19:51:39 +00:00
// Protocol represents the protocol used in the SMTP session
type Protocol string
const (
SMTP Protocol = "SMTP"
ESMTP = "ESMTP"
)
2014-07-14 12:51:31 +00:00
// Peer represents the client connecting to the server
2014-07-13 21:24:13 +00:00
type Peer struct {
HeloName string // Server name used in HELO/EHLO command
Username string // Username from authentication, if authenticated
Password string // Password from authentication, if authenticated
Protocol Protocol // Protocol used, SMTP or ESMTP
ServerName string // A copy of Server.Hostname
Addr net.Addr // Network address
TLS *tls.ConnectionState // TLS Connection details, if on TLS
2014-07-13 21:24:13 +00:00
}
2014-07-15 09:16:34 +00:00
// Error represents an Error reported in the SMTP session.
type Error struct {
Code int // The integer error code
Message string // The error message
}
// Error returns a string representation of the SMTP error
func (e Error) Error() string { return fmt.Sprintf("%d %s", e.Code, e.Message) }
2014-07-14 11:55:41 +00:00
type session struct {
server *Server
2014-07-14 12:20:36 +00:00
peer Peer
2014-07-14 11:55:41 +00:00
envelope *Envelope
2014-07-14 12:20:36 +00:00
conn net.Conn
2014-07-14 11:55:41 +00:00
2014-07-14 12:20:36 +00:00
reader *bufio.Reader
writer *bufio.Writer
2014-07-14 11:55:41 +00:00
scanner *bufio.Scanner
2014-07-14 12:20:36 +00:00
tls bool
2014-07-14 11:55:41 +00:00
}
2014-07-15 08:11:37 +00:00
func (srv *Server) newSession(c net.Conn) (s *session) {
2014-07-13 21:24:13 +00:00
s = &session{
server: srv,
conn: c,
reader: bufio.NewReader(c),
writer: bufio.NewWriter(c),
peer: Peer{
Addr: c.RemoteAddr(),
ServerName: srv.Hostname,
},
2014-07-13 21:24:13 +00:00
}
2014-07-14 12:20:36 +00:00
2014-07-14 11:55:41 +00:00
s.scanner = bufio.NewScanner(s.reader)
2014-07-13 21:24:13 +00:00
2014-07-15 08:11:37 +00:00
return
2014-07-13 21:24:13 +00:00
}
// ListenAndServe starts the SMTP server and listens on the address provided
func (srv *Server) ListenAndServe(addr string) error {
2014-07-14 11:55:41 +00:00
srv.configureDefaults()
l, err := net.Listen("tcp", addr)
2014-07-13 21:24:13 +00:00
if err != nil {
return err
}
2014-07-14 17:44:10 +00:00
2014-07-13 21:24:13 +00:00
return srv.Serve(l)
}
2014-07-14 12:51:31 +00:00
// Serve starts the SMTP server and listens on the Listener provided
2014-07-13 21:24:13 +00:00
func (srv *Server) Serve(l net.Listener) error {
srv.configureDefaults()
defer l.Close()
var limiter chan struct{}
if srv.MaxConnections > 0 {
limiter = make(chan struct{}, srv.MaxConnections)
} else {
limiter = nil
}
2014-07-13 21:24:13 +00:00
for {
conn, e := l.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
time.Sleep(time.Second)
continue
}
return e
}
2014-07-15 08:11:37 +00:00
session := srv.newSession(conn)
2014-07-13 21:24:13 +00:00
if limiter != nil {
go func() {
select {
case limiter <- struct{}{}:
session.serve()
<-limiter
default:
session.reject()
}
}()
} else {
go session.serve()
}
2014-07-13 21:24:13 +00:00
}
}
func (srv *Server) configureDefaults() {
if srv.MaxMessageSize == 0 {
srv.MaxMessageSize = 10240000
}
if srv.MaxConnections == 0 {
srv.MaxConnections = 100
}
if srv.MaxRecipients == 0 {
srv.MaxRecipients = 100
}
2014-07-13 21:24:13 +00:00
if srv.ReadTimeout == 0 {
srv.ReadTimeout = time.Second * 60
}
if srv.WriteTimeout == 0 {
srv.WriteTimeout = time.Second * 60
}
if srv.DataTimeout == 0 {
srv.DataTimeout = time.Minute * 5
}
2014-07-13 21:24:13 +00:00
if srv.ForceTLS && srv.TLSConfig == nil {
log.Fatal("Cannot use ForceTLS with no TLSConfig")
}
if srv.Hostname == "" {
srv.Hostname = "localhost.localdomain"
2014-07-14 11:55:41 +00:00
}
2014-07-13 21:24:13 +00:00
2014-07-14 11:55:41 +00:00
if srv.WelcomeMessage == "" {
srv.WelcomeMessage = fmt.Sprintf("%s ESMTP ready.", srv.Hostname)
2014-07-14 11:55:41 +00:00
}
2014-07-13 21:24:13 +00:00
2014-07-14 11:55:41 +00:00
}
2014-07-13 21:24:13 +00:00
2014-07-14 11:55:41 +00:00
func (session *session) serve() {
2014-07-13 21:24:13 +00:00
2014-07-14 11:55:41 +00:00
defer session.close()
2014-07-13 21:24:13 +00:00
2014-07-15 09:16:34 +00:00
session.welcome()
2014-07-13 21:24:13 +00:00
for {
for session.scanner.Scan() {
session.handle(session.scanner.Text())
}
err := session.scanner.Err()
if err == bufio.ErrTooLong {
session.reply(500, "Line too long")
// Advance reader to the next newline
session.reader.ReadString('\n')
session.scanner = bufio.NewScanner(session.reader)
// Reset and have the client start over.
session.reset()
continue
}
break
2014-07-13 21:24:13 +00:00
}
}
func (session *session) reject() {
session.reply(421, "Too busy. Try again later.")
session.close()
2014-07-13 21:24:13 +00:00
}
func (session *session) reset() {
session.envelope = nil
}
2014-07-15 09:16:34 +00:00
func (session *session) welcome() {
if session.server.ConnectionChecker != nil {
err := session.server.ConnectionChecker(session.peer)
if err != nil {
session.error(err)
session.close()
return
}
}
session.reply(220, session.server.WelcomeMessage)
}
2014-07-13 21:24:13 +00:00
func (session *session) reply(code int, message string) {
fmt.Fprintf(session.writer, "%d %s\r\n", code, message)
session.flush()
}
2014-07-13 21:24:13 +00:00
func (session *session) flush() {
2014-07-13 21:24:13 +00:00
session.conn.SetWriteDeadline(time.Now().Add(session.server.WriteTimeout))
session.writer.Flush()
session.conn.SetReadDeadline(time.Now().Add(session.server.ReadTimeout))
}
2014-07-14 11:55:41 +00:00
func (session *session) error(err error) {
2014-07-15 09:16:34 +00:00
if smtpdError, ok := err.(Error); ok {
session.reply(smtpdError.Code, smtpdError.Message)
} else {
session.reply(502, fmt.Sprintf("%s", err))
}
2014-07-14 11:55:41 +00:00
}
func (session *session) extensions() []string {
2014-07-13 21:24:13 +00:00
extensions := []string{
2014-07-14 12:51:31 +00:00
fmt.Sprintf("SIZE %d", session.server.MaxMessageSize),
2014-07-14 17:44:10 +00:00
"8BITMIME",
"PIPELINING",
2014-07-13 21:24:13 +00:00
}
2014-07-20 19:51:39 +00:00
if session.server.EnableXCLIENT {
extensions = append(extensions, "XCLIENT")
}
2014-07-13 21:24:13 +00:00
if session.server.TLSConfig != nil && !session.tls {
extensions = append(extensions, "STARTTLS")
}
2014-07-14 11:59:30 +00:00
if session.server.Authenticator != nil && session.tls {
2014-07-13 21:24:13 +00:00
extensions = append(extensions, "AUTH PLAIN LOGIN")
}
2014-07-14 11:55:41 +00:00
return extensions
2014-07-13 21:24:13 +00:00
}
2014-07-14 11:55:41 +00:00
func (session *session) deliver() error {
2014-07-13 21:24:13 +00:00
if session.server.Handler != nil {
2014-07-14 11:55:41 +00:00
return session.server.Handler(session.peer, *session.envelope)
2014-07-13 21:24:13 +00:00
}
2014-07-14 12:51:31 +00:00
return nil
2014-07-13 21:24:13 +00:00
}
2014-07-14 11:55:41 +00:00
func (session *session) close() {
session.writer.Flush()
2014-07-15 10:37:25 +00:00
time.Sleep(200 * time.Millisecond)
2014-07-14 11:55:41 +00:00
session.conn.Close()
2014-07-13 21:24:13 +00:00
}