mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-10-28 15:02:28 +00:00
5131fdacb4
The tlsconst internal package was created to provide human-readable names for TLS versions and ciphers, from the IANA registry. These days, that functionality is provided by stdlib's tls package, so the internal package is no longer needed, and this patch removes it. The only observable change is that the descriptive version in the Received header now says "TLS 1.3" instead of "TLS-1.3", but it's just as standard-compliant since that is in a free-form text section.
135 lines
2.8 KiB
Go
135 lines
2.8 KiB
Go
// smtp-check is a command-line too for checking SMTP setups.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/smtp"
|
|
"time"
|
|
|
|
"blitiri.com.ar/go/chasquid/internal/sts"
|
|
"blitiri.com.ar/go/spf"
|
|
|
|
"golang.org/x/net/idna"
|
|
)
|
|
|
|
var (
|
|
port = flag.String("port", "smtp",
|
|
"port to use for connecting to the MX servers")
|
|
localName = flag.String("localname", "localhost",
|
|
"specify the local name for the EHLO command")
|
|
skipTLSCheck = flag.Bool("skip_tls_check", false,
|
|
"skip TLS check (useful if connections are blocked)")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
domain := flag.Arg(0)
|
|
if domain == "" {
|
|
log.Fatal("Use: smtp-check <domain>")
|
|
}
|
|
|
|
domain, err := idna.ToASCII(domain)
|
|
if err != nil {
|
|
log.Fatalf("IDNA conversion failed: %v", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
log.Printf("=== STS policy")
|
|
policy, err := sts.UncheckedFetch(ctx, domain)
|
|
if err != nil {
|
|
log.Printf("Not available (%s)", err)
|
|
} else {
|
|
log.Printf("Parsed contents: [%+v]\n", *policy)
|
|
if err := policy.Check(); err != nil {
|
|
log.Fatalf("Invalid: %v", err)
|
|
}
|
|
log.Printf("OK")
|
|
}
|
|
log.Printf("")
|
|
|
|
mxs, err := net.LookupMX(domain)
|
|
if err != nil {
|
|
log.Fatalf("MX lookup: %v", err)
|
|
}
|
|
|
|
if len(mxs) == 0 {
|
|
log.Fatalf("MX lookup returned no results")
|
|
}
|
|
|
|
errs := []error{}
|
|
for _, mx := range mxs {
|
|
log.Printf("=== MX: %2d %s", mx.Pref, mx.Host)
|
|
|
|
ips, err := net.LookupIP(mx.Host)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for _, ip := range ips {
|
|
result, err := spf.CheckHostWithSender(ip, domain, "test@"+domain)
|
|
log.Printf("SPF %v for %v: %v", result, ip, err)
|
|
if result != spf.Pass {
|
|
errs = append(errs,
|
|
fmt.Errorf("%s: SPF failed (%v)", mx.Host, ip))
|
|
}
|
|
}
|
|
|
|
if *skipTLSCheck {
|
|
log.Printf("TLS check skipped")
|
|
} else {
|
|
c, err := smtp.Dial(mx.Host + ":" + *port)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = c.Hello(*localName)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
config := &tls.Config{
|
|
// Expect the server to have a certificate valid for the MX
|
|
// we're connecting to.
|
|
ServerName: mx.Host,
|
|
}
|
|
err = c.StartTLS(config)
|
|
if err != nil {
|
|
log.Printf("TLS error: %v", err)
|
|
errs = append(errs, fmt.Errorf("%s: TLS failed", mx.Host))
|
|
} else {
|
|
cstate, _ := c.TLSConnectionState()
|
|
log.Printf("TLS OK: %s - %s", tls.VersionName(cstate.Version),
|
|
tls.CipherSuiteName(cstate.CipherSuite))
|
|
}
|
|
|
|
c.Close()
|
|
}
|
|
|
|
if policy != nil {
|
|
if !policy.MXIsAllowed(mx.Host) {
|
|
log.Printf("NOT allowed by STS policy")
|
|
errs = append(errs, fmt.Errorf("%s: STS failed", mx.Host))
|
|
}
|
|
log.Printf("Allowed by policy")
|
|
}
|
|
|
|
log.Printf("")
|
|
}
|
|
|
|
if len(errs) == 0 {
|
|
log.Printf("=== Success")
|
|
} else {
|
|
log.Printf("=== FAILED")
|
|
for _, err := range errs {
|
|
log.Printf("%v", err)
|
|
}
|
|
log.Fatal("")
|
|
}
|
|
}
|