aboutsummaryrefslogtreecommitdiff
path: root/utils/smtp
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-03-07 17:59:31 +0530
committerBobby <[email protected]>2026-03-07 17:59:31 +0530
commita6ec9a807df68978bf3b85314a4c54c60ecc2b7a (patch)
tree6201e8d594a3c368cce50ebc402f248814e2025f /utils/smtp
parent57df54999e778887e66775481dab46191b46d0b6 (diff)
downloaddove-a6ec9a807df68978bf3b85314a4c54c60ecc2b7a.tar.xz
dove-a6ec9a807df68978bf3b85314a4c54c60ecc2b7a.zip
feat: implement SMTP server with authentication, port validation, and email storage
Diffstat (limited to 'utils/smtp')
-rw-r--r--utils/smtp/constants.go5
-rw-r--r--utils/smtp/server.go56
-rw-r--r--utils/smtp/session.go61
-rw-r--r--utils/smtp/storage.go22
-rw-r--r--utils/smtp/types.go13
5 files changed, 157 insertions, 0 deletions
diff --git a/utils/smtp/constants.go b/utils/smtp/constants.go
new file mode 100644
index 0000000..cc6d173
--- /dev/null
+++ b/utils/smtp/constants.go
@@ -0,0 +1,5 @@
+package smtp
+
+const (
+ LOG_PREFIX = "SMTP"
+)
diff --git a/utils/smtp/server.go b/utils/smtp/server.go
new file mode 100644
index 0000000..0092c16
--- /dev/null
+++ b/utils/smtp/server.go
@@ -0,0 +1,56 @@
+package smtp
+
+import (
+ "dove/config"
+ "dove/messages"
+ "dove/utils/logger"
+ "fmt"
+ "time"
+
+ gosmtp "github.com/emersion/go-smtp"
+)
+
+var activeServers []serverInstance
+
+func Start() {
+ plainAddress := fmt.Sprintf("%s:%d", config.SMTP.Host, config.SMTP.Port)
+ plainServer := createServer(plainAddress)
+
+ activeServers = append(activeServers, serverInstance{server: plainServer, label: LOG_PREFIX})
+
+ go startListener(plainServer, LOG_PREFIX, plainAddress)
+}
+
+func Shutdown() {
+ for _, instance := range activeServers {
+ if shutdownError := instance.server.Close(); shutdownError != nil {
+ logger.Errorf(LOG_PREFIX, messages.SMTPShutdownFailed, instance.label, shutdownError)
+ }
+ }
+
+ logger.Infof(LOG_PREFIX, messages.SMTPShutdownComplete)
+}
+
+func createServer(address string) *gosmtp.Server {
+ smtpServer := gosmtp.NewServer(gosmtp.BackendFunc(func(connection *gosmtp.Conn) (gosmtp.Session, error) {
+ logger.Debugf(LOG_PREFIX, messages.SMTPSessionStarted, connection.Hostname())
+ return &session{}, nil
+ }))
+
+ smtpServer.Addr = address
+ smtpServer.Domain = config.SMTP.Domain
+ smtpServer.ReadTimeout = time.Duration(config.SMTP.ReadTimeout) * time.Second
+ smtpServer.WriteTimeout = time.Duration(config.SMTP.WriteTimeout) * time.Second
+ smtpServer.MaxMessageBytes = int64(config.SMTP.MaxMessageSize)
+ smtpServer.AllowInsecureAuth = true
+
+ return smtpServer
+}
+
+func startListener(smtpServer *gosmtp.Server, label string, address string) {
+ logger.Successf(LOG_PREFIX, messages.SMTPServerStarting, label, address)
+
+ if listenError := smtpServer.ListenAndServe(); listenError != nil {
+ logger.Fatalf(LOG_PREFIX, messages.SMTPListenFailed, label, listenError)
+ }
+}
diff --git a/utils/smtp/session.go b/utils/smtp/session.go
new file mode 100644
index 0000000..029c257
--- /dev/null
+++ b/utils/smtp/session.go
@@ -0,0 +1,61 @@
+package smtp
+
+import (
+ "dove/config"
+ "dove/messages"
+ "dove/utils/logger"
+ "dove/utils/errors"
+ "io"
+
+ gosmtp "github.com/emersion/go-smtp"
+)
+
+func (self *session) AuthPlain(username string, password string) error {
+ if !config.SMTP.AuthRequired {
+ return nil
+ }
+
+ if username != config.SMTP.Username || password != config.SMTP.Password {
+ logger.Warnf(LOG_PREFIX, messages.SMTPAuthFailed, username)
+ return errors.Error(messages.SMTPInvalidCredentials)
+ }
+
+ return nil
+}
+
+func (self *session) Mail(senderAddress string, mailOptions *gosmtp.MailOptions) error {
+ logger.Debugf(LOG_PREFIX, messages.SMTPMailFrom, senderAddress)
+ self.fromAddress = senderAddress
+ return nil
+}
+
+func (self *session) Rcpt(recipientAddress string, recipientOptions *gosmtp.RcptOptions) error {
+ logger.Debugf(LOG_PREFIX, messages.SMTPRecipient, recipientAddress)
+ self.toAddresses = append(self.toAddresses, recipientAddress)
+ return nil
+}
+
+func (self *session) Data(messageReader io.Reader) error {
+ rawMessage, readError := io.ReadAll(messageReader)
+ if readError != nil {
+ return readError
+ }
+
+ logger.Infof(LOG_PREFIX, messages.SMTPMessageReceived, len(rawMessage))
+
+ if storeError := storeMessage(self.fromAddress, self.toAddresses, rawMessage); storeError != nil {
+ logger.Errorf(LOG_PREFIX, messages.SMTPMessageStoreFailed, storeError)
+ return storeError
+ }
+
+ return nil
+}
+
+func (self *session) Reset() {
+ self.fromAddress = ""
+ self.toAddresses = nil
+}
+
+func (self *session) Logout() error {
+ return nil
+}
diff --git a/utils/smtp/storage.go b/utils/smtp/storage.go
new file mode 100644
index 0000000..b580d67
--- /dev/null
+++ b/utils/smtp/storage.go
@@ -0,0 +1,22 @@
+package smtp
+
+import (
+ "dove/config"
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+func storeMessage(fromAddress string, toAddresses []string, rawMessage []byte) error {
+ emailDirectory := filepath.Join(config.DataDir, "emails")
+
+ if directoryError := os.MkdirAll(emailDirectory, 0750); directoryError != nil {
+ return directoryError
+ }
+
+ filename := fmt.Sprintf("%d.eml", time.Now().UnixNano())
+ filePath := filepath.Join(emailDirectory, filename)
+
+ return os.WriteFile(filePath, rawMessage, 0640)
+} \ No newline at end of file
diff --git a/utils/smtp/types.go b/utils/smtp/types.go
new file mode 100644
index 0000000..38c6504
--- /dev/null
+++ b/utils/smtp/types.go
@@ -0,0 +1,13 @@
+package smtp
+
+import gosmtp "github.com/emersion/go-smtp"
+
+type session struct {
+ fromAddress string
+ toAddresses []string
+}
+
+type serverInstance struct {
+ server *gosmtp.Server
+ label string
+}