aboutsummaryrefslogtreecommitdiff
path: root/utils/smtp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/smtp')
-rw-r--r--utils/smtp/messages.go32
-rw-r--r--utils/smtp/server.go41
-rw-r--r--utils/smtp/session.go108
3 files changed, 166 insertions, 15 deletions
diff --git a/utils/smtp/messages.go b/utils/smtp/messages.go
index 90568e0..6441fde 100644
--- a/utils/smtp/messages.go
+++ b/utils/smtp/messages.go
@@ -1,15 +1,25 @@
package smtp
const (
- AuthFailed = "Authentication failed for user: %s"
- InvalidCredentials = "Invalid credentials."
- ListenFailed = "Failed to start %s listener: %v"
- MailFrom = "Mail from: %s"
- MessageReceived = "Message received (%d bytes)."
- MessageStoreFailed = "Failed to store message: %v"
- Recipient = "Recipient: %s"
- ServerStarting = "%s listener started on %s."
- SessionStarted = "New session from %s."
- ShutdownComplete = "All listeners stopped."
- ShutdownFailed = "Failed to shutdown %s listener: %v"
+ AuthFailed = "Authentication failed for user: %s"
+ DeliveryFailed = "Failed to deliver to %s: %v"
+ DeliverySuccess = "Delivered to local mailbox: %s"
+ InvalidCredentials = "Invalid credentials."
+ ListenFailed = "Failed to start %s listener: %v"
+ MailFrom = "Mail from: %s"
+ MessageParseFailed = "Failed to parse incoming message: %v"
+ MessageReceived = "Message received (%d bytes) from %s to %v."
+ MessageStoreFailed = "Failed to store message: %v"
+ NoLocalRecipients = "No local recipients found for message from %s."
+ Recipient = "Recipient: %s"
+ RecipientNotLocal = "Recipient %s is not a local mailbox."
+ RelayDeliveryFailed = "Failed to relay message to %s: %v"
+ RelayDeliverySuccess = "Relayed message to %s via %s:%d."
+ RelayDisabled = "Relay disabled. External recipient %s will not receive the message."
+ ServerStarting = "%s listener started on %s."
+ SessionStarted = "New session from %s."
+ ShutdownComplete = "All listeners stopped."
+ ShutdownFailed = "Failed to shutdown %s listener: %v"
+ TLSCertLoadFailed = "Failed to load TLS certificate: %v"
+ UnknownRecipientDomain = "Recipient domain %s is not managed by this server."
)
diff --git a/utils/smtp/server.go b/utils/smtp/server.go
index 2773647..5260d20 100644
--- a/utils/smtp/server.go
+++ b/utils/smtp/server.go
@@ -1,6 +1,7 @@
package smtp
import (
+ "crypto/tls"
"dove/config"
"dove/utils/logger"
"fmt"
@@ -19,10 +20,26 @@ 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: "SMTP"})
+ go startListener(plainServer, "SMTP", plainAddress)
- activeServers = append(activeServers, ServerInstance{Server: plainServer, Label: LogPrefix})
+ if config.SMTP.TLSEnabled {
+ tlsConfig := loadTLSConfig()
+ if tlsConfig != nil {
+ smtpsAddress := fmt.Sprintf("%s:%d", config.SMTP.Host, config.SMTP.SMTPSPort)
+ smtpsServer := createServer(smtpsAddress)
+ smtpsServer.TLSConfig = tlsConfig
+ activeServers = append(activeServers, ServerInstance{Server: smtpsServer, Label: "SMTPS"})
+ go startTLSListener(smtpsServer, "SMTPS", smtpsAddress)
- go startListener(plainServer, LogPrefix, plainAddress)
+ starttlsAddress := fmt.Sprintf("%s:%d", config.SMTP.Host, config.SMTP.StartTLSPort)
+ starttlsServer := createServer(starttlsAddress)
+ starttlsServer.TLSConfig = tlsConfig
+ starttlsServer.EnableSMTPUTF8 = true
+ activeServers = append(activeServers, ServerInstance{Server: starttlsServer, Label: "STARTTLS"})
+ go startListener(starttlsServer, "STARTTLS", starttlsAddress)
+ }
+ }
}
func Shutdown() {
@@ -51,6 +68,18 @@ func createServer(address string) *gosmtp.Server {
return smtpServer
}
+func loadTLSConfig() *tls.Config {
+ certificate, loadError := tls.LoadX509KeyPair(config.SMTP.TLSCertPath, config.SMTP.TLSKeyPath)
+ if loadError != nil {
+ logger.Errorf(LogPrefix, TLSCertLoadFailed, loadError)
+ return nil
+ }
+
+ return &tls.Config{
+ Certificates: []tls.Certificate{certificate},
+ }
+}
+
func startListener(smtpServer *gosmtp.Server, label string, address string) {
logger.Successf(LogPrefix, ServerStarting, label, address)
@@ -58,3 +87,11 @@ func startListener(smtpServer *gosmtp.Server, label string, address string) {
logger.Fatalf(LogPrefix, ListenFailed, label, listenError)
}
}
+
+func startTLSListener(smtpServer *gosmtp.Server, label string, address string) {
+ logger.Successf(LogPrefix, ServerStarting, label, address)
+
+ if listenError := smtpServer.ListenAndServeTLS(); listenError != nil {
+ logger.Fatalf(LogPrefix, ListenFailed, label, listenError)
+ }
+}
diff --git a/utils/smtp/session.go b/utils/smtp/session.go
index 73fc4e1..ef5ddc7 100644
--- a/utils/smtp/session.go
+++ b/utils/smtp/session.go
@@ -2,9 +2,16 @@ package smtp
import (
"dove/config"
+ mailModel "dove/models/mail"
+ mailRepo "dove/repositories/mail"
+ "dove/utils/email"
"dove/utils/errors"
"dove/utils/logger"
+ "dove/utils/storage"
+ "fmt"
"io"
+ "strings"
+ "time"
gosmtp "github.com/emersion/go-smtp"
)
@@ -45,9 +52,41 @@ func (self *Session) Data(messageReader io.Reader) error {
return readError
}
- logger.Infof(LogPrefix, MessageReceived, len(rawMessage))
+ logger.Infof(LogPrefix, MessageReceived, len(rawMessage), self.fromAddress, self.toAddresses)
- _ = rawMessage
+ parsedEmail, parseError := email.Parse(rawMessage)
+ if parseError != nil {
+ logger.Errorf(LogPrefix, MessageParseFailed, parseError)
+ return parseError
+ }
+
+ deliveredToLocal := false
+
+ for _, recipientAddress := range self.toAddresses {
+ recipientMailbox := mailRepo.FindMailboxByAddress(recipientAddress)
+ if recipientMailbox == nil {
+ aliasMailbox := mailRepo.FindMailboxByAlias(recipientAddress)
+ if aliasMailbox != nil {
+ recipientMailbox = aliasMailbox
+ }
+ }
+
+ if recipientMailbox != nil {
+ deliverError := deliverToLocalMailbox(recipientMailbox, parsedEmail, rawMessage)
+ if deliverError != nil {
+ logger.Errorf(LogPrefix, DeliveryFailed, recipientAddress, deliverError)
+ continue
+ }
+ logger.Infof(LogPrefix, DeliverySuccess, recipientAddress)
+ deliveredToLocal = true
+ } else {
+ logger.Debugf(LogPrefix, RecipientNotLocal, recipientAddress)
+ }
+ }
+
+ if !deliveredToLocal {
+ logger.Warnf(LogPrefix, NoLocalRecipients, self.fromAddress)
+ }
return nil
}
@@ -60,3 +99,68 @@ func (self *Session) Reset() {
func (self *Session) Logout() error {
return nil
}
+
+func deliverToLocalMailbox(mailbox *mailModel.Mailbox, parsedEmail *email.ParsedEmail, rawMessage []byte) error {
+ inboxFolder := mailRepo.FindFolderBySlug(mailbox.ID, "inbox")
+ if inboxFolder == nil {
+ return errors.Error("Inbox folder not found for mailbox %s.", mailbox.Address)
+ }
+
+ filename := fmt.Sprintf("%d", time.Now().UnixNano())
+
+ toAddressesJoined := strings.Join(parsedEmail.ToAddresses, ", ")
+ ccAddressesJoined := strings.Join(parsedEmail.CcAddresses, ", ")
+ bccAddressesJoined := strings.Join(parsedEmail.BccAddresses, ", ")
+
+ attachmentCount := 0
+ inlineCount := 0
+ for _, attachment := range parsedEmail.Attachments {
+ if attachment.IsInline {
+ inlineCount++
+ } else {
+ attachmentCount++
+ }
+ }
+
+ emailRecord := &mailModel.Email{
+ MailboxID: mailbox.ID,
+ FolderID: inboxFolder.ID,
+ MessageID: parsedEmail.MessageID,
+ Filename: filename,
+ FromAddress: parsedEmail.FromAddress,
+ FromName: parsedEmail.FromName,
+ ToAddresses: toAddressesJoined,
+ CcAddresses: ccAddressesJoined,
+ BccAddresses: bccAddressesJoined,
+ ReplyToAddress: parsedEmail.ReplyToAddress,
+ ReturnPath: parsedEmail.ReturnPath,
+ Subject: parsedEmail.Subject,
+ Snippet: parsedEmail.Snippet,
+ Size: parsedEmail.Size,
+ IsRead: false,
+ AttachmentCount: attachmentCount,
+ InlineCount: inlineCount,
+ }
+
+ if createError := mailRepo.CreateEmail(emailRecord); createError != nil {
+ return createError
+ }
+
+ for _, parsedAttachment := range parsedEmail.Attachments {
+ attachmentRecord := &mailModel.Attachment{
+ EmailID: emailRecord.ID,
+ Filename: parsedAttachment.Filename,
+ ContentType: parsedAttachment.ContentType,
+ ContentID: parsedAttachment.ContentID,
+ Size: parsedAttachment.Size,
+ IsInline: parsedAttachment.IsInline,
+ }
+ mailRepo.CreateAttachment(attachmentRecord)
+ }
+
+ if writeError := storage.WriteMailFile(mailbox.Address, "inbox", filename, rawMessage); writeError != nil {
+ logger.Warnf(LogPrefix, MessageStoreFailed, writeError)
+ }
+
+ return nil
+}