aboutsummaryrefslogtreecommitdiff
path: root/lmtp_server.go
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-04-06 01:55:21 +0800
committerRunxi Yu <me@runxiyu.org>2025-04-06 02:08:58 +0800
commitfaa5ca8fab23176d390e9522f1485d467851545b (patch)
treed3b1d081e0ea5e7f71a94dc1d301e2540a8abcc8 /lmtp_server.go
parentSlight refactor on NewServer (diff)
downloadforge-0.1.28.tar.gz
forge-0.1.28.tar.zst
forge-0.1.28.zip
Move stuff into internal/unsortedv0.1.28
Diffstat (limited to 'lmtp_server.go')
-rw-r--r--lmtp_server.go204
1 files changed, 0 insertions, 204 deletions
diff --git a/lmtp_server.go b/lmtp_server.go
deleted file mode 100644
index 593dc5a..0000000
--- a/lmtp_server.go
+++ /dev/null
@@ -1,204 +0,0 @@
-// 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 forge
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "io"
- "log/slog"
- "net"
- "strings"
- "time"
-
- "github.com/emersion/go-message"
- "github.com/emersion/go-smtp"
- "go.lindenii.runxiyu.org/forge/internal/misc"
-)
-
-type lmtpHandler struct{}
-
-type lmtpSession struct {
- from string
- to []string
- ctx context.Context
- cancel context.CancelFunc
- s Server
-}
-
-func (session *lmtpSession) Reset() {
- session.from = ""
- session.to = nil
-}
-
-func (session *lmtpSession) Logout() error {
- session.cancel()
- 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) {
- ctx, cancel := context.WithCancel(context.Background())
- session := &lmtpSession{
- ctx: ctx,
- cancel: cancel,
- }
- return session, nil
-}
-
-func (s *Server) serveLMTP(listener net.Listener) error {
- smtpServer := smtp.NewServer(&lmtpHandler{})
- smtpServer.LMTP = true
- smtpServer.Domain = s.config.LMTP.Domain
- smtpServer.Addr = s.config.LMTP.Socket
- smtpServer.WriteTimeout = time.Duration(s.config.LMTP.WriteTimeout) * time.Second
- smtpServer.ReadTimeout = time.Duration(s.config.LMTP.ReadTimeout) * time.Second
- smtpServer.EnableSMTPUTF8 = true
- 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, session.s.config.LMTP.MaxSize)
- switch {
- case n == session.s.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 task is run.
- from = session.from
- to = session.to
-
- _ = from
-
- for _, to := range to {
- if !strings.HasSuffix(to, "@"+session.s.config.LMTP.Domain) {
- continue
- }
- localPart := to[:len(to)-len("@"+session.s.config.LMTP.Domain)]
- var segments []string
- segments, err = misc.PathToSegments(localPart)
- if err != nil {
- // TODO: Should the entire email fail or should we just
- // notify them out of band?
- err = fmt.Errorf("cannot parse path: %w", err)
- goto end
- }
- sepIndex := -1
- for i, part := range segments {
- if part == "-" {
- sepIndex = i
- break
- }
- }
- if segments[len(segments)-1] == "" {
- segments = segments[:len(segments)-1] // We don't care about dir or not.
- }
- if sepIndex == -1 || len(segments) <= sepIndex+2 {
- err = errors.New("illegal path")
- goto end
- }
-
- mbox := bytes.Buffer{}
- if _, err = fmt.Fprint(&mbox, "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\r\n"); err != nil {
- slog.Error("error handling patch... malloc???", "error", err)
- goto end
- }
- data = bytes.ReplaceAll(data, []byte("\r\n"), []byte("\n"))
- if _, err = mbox.Write(data); err != nil {
- slog.Error("error handling patch... malloc???", "error", err)
- goto end
- }
- // TODO: Is mbox's From escaping necessary here?
-
- groupPath := segments[:sepIndex]
- moduleType := segments[sepIndex+1]
- moduleName := segments[sepIndex+2]
- switch moduleType {
- case "repos":
- err = session.s.lmtpHandlePatch(session, groupPath, moduleName, &mbox)
- if err != nil {
- slog.Error("error handling patch", "error", err)
- goto end
- }
- default:
- err = errors.New("Emailing any endpoint other than repositories, is not supported yet.") // TODO
- goto end
- }
- }
-
-end:
- session.to = nil
- session.from = ""
- switch err {
- case nil:
- return nil
- default:
- return &smtp.SMTPError{
- Code: 550,
- Message: "Permanent failure: " + err.Error(),
- EnhancedCode: [3]int{5, 7, 1},
- }
- }
-}