Skip to content

Commit

Permalink
Merge branch 'feature/smtpd-debug' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
axllent committed Aug 17, 2024
2 parents 5fc025b + 4f2324a commit 3cec8bf
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 1 deletion.
2 changes: 2 additions & 0 deletions internal/storage/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ func Store(body *[]byte) (string, error) {

BroadcastMailboxStats()

logger.Log().Debugf("[db] saved message %s (%d bytes)", id, int64(size))

return id, nil
}

Expand Down
6 changes: 6 additions & 0 deletions server/pop3/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/axllent/mailpit/internal/auth"
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/storage"
"github.com/axllent/mailpit/server/websockets"
)

func authUser(username, password string) bool {
Expand All @@ -19,6 +20,11 @@ func authUser(username, password string) bool {
func sendResponse(c net.Conn, m string) {
fmt.Fprintf(c, "%s\r\n", m)
logger.Log().Debugf("[pop3] response: %s", m)

if strings.HasPrefix(m, "-ERR ") {
sub, _ := strings.CutPrefix(m, "-ERR ")
websockets.BroadCastClientError("error", "pop3", c.RemoteAddr().String(), sub)
}
}

// Send a response without debug logging (for data)
Expand Down
30 changes: 29 additions & 1 deletion server/smtpd/smtpd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ import (
"github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/stats"
"github.com/axllent/mailpit/internal/storage"
"github.com/axllent/mailpit/server/websockets"
"github.com/lithammer/shortuuid/v4"
"github.com/mhale/smtpd"
)

var (
// DisableReverseDNS allows rDNS to be disabled
DisableReverseDNS bool

warningResponse = regexp.MustCompile(`^4\d\d `)
errorResponse = regexp.MustCompile(`^5\d\d `)
)

// MailHandler handles the incoming message to store in the database
Expand All @@ -38,7 +42,7 @@ func Store(origin net.Addr, from string, to []string, data []byte) (string, erro

msg, err := mail.ReadMessage(bytes.NewReader(data))
if err != nil {
logger.Log().Errorf("[smtpd] error parsing message: %s", err.Error())
logger.Log().Warnf("[smtpd] error parsing message: %s", err.Error())
stats.LogSMTPRejected()
return "", err
}
Expand Down Expand Up @@ -210,7 +214,17 @@ func Listen() error {
return listenAndServe(config.SMTPListen, mailHandler, authHandler)
}

// Translate the smtpd verb from READ/WRITE
func verbLogTranslator(verb string) string {
if verb == "READ" {
return "received"
}

return "response"
}

func listenAndServe(addr string, handler smtpd.MsgIDHandler, authHandler smtpd.AuthHandler) error {
smtpd.Debug = true // to enable Mailpit logging
srv := &smtpd.Server{
Addr: addr,
MsgIDHandler: handler,
Expand All @@ -221,6 +235,20 @@ func listenAndServe(addr string, handler smtpd.MsgIDHandler, authHandler smtpd.A
AuthRequired: false,
MaxRecipients: config.SMTPMaxRecipients,
DisableReverseDNS: DisableReverseDNS,
LogRead: func(remoteIP, verb, line string) {
logger.Log().Debugf("[smtpd] %s (%s) %s", verbLogTranslator(verb), remoteIP, line)
},
LogWrite: func(remoteIP, verb, line string) {
if warningResponse.MatchString(line) {
logger.Log().Warnf("[smtpd] %s (%s) %s", verbLogTranslator(verb), remoteIP, line)
websockets.BroadCastClientError("warning", "smtpd", remoteIP, line)
} else if errorResponse.MatchString(line) {
logger.Log().Errorf("[smtpd] %s (%s) %s", verbLogTranslator(verb), remoteIP, line)
websockets.BroadCastClientError("error", "smtpd", remoteIP, line)
} else {
logger.Log().Debugf("[smtpd] %s (%s) %s", verbLogTranslator(verb), remoteIP, line)
}
},
}

if config.Label != "" {
Expand Down
37 changes: 37 additions & 0 deletions server/ui-src/components/Notifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default {
socketBreaks: 0, // to track sockets that continually connect & disconnect, reset every 15s
pauseNotifications: false, // prevent spamming
version: false,
clientErrors: [], // errors received via websocket
}
},
Expand All @@ -39,6 +40,8 @@ export default {
mailbox.notificationsSupported = window.isSecureContext
&& ("Notification" in window && Notification.permission !== "denied")
mailbox.notificationsEnabled = mailbox.notificationsSupported && Notification.permission == "granted"
this.errorNotificationCron()
},
methods: {
Expand Down Expand Up @@ -99,6 +102,9 @@ export default {
} else if (response.Type == "truncate") {
// broadcast for components
this.eventBus.emit("truncate")
} else if (response.Type == "error") {
// broadcast for components
this.addClientError(response.Data)
}
}
Expand Down Expand Up @@ -195,12 +201,43 @@ export default {
Toast.getOrCreateInstance(el).hide()
}
},
addClientError(d) {
d.expire = Date.now() + 5000 // expire after 5s
this.clientErrors.push(d)
},
errorNotificationCron() {
window.setTimeout(() => {
this.clientErrors.forEach((err, idx) => {
if (err.expire < Date.now()) {
this.clientErrors.splice(idx, 1)
}
})
this.errorNotificationCron()
}, 1000)
}
},
}
</script>

<template>
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div v-for="error in clientErrors" class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<svg class="bd-placeholder-img rounded me-2" width="20" height="20" xmlns="http://www.w3.org/2000/svg"
aria-hidden="true" preserveAspectRatio="xMidYMid slice" focusable="false">
<rect width="100%" height="100%" :fill="error.Level == 'warning' ? '#ffc107' : '#dc3545'"></rect>
</svg>
<strong class="me-auto">{{ error.Type }}</strong>
<small class="text-body-secondary">{{ error.IP }}</small>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
{{ error.Message }}
</div>
</div>

<div id="messageToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header" v-if="toastMessage">
<i class="bi bi-envelope-exclamation-fill me-2"></i>
Expand Down
17 changes: 17 additions & 0 deletions server/websockets/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,20 @@ func Broadcast(t string, msg interface{}) {

go func() { MessageHub.Broadcast <- b }()
}

// BroadCastClientError is a wrapper to broadcast client errors to the web UI
func BroadCastClientError(severity, errorType, ip, message string) {
msg := struct {
Level string
Type string
IP string
Message string
}{
severity,
errorType,
ip,
message,
}

Broadcast("error", msg)
}

0 comments on commit 3cec8bf

Please sign in to comment.