mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-17 14:37:02 +00:00
smtp-check exits on the first error, which is not ideal when troubleshooting, as seeing only one error can mask others, or make it more difficult to find the underlying cause. This patch improves how smtp-check reports errors by tweaking the presentation a bit, as well as perform almost all checks regardless of whether they pass or not.
133 lines
2.7 KiB
Go
133 lines
2.7 KiB
Go
// smtp-check is a command-line too for checking SMTP setups.
|
|
//
|
|
// +build !coverage
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/smtp"
|
|
"time"
|
|
|
|
"blitiri.com.ar/go/chasquid/internal/sts"
|
|
"blitiri.com.ar/go/chasquid/internal/tlsconst"
|
|
"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")
|
|
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)
|
|
}
|
|
|
|
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", tlsconst.VersionName(cstate.Version),
|
|
tlsconst.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("")
|
|
}
|
|
}
|