diff options
author | Runxi Yu <me@runxiyu.org> | 2025-04-01 13:27:26 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-04-01 13:29:14 +0800 |
commit | a8eee4110fe52e132411e4d171e3e08d22fb0079 (patch) | |
tree | e09781c31212628dcae8732cdc7087df7f435d54 /lmtp_server.go | |
parent | Stub LMTP listener (diff) | |
download | forge-a8eee4110fe52e132411e4d171e3e08d22fb0079.tar.gz forge-a8eee4110fe52e132411e4d171e3e08d22fb0079.tar.zst forge-a8eee4110fe52e132411e4d171e3e08d22fb0079.zip |
Basic debugging LMTP handler
Diffstat (limited to 'lmtp_server.go')
-rw-r--r-- | lmtp_server.go | 117 |
1 files changed, 115 insertions, 2 deletions
diff --git a/lmtp_server.go b/lmtp_server.go index 57f319a..8e574e4 100644 --- a/lmtp_server.go +++ b/lmtp_server.go @@ -1,10 +1,123 @@ // SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> +// SPDX-FileCopyrightText: Copyright (c) 2024 Robin Jarry <robin@jarry.cc> package main -import "net" +import ( + "bytes" + "errors" + "io" + "log/slog" + "net" + "strings" -func serveLMTP(_ net.Listener) error { + "github.com/emersion/go-message" + "github.com/emersion/go-smtp" +) + +type lmtpHandler struct{} + +type lmtpSession struct { + from string + to []string +} + +func (session *lmtpSession) Reset() { + session.from = "" + session.to = nil +} + +func (session *lmtpSession) Logout() error { + return nil +} + +func (session *lmtpSession) AuthPlain(_, _ string) error { + return nil +} + +func (session *lmtpSession) Mail(from string, _ *smtp.MailOptions) error { + session.from = from + return nil +} + +func (session *lmtpSession) Rcpt(to string, _ *smtp.RcptOptions) error { + session.to = append(session.to, to) return nil } + +func (*lmtpHandler) NewSession(_ *smtp.Conn) (smtp.Session, error) { + // TODO + session := &lmtpSession{} + return session, nil +} + +func serveLMTP(listener net.Listener) error { + smtpServer := smtp.NewServer(&lmtpHandler{}) + return smtpServer.Serve(listener) +} + +func (session *lmtpSession) Data(r io.Reader) error { + var ( + email *message.Entity + from string + to []string + err error + buf bytes.Buffer + data []byte + n int64 + ) + + n, err = io.CopyN(&buf, r, config.LMTP.MaxSize) + switch { + case n == config.LMTP.MaxSize: + err = errors.New("Message too big.") + // drain whatever is left in the pipe + _, _ = io.Copy(io.Discard, r) + goto end + case errors.Is(err, io.EOF): + // message was smaller than max size + break + case err != nil: + goto end + } + + data = buf.Bytes() + + email, err = message.Read(bytes.NewReader(data)) + if err != nil && message.IsUnknownCharset(err) { + goto end + } + + switch strings.ToLower(email.Header.Get("Auto-Submitted")) { + case "auto-generated", "auto-replied": + // disregard automatic emails like OOO replies + slog.Info("ignoring automatic message", + "from", session.from, + "to", strings.Join(session.to, ","), + "message-id", email.Header.Get("Message-Id"), + "subject", email.Header.Get("Subject"), + ) + goto end + } + + slog.Info("message received", + "from", session.from, + "to", strings.Join(session.to, ","), + "message-id", email.Header.Get("Message-Id"), + "subject", email.Header.Get("Subject"), + ) + + // Make local copies of the values before to ensure the references will + // still be valid when the queued task function is evaluated. + from = session.from + to = session.to + + // TODO: Process the actual message contents + _, _ = from, to + +end: + session.to = nil + session.from = "" + return err +} |