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() }