Skip to content

Commit

Permalink
Merge branch 'release/v1.12.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
axllent committed Jan 3, 2024
2 parents 7658fd8 + b8385dc commit fa8b398
Show file tree
Hide file tree
Showing 23 changed files with 177 additions and 83 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ jobs:
strategy:
matrix:
go-version: [1.21.x]
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: false
- uses: actions/checkout@v4
- name: Run Go tests
uses: actions/cache@v3
Expand All @@ -30,15 +31,19 @@ jobs:

# build the assets
- name: Build web UI
if: startsWith(matrix.os, 'ubuntu') == true
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- run: npm install
- run: npm run package
- if: startsWith(matrix.os, 'ubuntu') == true
run: npm install
- if: startsWith(matrix.os, 'ubuntu') == true
run: npm run package

# validate the swagger file
- name: Validate OpenAPI definition
if: startsWith(matrix.os, 'ubuntu') == true
uses: char0n/swagger-editor-validate@v1
with:
definition-file: server/ui/api/v1/swagger.json
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

Notable changes to Mailpit will be documented in this file.

## [v1.12.1]

### Chore
- Significantly increase database performance using WAL (Write-Ahead-Log)
- Standardize error logging & formatting

### Feature
- Add option to only allow SMTP recipients matching a regular expression (disable open-relay behaviour [#219](https://github.com/axllent/mailpit/issues/219))

### Fix
- Log total deleted messages when auto-pruning messages (--max)
- Prevent rare error from websocket connection (unexpected non-whitespace character)
- Log total deleted messages when deleting all messages from search

### Libs
- Update node modules

### Tests
- Run tests on Linux, Windows & Mac

### UI
- Automatically refresh connected browsers if Mailpit is upgraded (version change)


## [v1.12.0]

### Chore
Expand Down
4 changes: 4 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func init() {
rootCmd.Flags().BoolVar(&config.SMTPAuthAllowInsecure, "smtp-auth-allow-insecure", config.SMTPAuthAllowInsecure, "Enable insecure PLAIN & LOGIN authentication")
rootCmd.Flags().BoolVar(&config.SMTPStrictRFCHeaders, "smtp-strict-rfc-headers", config.SMTPStrictRFCHeaders, "Return SMTP error if message headers contain <CR><CR><LF>")
rootCmd.Flags().IntVar(&config.SMTPMaxRecipients, "smtp-max-recipients", config.SMTPMaxRecipients, "Maximum SMTP recipients allowed")
rootCmd.Flags().StringVar(&config.SMTPAllowedRecipients, "smtp-allowed-recipients", config.SMTPAllowedRecipients, "Only allow SMTP recipients matching a regular expression (default allow all)")

rootCmd.Flags().StringVar(&config.SMTPRelayConfigFile, "smtp-relay-config", config.SMTPRelayConfigFile, "SMTP configuration file to allow releasing messages")
rootCmd.Flags().BoolVar(&config.SMTPRelayAllIncoming, "smtp-relay-all", config.SMTPRelayAllIncoming, "Relay all incoming messages via external SMTP server (caution!)")
Expand Down Expand Up @@ -170,6 +171,9 @@ func initConfigFromEnv() {
if len(os.Getenv("MP_SMTP_MAX_RECIPIENTS")) > 0 {
config.SMTPMaxRecipients, _ = strconv.Atoi(os.Getenv("MP_SMTP_MAX_RECIPIENTS"))
}
if len(os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")) > 0 {
config.SMTPAllowedRecipients = os.Getenv("MP_SMTP_ALLOWED_RECIPIENTS")
}

// Relay server config
config.SMTPRelayConfigFile = os.Getenv("MP_SMTP_RELAY_CONFIG")
Expand Down
20 changes: 18 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ var (
// @see https://github.com/axllent/mailpit/issues/87 & https://github.com/axllent/mailpit/issues/153
SMTPStrictRFCHeaders bool

// SMTPAllowedRecipients if set, will only accept recipients matching this regular expression
SMTPAllowedRecipients string

// SMTPAllowedRecipientsRegexp is the compiled version of SMTPAllowedRecipients
SMTPAllowedRecipientsRegexp *regexp.Regexp

// ReleaseEnabled is whether message releases are enabled, requires a valid SMTPRelayConfigFile
ReleaseEnabled = false

Expand Down Expand Up @@ -262,6 +268,16 @@ func VerifyConfig() error {
}
}

if SMTPAllowedRecipients != "" {
restrictRegexp, err := regexp.Compile(SMTPAllowedRecipients)
if err != nil {
return fmt.Errorf("Failed to compile smtp-allowed-recipients regexp: %s", err.Error())
}

SMTPAllowedRecipientsRegexp = restrictRegexp
logger.Log().Infof("[smtp] only allowing recipients matching the following regexp: %s", SMTPAllowedRecipients)
}

if err := parseRelayConfig(SMTPRelayConfigFile); err != nil {
return err
}
Expand Down Expand Up @@ -335,11 +351,11 @@ func parseRelayConfig(c string) error {

if SMTPRelayConfig.RecipientAllowlist != "" {
if err != nil {
return fmt.Errorf("failed to compile recipient allowlist regexp: %e", err)
return fmt.Errorf("Failed to compile relay recipient allowlist regexp: %s", err.Error())
}

SMTPRelayConfig.RecipientAllowlistRegexp = allowlistRegexp
logger.Log().Infof("[smtp] recipient allowlist is active with the following regexp: %s", SMTPRelayConfig.RecipientAllowlist)
logger.Log().Infof("[smtp] relay recipient allowlist is active with the following regexp: %s", SMTPRelayConfig.RecipientAllowlist)

}

Expand Down
6 changes: 2 additions & 4 deletions internal/htmlcheck/css.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ func runCSSTests(html string) ([]Warning, int, error) {

inlined, err := inlineRemoteCSS(html)
if err != nil {
// logger.Log().Warn(err)
inlined = html
}

// merge all CSS inline
merged, err := mergeInlineCSS(inlined)
if err != nil {
// logger.Log().Warn(err)
merged = inlined
}

Expand Down Expand Up @@ -157,7 +155,7 @@ func inlineRemoteCSS(h string) (string, error) {

resp, err := downloadToBytes(a.Val)
if err != nil {
logger.Log().Warningf("html check failed to download %s", a.Val)
logger.Log().Warnf("[html-check] failed to download %s", a.Val)
continue
}

Expand All @@ -179,7 +177,7 @@ func inlineRemoteCSS(h string) (string, error) {

newDoc, err := doc.Html()
if err != nil {
logger.Log().Warning(err)
logger.Log().Warnf("[html-check] failed to download %s", err.Error())
return h, err
}

Expand Down
2 changes: 1 addition & 1 deletion internal/linkcheck/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func doHead(link string, followRedirects bool) (int, error) {

req, err := http.NewRequest("HEAD", link, nil)
if err != nil {
logger.Log().Error(err)
logger.Log().Errorf("[link-check] %s", err.Error())
return 0, err
}

Expand Down
42 changes: 21 additions & 21 deletions internal/stats/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ var (

mu sync.RWMutex

smtpReceived int
smtpReceivedSize int
smtpErrors int
smtpAccepted int
smtpAcceptedSize int
smtpRejected int
smtpIgnored int
)

Expand All @@ -50,15 +50,15 @@ type AppInformation struct {
Uptime int
// Current memory usage in bytes
Memory uint64
// Messages deleted
// Database runtime messages deleted
MessagesDeleted int
// SMTP messages received via since run
SMTPReceived int
// Total size in bytes of received messages since run
SMTPReceivedSize int
// SMTP errors since run
SMTPErrors int
// SMTP messages ignored since run (duplicate IDs)
// Accepted runtime SMTP messages
SMTPAccepted int
// Total runtime accepted messages size in bytes
SMTPAcceptedSize int
// Rejected runtime SMTP messages
SMTPRejected int
// Ignored runtime SMTP messages (when using --ignore-duplicate-ids)
SMTPIgnored int
}
}
Expand All @@ -75,9 +75,9 @@ func Load() AppInformation {

info.RuntimeStats.Uptime = int(time.Since(startedAt).Seconds())
info.RuntimeStats.MessagesDeleted = storage.StatsDeleted
info.RuntimeStats.SMTPReceived = smtpReceived
info.RuntimeStats.SMTPReceivedSize = smtpReceivedSize
info.RuntimeStats.SMTPErrors = smtpErrors
info.RuntimeStats.SMTPAccepted = smtpAccepted
info.RuntimeStats.SMTPAcceptedSize = smtpAcceptedSize
info.RuntimeStats.SMTPRejected = smtpRejected
info.RuntimeStats.SMTPIgnored = smtpIgnored

if latestVersionCache != "" {
Expand Down Expand Up @@ -116,18 +116,18 @@ func Track() {
startedAt = time.Now()
}

// LogSMTPReceived logs a successfully SMTP transaction
func LogSMTPReceived(size int) {
// LogSMTPAccepted logs a successful SMTP transaction
func LogSMTPAccepted(size int) {
mu.Lock()
smtpReceived = smtpReceived + 1
smtpReceivedSize = smtpReceivedSize + size
smtpAccepted = smtpAccepted + 1
smtpAcceptedSize = smtpAcceptedSize + size
mu.Unlock()
}

// LogSMTPError logs a failed SMTP transaction
func LogSMTPError() {
// LogSMTPRejected logs a rejected SMTP transaction
func LogSMTPRejected() {
mu.Lock()
smtpErrors = smtpErrors + 1
smtpRejected = smtpRejected + 1
mu.Unlock()
}

Expand Down
22 changes: 14 additions & 8 deletions internal/storage/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ func InitDB() error {
// @see https://github.com/mattn/go-sqlite3#faq
db.SetMaxOpenConns(1)

// SQLite performance tuning (https://phiresky.github.io/blog/2020/sqlite-performance-tuning/)
_, err = db.Exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = normal;")
if err != nil {
return err
}

// create tables if necessary & apply migrations
if err := dbApplyMigrations(); err != nil {
return err
Expand Down Expand Up @@ -110,7 +116,7 @@ func InitDB() error {
func Close() {
if db != nil {
if err := db.Close(); err != nil {
logger.Log().Warning("[db] error closing database, ignoring")
logger.Log().Warn("[db] error closing database, ignoring")
}
}

Expand All @@ -128,7 +134,7 @@ func Store(body *[]byte) (string, error) {
// Parse message body with enmime
env, err := enmime.ReadEnvelope(bytes.NewReader(*body))
if err != nil {
logger.Log().Warningf("[db] %s", err.Error())
logger.Log().Warnf("[message] %s", err.Error())
return "", nil
}

Expand Down Expand Up @@ -271,12 +277,12 @@ func List(start, limit int) ([]MessageSummary, error) {
em := MessageSummary{}

if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet); err != nil {
logger.Log().Error(err)
logger.Log().Errorf("[db] %s", err.Error())
return
}

if err := json.Unmarshal([]byte(metadata), &em); err != nil {
logger.Log().Error(err)
logger.Log().Errorf("[json] %s", err.Error())
return
}

Expand Down Expand Up @@ -349,15 +355,15 @@ func GetMessage(id string) (*Message, error) {
var created int64

if err := row.Scan(&created); err != nil {
logger.Log().Error(err)
logger.Log().Errorf("[db] %s", err.Error())
return
}

logger.Log().Debugf("[db] %s does not contain a date header, using received datetime", id)

date = time.UnixMilli(created)
}); err != nil {
logger.Log().Error(err)
logger.Log().Errorf("[db] %s", err.Error())
}
}

Expand Down Expand Up @@ -686,11 +692,11 @@ func DeleteAllMessages() error {
logger.Log().Debugf("[db] deleted %d messages in %s", total, elapsed)
}

logMessagesDeleted(total)

dbLastAction = time.Now()
dbDataDeleted = false

logMessagesDeleted(total)

websockets.Broadcast("prune", nil)
BroadcastMailboxStats()

Expand Down
2 changes: 1 addition & 1 deletion internal/storage/migrationTasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func migrateTagsToManyMany() {
tags := []string{}

if err := json.Unmarshal([]byte(jsonTags), &tags); err != nil {
logger.Log().Error(err)
logger.Log().Errorf("[json] %s", err.Error())
return
}

Expand Down
11 changes: 7 additions & 4 deletions internal/storage/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package storage
import (
"time"

"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/server/websockets"
)

Expand All @@ -23,11 +24,13 @@ func BroadcastMailboxStats() {
time.Sleep(250 * time.Millisecond)
bcStatsDelay = false
b := struct {
Total int
Unread int
Total int
Unread int
Version string
}{
Total: CountTotal(),
Unread: CountUnread(),
Total: CountTotal(),
Unread: CountUnread(),
Version: config.Version,
}

websockets.Broadcast("stats", b)
Expand Down
Loading

0 comments on commit fa8b398

Please sign in to comment.