diff --git a/prober/tcp.go b/prober/tcp.go index c08fbe2b..977099bd 100644 --- a/prober/tcp.go +++ b/prober/tcp.go @@ -71,7 +71,7 @@ var ( send: "EHLO prober", }, queryResponse{ - expect: "^250-STARTTLS", + expect: "^250(-| )STARTTLS", }, queryResponse{ send: "STARTTLS", diff --git a/prober/tcp_test.go b/prober/tcp_test.go index 22c1021e..50774c70 100644 --- a/prober/tcp_test.go +++ b/prober/tcp_test.go @@ -246,6 +246,46 @@ func TestProbeTCPStartTLSSMTP(t *testing.T) { checkTLSVersionMetrics("TLS 1.3", registry, t) } +// TestProbeTCPStartTLSSMTPWithDashInResponse tests STARTTLS against a mock SMTP server +// which provides STARTTLS as option with dash which is okay when it used as the last option +func TestProbeTCPStartTLSSMTPWithDashInResponse(t *testing.T) { + server, certPEM, _, caFile, teardown, err := test.SetupTCPServer() + if err != nil { + t.Fatalf(err.Error()) + } + defer teardown() + + server.StartSMTPWithDashInResponse() + defer server.Close() + + module := config.Module{ + TCP: config.TCPProbe{ + StartTLS: "smtp", + }, + TLSConfig: config.TLSConfig{ + CAFile: caFile, + InsecureSkipVerify: false, + }, + } + + registry := prometheus.NewRegistry() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := ProbeTCP(ctx, newTestLogger(), server.Listener.Addr().String(), module, registry); err != nil { + t.Fatalf("error: %s", err) + } + + cert, err := newCertificate(certPEM) + if err != nil { + t.Fatal(err) + } + checkCertificateMetrics(cert, registry, t) + checkOCSPMetrics([]byte{}, registry, t) + checkTLSVersionMetrics("TLS 1.3", registry, t) +} + // TestProbeTCPStartTLSFTP tests STARTTLS against a mock FTP server func TestProbeTCPStartTLSFTP(t *testing.T) { server, certPEM, _, caFile, teardown, err := test.SetupTCPServer() diff --git a/test/tcp.go b/test/tcp.go index 0137b1d7..f57afe80 100644 --- a/test/tcp.go +++ b/test/tcp.go @@ -105,6 +105,45 @@ func (t *TCPServer) StartSMTP() { }() } +// StartSMTPWithDashInResponse starts a listener that negotiates a TLS connection with an smtp +// client using STARTTLS. The server provides the STARTTLS response in the form '250 STARTTLS' +// (with a space, rather than a dash) +func (t *TCPServer) StartSMTPWithDashInResponse() { + go func() { + conn, err := t.Listener.Accept() + if err != nil { + panic(fmt.Sprintf("Error accepting on socket: %s", err)) + } + defer conn.Close() + + if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { + panic("Error setting deadline") + } + + fmt.Fprintf(conn, "220 ESMTP StartTLS pseudo-server\n") + if _, e := fmt.Fscanf(conn, "EHLO prober\n"); e != nil { + panic("Error in dialog. No EHLO received.") + } + fmt.Fprintf(conn, "250-pseudo-server.example.net\n") + fmt.Fprintf(conn, "250-DSN\n") + fmt.Fprintf(conn, "250 STARTTLS\n") + + if _, e := fmt.Fscanf(conn, "STARTTLS\n"); e != nil { + panic("Error in dialog. No (TLS) STARTTLS received.") + } + fmt.Fprintf(conn, "220 2.0.0 Ready to start TLS\n") + + // Upgrade to TLS. + tlsConn := tls.Server(conn, t.TLS) + if err := tlsConn.Handshake(); err != nil { + level.Error(t.logger).Log("msg", err) + } + defer tlsConn.Close() + + t.stopCh <- struct{}{} + }() +} + // StartFTP starts a listener that negotiates a TLS connection with an ftp // client using AUTH TLS func (t *TCPServer) StartFTP() {