Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
50c83b08f0 | |||
1a1cc2a683 | |||
9f015b99c1 | |||
ff35d6f004 | |||
509ef2a315 | |||
d1a35d0cce |
2 changed files with 34 additions and 40 deletions
|
@ -2,9 +2,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"net"
|
"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.
|
// 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) {
|
CheckUser: func(address *smtp.Address) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
WriteMail: func(mail *smtp.Mail) (uuid.UUID, error) {
|
WriteMail: func(mail *smtp.Mail) error {
|
||||||
fmt.Println(string(mail.Data))
|
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.
|
// AuthenticationBackend is a smtp.AuthenticationBackend implementation that always returns a fixed address for Authenticate.
|
||||||
var AuthenticationBackend = smtp.AuthenticationBackend{
|
var AuthenticationBackend = smtp.AuthenticationBackend{
|
||||||
Authenticate: func(authCommand string) (*smtp.Address, error) {
|
Authenticate: func(initial string, conn *textproto.Conn) (smtp.CheckAddress, error) {
|
||||||
return &smtp.Address{
|
return func(address *smtp.Address) (bool, error) {
|
||||||
Name: "test",
|
return true, nil
|
||||||
Address: "example.org",
|
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
57
smtp.go
57
smtp.go
|
@ -58,9 +58,12 @@ type DatabaseBackend struct {
|
||||||
|
|
||||||
// AuthenticationBackend is a struct that represents an authentication backend
|
// AuthenticationBackend is a struct that represents an authentication backend
|
||||||
type AuthenticationBackend struct {
|
type AuthenticationBackend struct {
|
||||||
Authenticate func(conn *textproto.Conn) (*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) {
|
func readMultilineCodeResponse(conn *textproto.Conn) (int, string, error) {
|
||||||
var lines strings.Builder
|
var lines strings.Builder
|
||||||
for {
|
for {
|
||||||
|
@ -147,7 +150,7 @@ func speakMultiLine(conn *textproto.Conn, lines []string) error {
|
||||||
type Receiver struct {
|
type Receiver struct {
|
||||||
underlyingListener net.Listener
|
underlyingListener net.Listener
|
||||||
hostname string
|
hostname string
|
||||||
ownedDomains map[string]any
|
ownedDomains map[string]struct{}
|
||||||
enforceTLS bool
|
enforceTLS bool
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
database DatabaseBackend
|
database DatabaseBackend
|
||||||
|
@ -156,9 +159,9 @@ type Receiver struct {
|
||||||
|
|
||||||
// NewReceiver creates a new Receiver
|
// NewReceiver creates a new Receiver
|
||||||
func NewReceiver(conn net.Listener, hostname string, ownedDomains []string, enforceTLS bool, database DatabaseBackend, authentication AuthenticationBackend, tlsConfig *tls.Config) *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 {
|
for _, domain := range ownedDomains {
|
||||||
ownedDomainsMap[domain] = nil
|
ownedDomainsMap[domain] = struct{}{}
|
||||||
}
|
}
|
||||||
return &Receiver{
|
return &Receiver{
|
||||||
underlyingListener: conn,
|
underlyingListener: conn,
|
||||||
|
@ -191,7 +194,7 @@ func (fr *Receiver) Serve() error {
|
||||||
func (fr *Receiver) handleConnection(conn net.Conn) {
|
func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||||
var state struct {
|
var state struct {
|
||||||
HELO bool
|
HELO bool
|
||||||
AUTH *Address
|
AUTH CheckAddress
|
||||||
TLS bool
|
TLS bool
|
||||||
FROM *Address
|
FROM *Address
|
||||||
RCPT []*Address
|
RCPT []*Address
|
||||||
|
@ -209,8 +212,6 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Connection from", conn.RemoteAddr().String())
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
line, err := textProto.ReadLine()
|
line, err := textProto.ReadLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -281,6 +282,9 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||||
if fr.enforceTLS {
|
if fr.enforceTLS {
|
||||||
capabilities = append(capabilities, "250-REQUIRETLS")
|
capabilities = append(capabilities, "250-REQUIRETLS")
|
||||||
}
|
}
|
||||||
|
if fr.auth.SupportedMechanisms != nil {
|
||||||
|
capabilities = append(capabilities, "250-AUTH "+strings.Join(fr.auth.SupportedMechanisms, " "))
|
||||||
|
}
|
||||||
capabilities = append(capabilities, defaultCapabilities...)
|
capabilities = append(capabilities, defaultCapabilities...)
|
||||||
state.HELO = true
|
state.HELO = true
|
||||||
err = speakMultiLine(textProto, capabilities)
|
err = speakMultiLine(textProto, capabilities)
|
||||||
|
@ -320,14 +324,14 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
address, err := fr.auth.Authenticate(textProto)
|
checkAddress, err := fr.auth.Authenticate(strings.TrimPrefix(line, "AUTH "), textProto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
_ = textProto.PrintfLine(err.Error())
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if address == nil {
|
if checkAddress == nil {
|
||||||
err = textProto.PrintfLine("535 5.7.8 Authentication failed")
|
err = textProto.PrintfLine("535 5.7.8 Authentication failed")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||||
|
@ -335,7 +339,7 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.AUTH = address
|
state.AUTH = checkAddress
|
||||||
err = textProto.PrintfLine("235 2.7.0 Authentication successful")
|
err = textProto.PrintfLine("235 2.7.0 Authentication successful")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||||
|
@ -410,7 +414,14 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||||
continue
|
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")
|
err = textProto.PrintfLine("535 5.7.8 Authenticated wrong user")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
_ = textProto.PrintfLine("421 4.7.0 Temporary server error")
|
||||||
|
@ -579,7 +590,6 @@ func (fr *Receiver) handleConnection(conn net.Conn) {
|
||||||
Host: strings.Split(conn.RemoteAddr().String(), ":")[0],
|
Host: strings.Split(conn.RemoteAddr().String(), ":")[0],
|
||||||
}
|
}
|
||||||
go sendEmail(SenderArgs{
|
go sendEmail(SenderArgs{
|
||||||
Hostname: fr.hostname,
|
|
||||||
EnforceTLS: fr.enforceTLS,
|
EnforceTLS: fr.enforceTLS,
|
||||||
}, mail, fr.database, queueID)
|
}, 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
|
// SenderArgs is a struct that represents the arguments for the Sender
|
||||||
type SenderArgs struct {
|
type SenderArgs struct {
|
||||||
Hostname string
|
|
||||||
EnforceTLS bool
|
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)
|
return errors.New("unexpected greeting - " + line)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = textConn.PrintfLine("EHLO %s", args.Hostname)
|
err = textConn.PrintfLine("EHLO %s", mxHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -673,16 +682,11 @@ func Send(args SenderArgs, mail *Mail, conn net.Conn, mxHost string) (err error)
|
||||||
InsecureSkipVerify: false,
|
InsecureSkipVerify: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
err = tlsConn.Handshake()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
textConn = textproto.NewConn(tlsConn)
|
textConn = textproto.NewConn(tlsConn)
|
||||||
|
|
||||||
// Just use HELO, no point using EHLO when we already have all the capabilities
|
// Just use HELO, no point using EHLO when we already have all the capabilities
|
||||||
// This also gets us out of using readMultilineCodeResponse
|
// This also gets us out of using readMultilineCodeResponse
|
||||||
err = textConn.PrintfLine("HELO %s", args.Hostname)
|
err = textConn.PrintfLine("HELO %s", mxHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
code, line, err = textConn.ReadCodeLine(0)
|
||||||
fmt.Println(code, line, err)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// For some reason the EHLO stuff ends up here
|
|
||||||
fmt.Println("5")
|
|
||||||
return err
|
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)
|
code, line, err = textConn.ReadCodeLine(0)
|
||||||
fmt.Println(code, line, err)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("6")
|
|
||||||
return err
|
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)
|
code, line, err = textConn.ReadCodeLine(0)
|
||||||
fmt.Println(code, line, err)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("7")
|
|
||||||
return err
|
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)
|
code, line, err = textConn.ReadCodeLine(0)
|
||||||
fmt.Println(code, line, err)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("8")
|
|
||||||
return err
|
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)
|
code, line, err = textConn.ReadCodeLine(0)
|
||||||
fmt.Println(code, line, err)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("9")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue