Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion notify/email/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ func (n *Email) Notify(ctx context.Context, as ...*alert.Alert) notify.NotifyVer
tlsConfig.ServerName = n.conf.Smarthost.Host
}

conn, err = tls.Dial("tcp", n.conf.Smarthost.String(), tlsConfig)
d := tls.Dialer{Config: tlsConfig}
conn, err = d.DialContext(ctx, "tcp", n.conf.Smarthost.String())
if err != nil {
return notify.Retry(0, fmt.Errorf("establish TLS connection to server: %w", err), notify.DefaultReason)
}
Expand Down
62 changes: 62 additions & 0 deletions notify/email/email_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,68 @@ func TestEmailNotifyWithDoneContext(t *testing.T) {
require.Contains(t, err.Error(), "establish connection to server")
}

// TestEmailNotifyImplicitTLSWithDoneContext checks that the implicit TLS dial
// honours the context when the server accepts the connection but never answers
// the TLS handshake.
func TestEmailNotifyImplicitTLSWithDoneContext(t *testing.T) {
t.Parallel()

l, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)

var conns []net.Conn
accepted := make(chan struct{})
go func() {
defer close(accepted)
for {
conn, err := l.Accept()
if err != nil {
return
}
// Keep the connection open without ever writing to it.
conns = append(conns, conn)
}
}()
t.Cleanup(func() {
l.Close()
<-accepted
for _, c := range conns {
c.Close()
}
})

host, port, err := net.SplitHostPort(l.Addr().String())
require.NoError(t, err)

forceImplicitTLS := true
cfg := &EmailConfig{
Smarthost: amcommoncfg.HostPort{Host: host, Port: port},
To: emailTo,
From: emailFrom,
HTML: "HTML body",
Text: "Text body",
ForceImplicitTLS: &forceImplicitTLS,
TLSConfig: &commoncfg.TLSConfig{},
}
tmpl, firingAlert, err := prepare(cfg)
require.NoError(t, err)

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

done := make(chan notify.NotifyVerdict, 1)
go func() {
done <- New(cfg, tmpl, promslog.NewNopLogger()).Notify(ctx, firingAlert)
}()

select {
case verdict := <-done:
require.ErrorContains(t, verdict.Err(), "establish TLS connection to server")
case <-time.After(5 * time.Second):
t.Fatal("Notify did not return after the context was done")
}
}

// TestEmailNotifyWithoutAuthentication sends an email to an instance of
// MailDev configured with no authentication then it checks that the server has
// successfully processed the email.
Expand Down