diff options
| author | Bobby <[email protected]> | 2026-03-07 17:59:31 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2026-03-07 17:59:31 +0530 |
| commit | a6ec9a807df68978bf3b85314a4c54c60ecc2b7a (patch) | |
| tree | 6201e8d594a3c368cce50ebc402f248814e2025f /utils | |
| parent | 57df54999e778887e66775481dab46191b46d0b6 (diff) | |
| download | dove-a6ec9a807df68978bf3b85314a4c54c60ecc2b7a.tar.xz dove-a6ec9a807df68978bf3b85314a4c54c60ecc2b7a.zip | |
feat: implement SMTP server with authentication, port validation, and email storage
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/meta/request.go | 40 | ||||
| -rw-r--r-- | utils/meta/types.go | 3 | ||||
| -rw-r--r-- | utils/meta/value.go | 23 | ||||
| -rw-r--r-- | utils/smtp/constants.go | 5 | ||||
| -rw-r--r-- | utils/smtp/server.go | 56 | ||||
| -rw-r--r-- | utils/smtp/session.go | 61 | ||||
| -rw-r--r-- | utils/smtp/storage.go | 22 | ||||
| -rw-r--r-- | utils/smtp/types.go | 13 |
8 files changed, 202 insertions, 21 deletions
diff --git a/utils/meta/request.go b/utils/meta/request.go index 3098970..34a659a 100644 --- a/utils/meta/request.go +++ b/utils/meta/request.go @@ -8,36 +8,56 @@ import ( "github.com/gofiber/fiber/v2" ) -func Request(context *fiber.Ctx) request { +func Request(context *fiber.Ctx) *request { data, ok := context.Locals(REQUEST_KEY).(types.Request) if !ok { logger.Errorf(LOG_PREFIX, messages.MetaRequestContextMissing) - return request{} + return nil } - return request{ + return &request{ Request: data, context: context, } } -func (self request) Param(key string) value { +func (self *request) Param(key string) *value { + if self == nil { + return nil + } + if self.context != nil { result := self.context.Params(key) if result != "" { - return value{data: result, found: true} + return &value{data: result} } } - return value{} + return nil } -func (self request) Query(key string) value { +func (self *request) Query(key string) *value { + if self == nil { + return nil + } + result, found := findParam(self.Request.Query, key) - return value{data: result, found: found} + if found { + return &value{data: result} + } + + return nil } -func (self request) Header(key string) value { +func (self *request) Header(key string) *value { + if self == nil { + return nil + } + result, found := findParam(self.Request.Headers, key) - return value{data: result, found: found} + if found { + return &value{data: result} + } + + return nil } diff --git a/utils/meta/types.go b/utils/meta/types.go index 8aa710f..3bc8a0e 100644 --- a/utils/meta/types.go +++ b/utils/meta/types.go @@ -12,6 +12,5 @@ type request struct { } type value struct { - data string - found bool + data string } diff --git a/utils/meta/value.go b/utils/meta/value.go index fb90500..889ba0e 100644 --- a/utils/meta/value.go +++ b/utils/meta/value.go @@ -5,25 +5,30 @@ import ( "dove/utils/logger" ) -func (self value) String() string { +func (self *value) String() string { + if self == nil { + return "" + } + return self.data } -func (self value) Exists() bool { - return self.found +func (self *value) Exists() bool { + return self != nil } -func (self value) Or(fallback string) string { - if self.found { - return self.data +func (self *value) Or(fallback string) string { + if self == nil { + return fallback } - return fallback + return self.data } -func (self value) Required() string { - if !self.found { +func (self *value) Required() string { + if self == nil { logger.Errorf(LOG_PREFIX, messages.MetaRequiredValueMissing) + return "" } return self.data 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 +} |
