diff --git a/smtp/connection.go b/smtp/connection.go index a185b84..05b280a 100644 --- a/smtp/connection.go +++ b/smtp/connection.go @@ -20,30 +20,65 @@ package smtp import ( "net" + // "fmt" + "strings" "github.com/rs/zerolog/log" ) type Connection struct { connection net.Conn + + ReversePathBuffer *string + ForwardPathBuffer *string + MailBuffer *string } -func (self Connection) Read() (string, error) { - buffer := [64]byte{} - read, err := self.connection.Read(buffer[:]) - if err != nil { - return "", err +func NewConnection(connection net.Conn) Connection { + return Connection{ + connection, + nil, + nil, + nil, + } +} + +var buffer = [1024]byte{} +func (self Connection) Read(delimiter string) (string, error) { + var message string + for !strings.Contains(message, delimiter) { + num_read, err := self.connection.Read(buffer[:]) + if err != nil { + return "", err + } + message += string(buffer[:num_read]) } - message := string(buffer[:read - 2]) + lines := strings.Split(message, "\n") + for index, line := range lines { + if index != len(lines) - 1 || line != "" { + if index == 0 { + log.Trace().Msgf("%v -> %v", self.RemoteAddr(), line) + } else { + log.Trace().Msgf("%v %v", self.RemoteAddr(), line) + } + } + } - log.Trace().Msgf("%v -> %v", self.RemoteAddr(), message) - - return message, nil + return message[:len(message) - len(delimiter)], nil } func (self Connection) Write(message string) error { - log.Trace().Msgf("%v <- %v", self.RemoteAddr(), message[:len(message) - 1]) + lines := strings.Split(message, "\n") + for index, line := range lines { + if index != len(lines) - 1 || line != "" { + if index == 0 { + log.Trace().Msgf("%v <- %v", self.RemoteAddr(), line) + } else { + log.Trace().Msgf("%v %v", self.RemoteAddr(), line) + } + } + } _, err := self.connection.Write([]byte(message)) return err } diff --git a/smtp/handlers.go b/smtp/handlers.go index e5ff087..990910d 100644 --- a/smtp/handlers.go +++ b/smtp/handlers.go @@ -25,7 +25,7 @@ import ( type Command struct { name string - Exec func(Connection)([]Command, error) + Exec func(Connection, string)([]Command, error) } func (self Command) Name() string { @@ -42,7 +42,7 @@ func (self Connection) Chain() error { return err } for { - message, err := self.Read() + message, err := self.Read("\r\n") if err != nil { return err } @@ -50,7 +50,7 @@ func (self Connection) Chain() error { command_found := false for _, command := range commands { if command.Check(message) { - commands, err = command.Exec(self) + commands, err = command.Exec(self, message[len(command.Name()):]) if err != nil { return err } @@ -80,24 +80,105 @@ func Greet(connection Connection) ([]Command, error) { "220 localhost ESMTP diodemail -- Service ready" + "\n", ) if err != nil { - return nil, err + return []Command{}, err } return []Command{ - Command{ "HELO", Hello }, - Command{ "EHLO", Hello }, + HELO, + EHLO, }, nil } /* --- HELO/EHLO RESPONSE --- */ -func Hello(connection Connection) ([]Command, error) { +var HELO = Command{ "HELO", Hello } +var EHLO = Command{ "EHLO", Hello } +func Hello(connection Connection, message string) ([]Command, error) { err := connection.Write( - fmt.Sprintf("250 %v is shy", connection.connection.LocalAddr()) + "\n", + fmt.Sprintf( + "250 %v is shy" + "\r\n", + connection.connection.LocalAddr(), + ), + ) + if err != nil { + return []Command{}, err + } + + return []Command{ + MAIL_FROM, + }, nil +} + +/* --- MAIL FROM RESPONSE --- */ + +var MAIL_FROM = Command{ "MAIL FROM:", MailFrom } +func MailFrom(connection Connection, message string) ([]Command, error) { + match := ReversePath.FindStringSubmatch(message) + if match == nil { + return []Command{}, fmt.Errorf("Invalid forward-path: %v", message) + } else if len(match) > 1 { + connection.ReversePathBuffer = &match[1] + } + err := connection.Write( + "250 OK\n", + ) + if err != nil { + return []Command{}, err + } + + return []Command{ + RCPT_TO, + }, nil +} + +var RCPT_TO = Command{ "RCPT TO:", RcptTo } +func RcptTo(connection Connection, message string) ([]Command, error) { + match := ForwardPath.FindStringSubmatch(message) + if match == nil { + return []Command{}, fmt.Errorf("Invalid forward-path: %v", message) + } else { + connection.ForwardPathBuffer = &match[1] + } + err := connection.Write( + "250 OK\n", + ) + if err != nil { + return []Command{}, err + } + + return []Command{ + DATA, + }, nil +} + +var DATA = Command { "DATA", Data } +func Data(connection Connection, message string) ([]Command, error) { + err := connection.Write( + "354 Start Input\n", + ) + if err != nil { + return []Command{}, err + } + data, err := connection.Read("\r\n.\r\n") + connection.MailBuffer = &data + err = connection.Write( + "250 OK\n", ) if err != nil { return nil, err } + return []Command{ + QUIT, + }, nil +} +var QUIT = Command { "QUIT", Quit } +func Quit(connection Connection, message string) ([]Command, error) { + err := connection.Write( + "221 OK\n", + ) + if err != nil { + return nil, err + } return []Command{}, nil } diff --git a/smtp/parsers.go b/smtp/parsers.go new file mode 100644 index 0000000..efe7279 --- /dev/null +++ b/smtp/parsers.go @@ -0,0 +1,33 @@ +/* diodemail - send-only smtp server + * Copyright (c) 2024 Gnarwhal + * + * This file is part of SSHare. + * + * SSHare is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * SSHare is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * SSHare. If not, see . + */ + +package smtp + +import ( + "fmt" + "regexp" +) + +var ReversePath = regexp.MustCompile(fmt.Sprintf("(?:%v)|<>", path)) +var ForwardPath = regexp.MustCompile(path) + +// https://datatracker.ietf.org/doc/html/rfc5321#page-41 +// Is this...legal, m'lord? (no, but ¯\_(ツ)_/¯) +// ... +// Ok fine. TODO. Happy now? +var path = "<(?:.*:)?(\\w+@\\w+(?:\\.\\w+)*)>" diff --git a/smtp/server.go b/smtp/server.go index 21b9fa1..5ace672 100644 --- a/smtp/server.go +++ b/smtp/server.go @@ -60,7 +60,7 @@ func Run(host string, implicit_tls bool) error { return err } - go handle(Connection{connection}) + go handle(NewConnection(connection)) } }