// This file is part of the happyDomain (R) project. // Copyright (c) 2020-2026 happyDomain // Authors: Pierre-Olivier Mercier, et al. // // This program is offered under a commercial and under the AGPL license. // For commercial licensing, contact us at . // // For AGPL licensing: // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package checker import ( "fmt" sdk "git.happydns.org/checker-sdk-go/checker" tlsct "git.happydns.org/checker-tls/contract" ) // Matching on the service name is more authoritative than matching on the // port: port 636 could carry anything, but _ldaps._tcp unambiguously // designates LDAP over TLS, even on a non-standard port. var directTLSServices = map[string]bool{ "https": true, "ftps": true, // FTPS implicit "smtps": true, // SMTP over TLS (legacy port 465 semantics) "submissions": true, // RFC 8314: SMTP submission over TLS "imaps": true, "pop3s": true, "nntps": true, "ircs": true, "telnets": true, "ldaps": true, "sips": true, "ipps": true, // IPP over TLS (printing) "xmpps-client": true, // XMPP client over direct TLS "xmpps-server": true, // XMPP server-to-server over direct TLS "mqtts": true, "coaps": true, "stuns": true, "turns": true, } type starttlsSpec struct { // When two SRV services share the same wire upgrade (submission/smtp both do // ESMTP STARTTLS), Proto is the canonical one agreed with checker-tls/contract. Proto string // Required is false for opportunistic STARTTLS (e.g. SMTP on port 25, s2s XMPP). // The consumer uses this to pick severity when the server does not advertise STARTTLS. Required bool } // Proto values follow the tls.endpoint.v1 contract's vocabulary; the SDK // itself has no opinion on these values, they belong to checker-tls. var starttlsServices = map[string]starttlsSpec{ "submission": {"smtp", true}, // RFC 8314: STARTTLS required "smtp": {"smtp", false}, // port 25: opportunistic "imap": {"imap", true}, "pop3": {"pop3", true}, "xmpp-client": {"xmpp-client", true}, // RFC 7590 "xmpp-server": {"xmpp-server", false}, // s2s: opportunistic "ldap": {"ldap", false}, "nntp": {"nntp", false}, "ftp": {"ftp", false}, "sieve": {"sieve", true}, "postgresql": {"postgres", false}, } // DiscoverEntries publishes tls.endpoint.v1 entries for known TLS/STARTTLS services. // Unknown service names produce no entries: we lean on the SRV naming // convention rather than guessing from the port, since a port alone // conveys no protocol semantics. func (p *srvProvider) DiscoverEntries(data any) ([]sdk.DiscoveryEntry, error) { d, ok := data.(*SRVData) if !ok { return nil, fmt.Errorf("unexpected data type %T", data) } var out []sdk.DiscoveryEntry for _, r := range d.Records { if r.IsNullTarget || r.Target == "" { continue } if directTLSServices[r.Service] { e, err := tlsct.NewEntry(tlsct.TLSEndpoint{ Host: r.Target, Port: r.Port, SNI: r.Target, }) if err != nil { return nil, fmt.Errorf("build tls entry for %s:%d: %w", r.Target, r.Port, err) } out = append(out, e) continue } if spec, ok := starttlsServices[r.Service]; ok { e, err := tlsct.NewEntry(tlsct.TLSEndpoint{ Host: r.Target, Port: r.Port, SNI: r.Target, STARTTLS: spec.Proto, RequireSTARTTLS: spec.Required, }) if err != nil { return nil, fmt.Errorf("build starttls entry for %s:%d: %w", r.Target, r.Port, err) } out = append(out, e) } } return out, nil }