Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
50c83b08f0 | |||
1a1cc2a683 | |||
9f015b99c1 | |||
ff35d6f004 | |||
509ef2a315 | |||
d1a35d0cce | |||
8dd23955ad |
2 changed files with 42 additions and 48 deletions
|
@ -2,9 +2,11 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"net"
|
||||
"smtp"
|
||||
|
||||
"net/textproto"
|
||||
|
||||
"git.ailur.dev/ailur/smtp"
|
||||
)
|
||||
|
||||
// DatabaseBackend is a smtp.DatabaseBackend implementation that always returns true for CheckUser and prints the mail data to stdout.
|
||||
|
@ -12,18 +14,17 @@ var DatabaseBackend = smtp.DatabaseBackend{
|
|||
CheckUser: func(address *smtp.Address) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
WriteMail: func(mail *smtp.Mail) (uuid.UUID, error) {
|
||||
WriteMail: func(mail *smtp.Mail) error {
|
||||
fmt.Println(string(mail.Data))
|
||||
return uuid.New(), nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// AuthenticationBackend is a smtp.AuthenticationBackend implementation that always returns a fixed address for Authenticate.
|
||||
var AuthenticationBackend = smtp.AuthenticationBackend{
|
||||
Authenticate: func(authCommand string) (*smtp.Address, error) {
|
||||
return &smtp.Address{
|
||||
Name: "test",
|
||||
Address: "example.org",
|
||||
Authenticate: func(initial string, conn *textproto.Conn) (smtp.CheckAddress, error) {
|
||||
return func(address *smtp.Address) (bool, error) {
|
||||
return true, nil
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
|
73
smtp.go
73
smtp.go
|
@ -7,12 +7,12 @@ import (
|
|||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto/tls"
|
||||
"net/textproto"
|
||||
|
||||
"git.ailur.dev/ailur/spf"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -22,7 +22,7 @@ var (
|
|||
"250-SMTPUTF8",
|
||||
"250 BINARYMIME",
|
||||
}
|
||||
queue = make(map[uuid.UUID]*MailQueueItem)
|
||||
queue = make(map[time.Time]*MailQueueItem)
|
||||
)
|
||||
|
||||
// MailQueueItem is a struct that represents an item in the mail queue
|
||||
|
@ -33,7 +33,7 @@ type MailQueueItem struct {
|
|||
}
|
||||
|
||||
// ViewMailQueue returns the current mail queue
|
||||
func ViewMailQueue() map[uuid.UUID]*MailQueueItem {
|
||||
func ViewMailQueue() map[time.Time]*MailQueueItem {
|
||||
return queue
|
||||
}
|
||||
|
||||
|
@ -53,14 +53,17 @@ type Mail struct {
|
|||
// DatabaseBackend is a struct that represents a database backend
|
||||
type DatabaseBackend struct {
|
||||
CheckUser func(*Address) (bool, error)
|
||||
WriteMail func(*Mail) (uuid.UUID, error)
|
||||
WriteMail func(*Mail) error
|
||||
}
|
||||
|
||||
// AuthenticationBackend is a struct that represents an authentication backend
|
||||
type AuthenticationBackend struct {
|
||||
Authenticate func(string) (*Address, error)
|
||||
Authenticate func(initial string, conn *textproto.Conn) (CheckAddress, error)
|
||||
SupportedMechanisms []string
|
||||
}
|
||||
|
||||
type CheckAddress func(*Address) (bool, error)
|
||||
|
||||
func readMultilineCodeResponse(conn *textproto.Conn) (int, string, error) {
|
||||
var lines strings.Builder
|
||||
for {
|
||||
|
@ -83,7 +86,7 @@ func readMultilineCodeResponse(conn *textproto.Conn) (int, string, error) {
|
|||
}
|
||||
|
||||
func systemError(err error, receiver *Address, database DatabaseBackend) {
|
||||
_, _ = database.WriteMail(&Mail{
|
||||
_ = database.WriteMail(&Mail{
|
||||
From: &Address{
|
||||
Name: "EMail System",
|
||||
Address: "system",
|
||||
|
@ -93,7 +96,7 @@ func systemError(err error, receiver *Address, database DatabaseBackend) {
|
|||
})
|
||||
}
|
||||
|
||||
func sendEmail(args SenderArgs, mail *Mail, database DatabaseBackend, queueID uuid.UUID) {
|
||||
func sendEmail(args SenderArgs, mail *Mail, database DatabaseBackend, queueID time.Time) {
|
||||
mxs, err := net.LookupMX(mail.To[0].Address)
|
||||
if err != nil {
|
||||
systemError(err, queue[queueID].From, database)
|
||||
|
@ -147,7 +150,7 @@ func speakMultiLine(conn *textproto.Conn, lines []string) error {
|
|||
type Receiver struct {
|
||||
underlyingListener net.Listener
|
||||
hostname string
|
||||
ownedDomains map[string]any
|
||||
ownedDomains map[string]struct{}
|
||||
enforceTLS bool
|
||||
tlsConfig *tls.Config
|
||||
database DatabaseBackend
|
||||
|
@ -156,9 +159,9 @@ type Receiver struct {
|
|||
|
||||
// NewReceiver creates a new Receiver
|
||||
func NewReceiver(conn net.Listener, hostname string, ownedDomains []string, enforceTLS bool, database DatabaseBackend, authentication AuthenticationBackend, tlsConfig *tls.Config) *Receiver {
|
||||
var ownedDomainsMap = make(map[string]any)
|
||||
var ownedDomainsMap = make(map[string]struct{})
|
||||
for _, domain := range ownedDomains {
|
||||
ownedDomainsMap[domain] = nil
|
||||
ownedDomainsMap[domain] = struct{}{}
|
||||
}
|
||||
return &Receiver{
|
||||
underlyingListener: conn,
|
||||
|
@ -191,7 +194,7 @@ func (fr *Receiver) Serve() error {
|
|||
func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||
var state struct {
|
||||
HELO bool
|
||||
AUTH *Address
|
||||
AUTH CheckAddress
|
||||
TLS bool
|
||||
FROM *Address
|
||||
RCPT []*Address
|
||||
|
@ -209,8 +212,6 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
return
|
||||
}
|
||||
|
||||
fmt.Println("Connection from", conn.RemoteAddr().String())
|
||||
|
||||
for {
|
||||
line, err := textProto.ReadLine()
|
||||
if err != nil {
|
||||
|
@ -281,6 +282,9 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
if fr.enforceTLS {
|
||||
capabilities = append(capabilities, "250-REQUIRETLS")
|
||||
}
|
||||
if fr.auth.SupportedMechanisms != nil {
|
||||
capabilities = append(capabilities, "250-AUTH "+strings.Join(fr.auth.SupportedMechanisms, " "))
|
||||
}
|
||||
capabilities = append(capabilities, defaultCapabilities...)
|
||||
state.HELO = true
|
||||
err = speakMultiLine(textProto, capabilities)
|
||||
|
@ -320,14 +324,14 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
}
|
||||
continue
|
||||
} else {
|
||||
address, err := fr.auth.Authenticate(line)
|
||||
checkAddress, err := fr.auth.Authenticate(strings.TrimPrefix(line, "AUTH "), textProto)
|
||||
if err != nil {
|
||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||
_ = textProto.PrintfLine(err.Error())
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if address == nil {
|
||||
if checkAddress == nil {
|
||||
err = textProto.PrintfLine("535 5.7.8 Authentication failed")
|
||||
if err != nil {
|
||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||
|
@ -335,7 +339,7 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
return
|
||||
}
|
||||
} else {
|
||||
state.AUTH = address
|
||||
state.AUTH = checkAddress
|
||||
err = textProto.PrintfLine("235 2.7.0 Authentication successful")
|
||||
if err != nil {
|
||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||
|
@ -410,7 +414,14 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
continue
|
||||
}
|
||||
|
||||
if *address != *state.AUTH {
|
||||
ok, err := state.AUTH(address)
|
||||
if err != nil {
|
||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
err = textProto.PrintfLine("535 5.7.8 Authenticated wrong user")
|
||||
if err != nil {
|
||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||
|
@ -557,7 +568,7 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
}
|
||||
|
||||
if !isSubmission {
|
||||
_, err := fr.database.WriteMail(mail)
|
||||
err := fr.database.WriteMail(mail)
|
||||
if err != nil {
|
||||
_ = textProto.PrintfLine(err.Error())
|
||||
_ = conn.Close()
|
||||
|
@ -571,7 +582,7 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
return
|
||||
}
|
||||
} else {
|
||||
queueID := uuid.New()
|
||||
queueID := time.Now()
|
||||
|
||||
queue[queueID] = &MailQueueItem{
|
||||
From: state.FROM,
|
||||
|
@ -579,7 +590,6 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
Host: strings.Split(conn.RemoteAddr().String(), ":")[0],
|
||||
}
|
||||
go sendEmail(SenderArgs{
|
||||
Hostname: fr.hostname,
|
||||
EnforceTLS: fr.enforceTLS,
|
||||
}, mail, fr.database, queueID)
|
||||
|
||||
|
@ -608,7 +618,6 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
|||
|
||||
// SenderArgs is a struct that represents the arguments for the Sender
|
||||
type SenderArgs struct {
|
||||
Hostname string
|
||||
EnforceTLS bool
|
||||
}
|
||||
|
||||
|
@ -639,7 +648,7 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
|||
return errors.New("unexpected greeting - " + line)
|
||||
}
|
||||
|
||||
err = textConn.PrintfLine("EHLO %s", args.Hostname)
|
||||
err = textConn.PrintfLine("EHLO %s", mxHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -673,16 +682,11 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
|||
InsecureSkipVerify: false,
|
||||
})
|
||||
|
||||
err = tlsConn.Handshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
textConn = textproto.NewConn(tlsConn)
|
||||
|
||||
// Just use HELO, no point using EHLO when we already have all the capabilities
|
||||
// This also gets us out of using readMultilineCodeResponse
|
||||
err = textConn.PrintfLine("HELO %s", args.Hostname)
|
||||
err = textConn.PrintfLine("HELO %s", mxHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -705,10 +709,7 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
|||
}
|
||||
|
||||
code, line, err = textConn.ReadCodeLine(0)
|
||||
fmt.Println(code, line, err)
|
||||
if err != nil {
|
||||
// For some reason the EHLO stuff ends up here
|
||||
fmt.Println("5")
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -723,9 +724,7 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
|||
}
|
||||
|
||||
code, line, err = textConn.ReadCodeLine(0)
|
||||
fmt.Println(code, line, err)
|
||||
if err != nil {
|
||||
fmt.Println("6")
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -740,9 +739,7 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
|||
}
|
||||
|
||||
code, line, err = textConn.ReadCodeLine(0)
|
||||
fmt.Println(code, line, err)
|
||||
if err != nil {
|
||||
fmt.Println("7")
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -762,9 +759,7 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
|||
}
|
||||
|
||||
code, line, err = textConn.ReadCodeLine(0)
|
||||
fmt.Println(code, line, err)
|
||||
if err != nil {
|
||||
fmt.Println("8")
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -778,9 +773,7 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
|||
}
|
||||
|
||||
code, line, err = textConn.ReadCodeLine(0)
|
||||
fmt.Println(code, line, err)
|
||||
if err != nil {
|
||||
fmt.Println("9")
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue