Skip to content

Commit

Permalink
Add support for direct SSL/TLS (non-STARTTLS) SMTP connections.
Browse files Browse the repository at this point in the history
- Add support for TLS in `smtppool` (v0.4.0) and upgrade the lib.
- Change `tls_enabled: bool` in the settings table to string
  `tls_type: STARTTLS|TLS|none` and on the settings UI.
- Add DB migrations and schema changes to apply the field change.

Closes #504.
  • Loading branch information
knadh committed Jan 3, 2022
1 parent b48a15c commit dd061f5
Show file tree
Hide file tree
Showing 22 changed files with 52 additions and 11 deletions.
2 changes: 1 addition & 1 deletion cmd/settings.go
Expand Up @@ -68,7 +68,7 @@ type settings struct {
MaxMsgRetries int `json:"max_msg_retries"`
IdleTimeout string `json:"idle_timeout"`
WaitTimeout string `json:"wait_timeout"`
TLSEnabled bool `json:"tls_enabled"`
TLSType string `json:"tls_type"`
TLSSkipVerify bool `json:"tls_skip_verify"`
} `json:"smtp"`

Expand Down
12 changes: 8 additions & 4 deletions frontend/src/views/settings/smtp.vue
Expand Up @@ -81,13 +81,17 @@
<div class="column">
<b-field grouped>
<b-field :label="$t('settings.mailserver.tls')" expanded
:message="$t('settings.mailserver.tlsHelp')">
<b-switch v-model="item.tls_enabled" name="item.tls_enabled" />
:message="$t('settings.mailserver.tlsHelp')" label-position="on-border">
<b-select v-model="item.tls_type" name="items.tls_type">
<option value="none">{{ $t('globals.states.off') }}</option>
<option value="STARTTLS">STARTTLS</option>
<option value="TLS">SSL/TLS</option>
</b-select>
</b-field>
<b-field :label="$t('settings.mailserver.skipTLS')" expanded
:message="$t('settings.mailserver.skipTLSHelp')">
<b-switch v-model="item.tls_skip_verify"
:disabled="!item.tls_enabled" name="item.tls_skip_verify" />
:disabled="item.tls_type === 'none'" name="item.tls_skip_verify" />
</b-field>
</b-field>
</div>
Expand Down Expand Up @@ -188,7 +192,7 @@ export default Vue.extend({
max_msg_retries: 2,
idle_timeout: '15s',
wait_timeout: '5s',
tls_enabled: true,
tls_type: 'STARTTLS',
tls_skip_verify: false,
});
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -15,7 +15,7 @@ require (
github.com/knadh/go-pop3 v0.3.0
github.com/knadh/goyesql/v2 v2.1.2
github.com/knadh/koanf v1.2.3
github.com/knadh/smtppool v0.3.1
github.com/knadh/smtppool v0.4.0
github.com/knadh/stuffbin v1.1.0
github.com/labstack/echo/v4 v4.6.1
github.com/labstack/gommon v0.3.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -84,6 +84,8 @@ github.com/knadh/koanf v1.2.3 h1:2Rkr0YhhYk+4QEOm800Q3Pu0Wi87svTxM6uuEb4WhYw=
github.com/knadh/koanf v1.2.3/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY=
github.com/knadh/smtppool v0.3.1 h1:teF/Lp/8wInTq2gTAOnmxPIcX9yPY6o4n57qf0qdJfM=
github.com/knadh/smtppool v0.3.1/go.mod h1:3DJHouXAgPDBz0kC50HukOsdapYSwIEfJGwuip46oCA=
github.com/knadh/smtppool v0.4.0 h1:335iXPwZ6katJVhauV4O6e8uPvvPmO6YLrfDQhb6UvE=
github.com/knadh/smtppool v0.4.0/go.mod h1:3DJHouXAgPDBz0kC50HukOsdapYSwIEfJGwuip46oCA=
github.com/knadh/stuffbin v1.1.0 h1:f5S5BHzZALjuJEgTIOMC9NidEnBJM7Ze6Lu1GHR/lwU=
github.com/knadh/stuffbin v1.1.0/go.mod h1:yVCFaWaKPubSNibBsTAJ939q2ABHudJQxRWZWV5yh+4=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand Down
1 change: 1 addition & 0 deletions i18n/cs-cz.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Črc",
"globals.months.8": "Srp",
"globals.months.9": "Zář",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Nedoručitelnost | Případy nedoručitelnosti",
"globals.terms.bounces": "Případy nedoručitelnosti",
Expand Down
1 change: 1 addition & 0 deletions i18n/de.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Jul",
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
3 changes: 2 additions & 1 deletion i18n/en.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Jul",
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down Expand Up @@ -360,7 +361,7 @@
"settings.mailserver.skipTLS": "Skip TLS verification",
"settings.mailserver.skipTLSHelp": "Skip hostname check on the TLS certificate.",
"settings.mailserver.tls": "TLS",
"settings.mailserver.tlsHelp": "Enable STARTTLS.",
"settings.mailserver.tlsHelp": "TLS/SSL encryption. STARTTLS is commonly used.",
"settings.mailserver.username": "Username",
"settings.mailserver.waitTimeout": "Wait timeout",
"settings.mailserver.waitTimeoutHelp": "Time to wait for new activity on a connection before closing it and removing it from the pool (s for second, m for minute).",
Expand Down
1 change: 1 addition & 0 deletions i18n/es.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Julio",
"globals.months.8": "Agosto",
"globals.months.9": "Setiembre",
"globals.states.off": "Off",
"globals.terms.analytics": "Analitica",
"globals.terms.bounce": "Rebote | Rebotes",
"globals.terms.bounces": "Rebotes",
Expand Down
1 change: 1 addition & 0 deletions i18n/fr.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "juil.",
"globals.months.8": "août",
"globals.months.9": "sept.",
"globals.states.off": "Off",
"globals.terms.analytics": "Analyses",
"globals.terms.bounce": "Rebond | Rebonds",
"globals.terms.bounces": "Rebonds",
Expand Down
1 change: 1 addition & 0 deletions i18n/hu.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Jul",
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.analytics": "Analitika",
"globals.terms.bounce": "Visszapattanó | Visszapattanók",
"globals.terms.bounces": "Visszapattanók",
Expand Down
1 change: 1 addition & 0 deletions i18n/it.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Lug",
"globals.months.8": "Ago",
"globals.months.9": "Set",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
1 change: 1 addition & 0 deletions i18n/ml.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "ജൂലൈ",
"globals.months.8": "ഓഗസ്റ്റ്",
"globals.months.9": "സെപ്റ്റംബർ",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
1 change: 1 addition & 0 deletions i18n/nl.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Jul",
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
1 change: 1 addition & 0 deletions i18n/pl.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Lip",
"globals.months.8": "Sie",
"globals.months.9": "Wrz",
"globals.states.off": "Off",
"globals.terms.analytics": "Analityka",
"globals.terms.bounce": "Odbicie | Obicia",
"globals.terms.bounces": "Odbicia",
Expand Down
1 change: 1 addition & 0 deletions i18n/pt-BR.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Jul",
"globals.months.8": "Ago",
"globals.months.9": "Set",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
1 change: 1 addition & 0 deletions i18n/pt.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Jul",
"globals.months.8": "Ago",
"globals.months.9": "Set",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
1 change: 1 addition & 0 deletions i18n/ro.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Iul",
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.analytics": "Analitice",
"globals.terms.bounce": "Respins | Respinse",
"globals.terms.bounces": "Respinse",
Expand Down
1 change: 1 addition & 0 deletions i18n/ru.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Июл",
"globals.months.8": "Авг",
"globals.months.9": "Сен",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
1 change: 1 addition & 0 deletions i18n/tr.json
Expand Up @@ -172,6 +172,7 @@
"globals.months.7": "Tem",
"globals.months.8": "Aug",
"globals.months.9": "Eyl",
"globals.states.off": "Off",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
Expand Down
9 changes: 7 additions & 2 deletions internal/messenger/email/email.go
Expand Up @@ -18,7 +18,7 @@ type Server struct {
Username string `json:"username"`
Password string `json:"password"`
AuthProtocol string `json:"auth_protocol"`
TLSEnabled bool `json:"tls_enabled"`
TLSType string `json:"tls_type"`
TLSSkipVerify bool `json:"tls_skip_verify"`
EmailHeaders map[string]string `json:"email_headers"`

Expand Down Expand Up @@ -57,13 +57,18 @@ func New(servers ...Server) (*Emailer, error) {
s.Opt.Auth = auth

// TLS config.
if s.TLSEnabled {
if s.TLSType != "none" {
s.TLSConfig = &tls.Config{}
if s.TLSSkipVerify {
s.TLSConfig.InsecureSkipVerify = s.TLSSkipVerify
} else {
s.TLSConfig.ServerName = s.Host
}

// SSL/TLS, not STARTTLS.
if s.TLSType == "TLS" {
s.Opt.SSL = true
}
}

pool, err := smtppool.New(s.Opt)
Expand Down
15 changes: 15 additions & 0 deletions internal/migrations/v2.1.0.go
Expand Up @@ -8,6 +8,7 @@ import (

// V2_1_0 performs the DB migrations for v.2.1.0.
func V2_1_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
// Insert into appearance related settings.
if _, err := db.Exec(`
INSERT INTO settings (key, value) VALUES
('appearance.admin.custom_css', '""'),
Expand All @@ -19,5 +20,19 @@ func V2_1_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
return err
}

// Replace all `tls_enabled: true/false` keys in the `smtp` settings JSON array
// with the new field `tls_type: STARTTLS|TLS|none`.
// The `tls_enabled` key is removed.
if _, err := db.Exec(`
UPDATE settings SET value = s.updated
FROM (
SELECT JSONB_AGG(
JSONB_SET(v - 'tls_enabled', '{tls_type}', (CASE WHEN v->>'tls_enabled' = 'true' THEN '"STARTTLS"' ELSE '"none"' END)::JSONB)
) AS updated FROM settings, JSONB_ARRAY_ELEMENTS(value) v WHERE key = 'smtp'
) s WHERE key = 'smtp';
`); err != nil {
return err
}

return nil
}
4 changes: 2 additions & 2 deletions schema.sql
Expand Up @@ -207,8 +207,8 @@ INSERT INTO settings (key, value) VALUES
('upload.s3.bucket_type', '"public"'),
('upload.s3.expiry', '"14d"'),
('smtp',
'[{"enabled":true, "host":"smtp.yoursite.com","port":25,"auth_protocol":"cram","username":"username","password":"password","hello_hostname":"","max_conns":10,"idle_timeout":"15s","wait_timeout":"5s","max_msg_retries":2,"tls_enabled":true,"tls_skip_verify":false,"email_headers":[]},
{"enabled":false, "host":"smtp2.yoursite.com","port":587,"auth_protocol":"plain","username":"username","password":"password","hello_hostname":"","max_conns":10,"idle_timeout":"15s","wait_timeout":"5s","max_msg_retries":2,"tls_enabled":false,"tls_skip_verify":false,"email_headers":[]}]'),
'[{"enabled":true, "host":"smtp.yoursite.com","port":25,"auth_protocol":"cram","username":"username","password":"password","hello_hostname":"","max_conns":10,"idle_timeout":"15s","wait_timeout":"5s","max_msg_retries":2,"tls_type":"STARTTLS","tls_skip_verify":false,"email_headers":[]},
{"enabled":false, "host":"smtp.gmail.com","port":465,"auth_protocol":"login","username":"username@gmail.com","password":"password","hello_hostname":"","max_conns":10,"idle_timeout":"15s","wait_timeout":"5s","max_msg_retries":2,"tls_type":"TLS","tls_skip_verify":false,"email_headers":[]}]'),
('messengers', '[]'),
('bounce.enabled', 'false'),
('bounce.webhooks_enabled', 'false'),
Expand Down

0 comments on commit dd061f5

Please sign in to comment.