125 lines
2.9 KiB
Go
125 lines
2.9 KiB
Go
package smtp
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/mail"
|
|
"time"
|
|
|
|
smtplib "github.com/emersion/go-smtp"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type backend struct {
|
|
honeypot *SMTPHoneypot
|
|
}
|
|
|
|
func (b *backend) Login(state *smtplib.ConnectionState, username, password string) (smtplib.Session, error) {
|
|
sess := &session{
|
|
honeypot: b.honeypot,
|
|
loginUsername: username,
|
|
loginPassword: password,
|
|
remoteAddr: state.RemoteAddr.String(),
|
|
}
|
|
return sess, nil
|
|
}
|
|
func (b *backend) AnonymousLogin(state *smtplib.ConnectionState) (smtplib.Session, error) {
|
|
sess := &session{
|
|
loginAnonymous: true,
|
|
remoteAddr: state.RemoteAddr.String(),
|
|
}
|
|
return sess, nil
|
|
}
|
|
|
|
type session struct {
|
|
honeypot *SMTPHoneypot
|
|
|
|
loginUsername string
|
|
loginPassword string
|
|
loginAnonymous bool
|
|
remoteAddr string
|
|
|
|
mail MailData
|
|
}
|
|
|
|
func (sess *session) Reset() {
|
|
}
|
|
func (sess *session) Logout() error {
|
|
return nil
|
|
}
|
|
func (sess *session) Mail(from string, opts smtplib.MailOptions) error {
|
|
sess.mail.From = from
|
|
return nil
|
|
}
|
|
func (sess *session) Rcpt(to string) error {
|
|
sess.mail.Recipients = append(sess.mail.Recipients, to)
|
|
return nil
|
|
}
|
|
func (sess *session) Data(r io.Reader) error {
|
|
lr := io.LimitReader(r, 1024*1024)
|
|
var buf bytes.Buffer
|
|
n, err := io.Copy(&buf, lr)
|
|
if err != nil {
|
|
sess.honeypot.Logger.Debugw("Error reading email data from client.", "error", err, "remote_addr", sess.remoteAddr)
|
|
}
|
|
|
|
if n == 1024*1024 {
|
|
// There is probably more data, read the rest from the client
|
|
// We don't really care what happens to this, or if it errors
|
|
_, _ = io.Copy(io.Discard, r)
|
|
}
|
|
|
|
sess.mail.Data = buf.String()
|
|
|
|
var toAddr string
|
|
var subject string
|
|
// Parse mail headers
|
|
msg, err := mail.ReadMessage(&buf)
|
|
if err != nil {
|
|
sess.honeypot.Logger.Infow("Unable to parse mail.", "error", err, "remote_addr", sess.remoteAddr)
|
|
} else {
|
|
toAddr = msg.Header.Get("To")
|
|
subject = msg.Header.Get("Subject")
|
|
}
|
|
|
|
if err := sess.honeypot.Store.AddMailData(&sess.mail); err != nil {
|
|
sess.honeypot.Logger.Warnw("Error storing maildata.", "error", err, "remote_addr", sess.remoteAddr)
|
|
return smtplib.ErrDataTooLarge
|
|
}
|
|
sess.honeypot.Logger.Infow("Mail received", "from", sess.mail.From, "to", toAddr, "subject", subject, "remote_addr", sess.remoteAddr, "size", len(sess.mail.Data))
|
|
|
|
return nil
|
|
}
|
|
|
|
type SMTPHoneypot struct {
|
|
Addr string
|
|
Logger *zap.SugaredLogger
|
|
Store SMTPStore
|
|
server *smtplib.Server
|
|
}
|
|
|
|
func NewSMTPHoneypot() (*SMTPHoneypot, error) {
|
|
honeypot := &SMTPHoneypot{
|
|
Logger: zap.NewNop().Sugar(),
|
|
}
|
|
|
|
return honeypot, nil
|
|
}
|
|
|
|
func (sh *SMTPHoneypot) ListenAndServe() error {
|
|
server := smtplib.NewServer(&backend{
|
|
honeypot: sh,
|
|
})
|
|
server.Addr = sh.Addr
|
|
server.Domain = "apiary.t-juice.club"
|
|
server.AllowInsecureAuth = true
|
|
server.ReadTimeout = 10 * time.Second
|
|
server.WriteTimeout = 10 * time.Second
|
|
sh.server = server
|
|
return server.ListenAndServe()
|
|
}
|
|
|
|
func (sh *SMTPHoneypot) Shutdown() error {
|
|
return sh.server.Close()
|
|
}
|