Files
nzx056/nzx_tmpmail/network/lmtp.go
2026-04-29 17:49:51 +02:00

137 lines
2.8 KiB
Go

package network
import (
"bufio"
"fmt"
"io"
"net"
"strings"
"git.uwushka.cc/nzx056/nzx056/nzx_tmpmail/loggingshit"
)
type Session struct {
Conn net.Conn
Reader *bufio.Reader
Writer *bufio.Writer
MailFrom string
RcptTo []string
RemoteAddr string
hasMailFrom bool
hasRcptTo bool
}
func init() {
loggingshit.Log("lmtp init", 1)
ln, err := net.Listen("tcp", "127.0.0.1:2525")
if err != nil {
loggingshit.Log("lmtp init failed with error %v", 3, err)
}
for {
conn, err := ln.Accept()
if err != nil {
loggingshit.Log("failed to accept connection with error %v, skipping", 2, err)
continue
}
go handle_conn(conn)
}
}
func handle_conn(conn net.Conn) {
defer conn.Close()
s := &Session{
Conn: conn,
Writer: bufio.NewWriter(conn),
Reader: bufio.NewReader(conn),
RemoteAddr: conn.RemoteAddr().String(),
}
s.reply("220 nzx056.love LMTP ready")
for {
l, err := s.readline()
if err != nil {
if err != io.EOF {
loggingshit.Log("readline failed for %s with error: %v", 2, s.RemoteAddr, err)
}
loggingshit.Log("caught EOF from %s, closing conn", 2, s.RemoteAddr)
return
}
upper := strings.ToUpper(l)
switch {
case strings.HasPrefix(upper, "LHLO "):
s.reply("250-nzx056.love")
s.reply("250-8BITMIME")
s.reply("250 PIPELINING")
case strings.HasPrefix(upper, "MAIL FROM:"):
s.MailFrom = extractSender(l[len("MAIL FROM:"):])
s.hasMailFrom = true
s.RcptTo = nil
s.reply("250 2.1.0 Ok")
case strings.HasPrefix(upper, "RCPT TO:"):
rcpt := strings.ToLower(extractSender(l[len("RCPT TO:"):]))
s.hasRcptTo = false
if !strings.HasSuffix(rcpt, "@nzx056.love") {
s.reply("550 5.1.1 User unknown")
loggingshit.Log("got an email for unknown domain %s, skipping", 1, rcpt)
continue
}
s.RcptTo = append(s.RcptTo, rcpt)
s.reply("250 2.1.5 Ok")
case upper == "DATA":
if !s.hasMailFrom {
s.reply("503 5.5.1 Need MAIL FROM first")
continue
}
if !s.hasRcptTo {
s.reply("503 5.5.1 Need RCPT TO first")
continue
}
s.reply("354 End data with <CR><LF>.<CR><LF>")
}
}
}
func extractSender(s string) string {
s = strings.TrimSpace(s)
if strings.HasPrefix(s, "<") {
end := strings.Index(s, ">")
if end >= 0 {
return s[1:end]
}
}
fields := strings.Fields(s)
if len(fields) == 0 {
return ""
}
return strings.Trim(fields[0], "<>")
}
func (s *Session) readline() (string, error) {
l, err := s.Reader.ReadString('\n')
if err != nil {
return "", err
}
l = strings.TrimRight(l, "\r\n")
loggingshit.Log("readline %s from %s", 1, l, s.RemoteAddr)
return l, nil
}
func (s *Session) reply(st string) {
loggingshit.Log("write to %s: %s", 1, s.RemoteAddr, st)
fmt.Fprintf(s.Writer, "%s\r\n", st)
s.Writer.Flush()
}