diff options
Diffstat (limited to 'utils/smtp')
| -rw-r--r-- | utils/smtp/messages.go | 32 | ||||
| -rw-r--r-- | utils/smtp/server.go | 41 | ||||
| -rw-r--r-- | utils/smtp/session.go | 108 |
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 +} |
