mirror of
https://github.com/mjl-/mox.git
synced 2025-07-14 03:34:37 +03:00
add more documentation, examples with tests to illustrate reusable components
This commit is contained in:
@ -1,4 +1,29 @@
|
||||
// Package smtpclient is an SMTP client, used by the queue for sending outgoing messages.
|
||||
// Package smtpclient is an SMTP client, for submitting to an SMTP server or
|
||||
// delivering from a queue.
|
||||
//
|
||||
// Email clients can submit a message to SMTP server, after which the server is
|
||||
// responsible for delivery to the final destination. A submission client
|
||||
// typically connects with TLS, and PKIX-verifies the server's certificate. The
|
||||
// client then authenticates using a SASL mechanism.
|
||||
//
|
||||
// Email servers manage a message queue, from which they will try to deliver
|
||||
// messages. In case of temporary failures, the message is kept in the queue and
|
||||
// tried again later. For delivery, no authentication is done. TLS is opportunistic
|
||||
// by default (TLS certificates not verified), but TLS and certificate verification
|
||||
// can be opted into by domains by specifying an MTA-STS policy for the domain, or
|
||||
// DANE TLSA records for their MX hosts.
|
||||
//
|
||||
// Delivering a message from a queue would involve:
|
||||
// 1. Looking up an MTA-STS policy, through a cache.
|
||||
// 2. Resolving the MX targets for a domain, through smtpclient.GatherDestinations,
|
||||
// and for each destination try delivery through:
|
||||
// 3. Looking up IP addresses for the destination, with smtpclient.GatherIPs.
|
||||
// 4. Looking up TLSA records for DANE, in case of authentic DNS responses
|
||||
// (DNSSEC), with smtpclient.GatherTLSA.
|
||||
// 5. Dialing the MX target with smtpclient.Dial.
|
||||
// 6. Initializing a SMTP session with smtpclient.New, with proper TLS
|
||||
// configuration based on discovered MTA-STS and DANE policies, and finally calling
|
||||
// client.Deliver.
|
||||
package smtpclient
|
||||
|
||||
import (
|
||||
@ -201,22 +226,28 @@ type Opts struct {
|
||||
// returned on which eventually Close must be called. Otherwise an error is
|
||||
// returned and the caller is responsible for closing the connection.
|
||||
//
|
||||
// Connecting to the correct host is outside the scope of the client. The queue
|
||||
// managing outgoing messages decides which host to deliver to, taking multiple MX
|
||||
// records with preferences, other DNS records, MTA-STS, retries and special
|
||||
// cases into account.
|
||||
// Connecting to the correct host for delivery can be done using the Gather
|
||||
// functions, and with Dial. The queue managing outgoing messages typically decides
|
||||
// which host to deliver to, taking multiple MX records with preferences, other DNS
|
||||
// records, MTA-STS, retries and special cases into account.
|
||||
//
|
||||
// tlsMode indicates if and how TLS may/must (not) be used. tlsVerifyPKIX
|
||||
// indicates if TLS certificates must be validated against the PKIX/WebPKI
|
||||
// certificate authorities (if TLS is done). DANE-verification is done when
|
||||
// opts.DANERecords is not nil. TLS verification errors will be ignored if
|
||||
// opts.IgnoreTLSVerification is set. If TLS is done, PKIX verification is
|
||||
// always performed for tracking the results for TLS reporting, but if
|
||||
// tlsVerifyPKIX is false, the verification result does not affect the
|
||||
// connection. At the time of writing, delivery of email on the internet is done
|
||||
// with opportunistic TLS without PKIX verification by default. Recipient domains
|
||||
// can opt-in to PKIX verification by publishing an MTA-STS policy, or opt-in to
|
||||
// DANE verification by publishing DNSSEC-protected TLSA records in DNS.
|
||||
// tlsMode indicates if and how TLS may/must (not) be used.
|
||||
//
|
||||
// tlsVerifyPKIX indicates if TLS certificates must be validated against the
|
||||
// PKIX/WebPKI certificate authorities (if TLS is done).
|
||||
//
|
||||
// DANE-verification is done when opts.DANERecords is not nil.
|
||||
//
|
||||
// TLS verification errors will be ignored if opts.IgnoreTLSVerification is set.
|
||||
//
|
||||
// If TLS is done, PKIX verification is always performed for tracking the results
|
||||
// for TLS reporting, but if tlsVerifyPKIX is false, the verification result does
|
||||
// not affect the connection.
|
||||
//
|
||||
// At the time of writing, delivery of email on the internet is done with
|
||||
// opportunistic TLS without PKIX verification by default. Recipient domains can
|
||||
// opt-in to PKIX verification by publishing an MTA-STS policy, or opt-in to DANE
|
||||
// verification by publishing DNSSEC-protected TLSA records in DNS.
|
||||
func New(ctx context.Context, elog *slog.Logger, conn net.Conn, tlsMode TLSMode, tlsVerifyPKIX bool, ehloHostname, remoteHostname dns.Domain, opts Opts) (*Client, error) {
|
||||
ensureResult := func(r *tlsrpt.Result) *tlsrpt.Result {
|
||||
if r == nil {
|
||||
|
@ -41,8 +41,9 @@ type Dialer interface {
|
||||
// Dial connects to host by dialing ips, taking previous attempts in dialedIPs into
|
||||
// accounts (for greylisting, blocklisting and ipv4/ipv6).
|
||||
//
|
||||
// If the previous attempt used IPv4, this attempt will use IPv6 (in case one of
|
||||
// the IPs is in a DNSBL).
|
||||
// If the previous attempt used IPv4, this attempt will use IPv6 (useful in case
|
||||
// one of the IPs is in a DNSBL).
|
||||
//
|
||||
// The second attempt for an address family we prefer the same IP as earlier, to
|
||||
// increase our chances if remote is doing greylisting.
|
||||
//
|
||||
|
58
smtpclient/examples_test.go
Normal file
58
smtpclient/examples_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package smtpclient_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
|
||||
"github.com/mjl-/mox/dns"
|
||||
"github.com/mjl-/mox/sasl"
|
||||
"github.com/mjl-/mox/smtpclient"
|
||||
)
|
||||
|
||||
func ExampleClient() {
|
||||
// Submit a message to an SMTP server, with authentication. The SMTP server is
|
||||
// responsible for getting the message delivered.
|
||||
|
||||
// Make TCP connection to submission server.
|
||||
conn, err := net.Dial("tcp", "submit.example.org:465")
|
||||
if err != nil {
|
||||
log.Fatalf("dial submission server: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Initialize the SMTP session, with a EHLO, STARTTLS and authentication.
|
||||
// Verify the server TLS certificate with PKIX/WebPKI.
|
||||
ctx := context.Background()
|
||||
tlsVerifyPKIX := true
|
||||
opts := smtpclient.Opts{
|
||||
Auth: []sasl.Client{
|
||||
// Prefer strongest authentication mechanism, allow up to older CRAM-MD5.
|
||||
sasl.NewClientSCRAMSHA256("mjl", "test1234"),
|
||||
sasl.NewClientSCRAMSHA1("mjl", "test1234"),
|
||||
sasl.NewClientCRAMMD5("mjl", "test1234"),
|
||||
},
|
||||
}
|
||||
localname := dns.Domain{ASCII: "localhost"}
|
||||
remotename := dns.Domain{ASCII: "submit.example.org"}
|
||||
client, err := smtpclient.New(ctx, slog.Default(), conn, smtpclient.TLSImmediate, tlsVerifyPKIX, localname, remotename, opts)
|
||||
if err != nil {
|
||||
log.Fatalf("initialize smtp to submission server: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Send the message to the server, which will add it to its queue.
|
||||
req8bitmime := false // ASCII-only, so 8bitmime not required.
|
||||
reqSMTPUTF8 := false // No UTF-8 headers, so smtputf8 not required.
|
||||
requireTLS := false // Not supported by most servers at the time of writing.
|
||||
msg := "From: <mjl@example.org>\r\nTo: <other@example.org>\r\nSubject: hi\r\n\r\nnice to test you.\r\n"
|
||||
err = client.Deliver(ctx, "mjl@example.org", "other@example.com", int64(len(msg)), strings.NewReader(msg), req8bitmime, reqSMTPUTF8, requireTLS)
|
||||
if err != nil {
|
||||
log.Fatalf("submit message to smtp server: %v", err)
|
||||
}
|
||||
|
||||
// Message has been submitted.
|
||||
}
|
@ -42,11 +42,11 @@ var (
|
||||
// expandedNextHopAuthentic indicates if the DNS records after following CNAMEs were
|
||||
// DNSSEC secure.
|
||||
//
|
||||
// These authentic flags are used by DANE, to determine where to look up TLSA
|
||||
// These authentic results are needed for DANE, to determine where to look up TLSA
|
||||
// records, and which names to allow in the remote TLS certificate. If MX records
|
||||
// were found, both the original and expanded next-hops must be authentic for DANE
|
||||
// to apply. For a non-IP with no MX records found, the authentic result can be
|
||||
// used to decide which of the names to use as TLSA base domain.
|
||||
// to be option. For a non-IP with no MX records found, the authentic result can
|
||||
// be used to decide which of the names to use as TLSA base domain.
|
||||
func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, origNextHop dns.IPDomain) (haveMX, origNextHopAuthentic, expandedNextHopAuthentic bool, expandedNextHop dns.Domain, hosts []dns.IPDomain, permanent bool, err error) {
|
||||
// ../rfc/5321:3824
|
||||
|
||||
|
Reference in New Issue
Block a user