mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 00:13:47 +03:00
quickstart: recognize likely NAT setup and set up host IPs in "NATIPs" field in the public listener
for issue #59 by pmarini, thanks!
This commit is contained in:
parent
cde54442d2
commit
d649cf7dc2
3 changed files with 141 additions and 85 deletions
220
quickstart.go
220
quickstart.go
|
@ -149,78 +149,76 @@ logging in with IMAP.
|
|||
// We are going to find the (public) IPs to listen on and possibly the host name.
|
||||
|
||||
// Start with reasonable defaults. We'll replace them specific IPs, if we can find them.
|
||||
publicListenerIPs := []string{"0.0.0.0", "::"}
|
||||
privateListenerIPs := []string{"127.0.0.1", "::1"}
|
||||
publicListenerIPs := []string{"0.0.0.0", "::"}
|
||||
var publicNATIPs []string // Actual public IP, but when it is NATed and machine doesn't have direct access.
|
||||
defaultPublicListenerIPs := true
|
||||
|
||||
// If we find IPs based on network interfaces, {public,private}ListenerIPs are set
|
||||
// based on these values.
|
||||
var privateIPs, publicIPs []string
|
||||
var loopbackIPs, privateIPs, publicIPs []string
|
||||
|
||||
// Gather IP addresses for public and private listeners.
|
||||
// We look at each network interface. If an interface has a private address, we
|
||||
// conservatively assume all addresses on that interface are private.
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
fatalf("listing network interfaces: %s", err)
|
||||
}
|
||||
parseAddrIP := func(s string) net.IP {
|
||||
if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
ip, _, _ := net.ParseCIDR(s)
|
||||
return ip
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
fatalf("listing address for network interface: %s", err)
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// todo: should we detect temporary/ephemeral ipv6 addresses and not add them?
|
||||
var nonpublic bool
|
||||
for _, addr := range addrs {
|
||||
ip := parseAddrIP(addr.String())
|
||||
if ip.IsInterfaceLocalMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsMulticast() {
|
||||
continue
|
||||
}
|
||||
if ip.IsLoopback() || ip.IsPrivate() {
|
||||
nonpublic = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ip := parseAddrIP(addr.String())
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if ip.IsInterfaceLocalMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsMulticast() {
|
||||
continue
|
||||
}
|
||||
if nonpublic {
|
||||
if ip.IsLoopback() {
|
||||
loopbackIPs = append(loopbackIPs, ip.String())
|
||||
} else {
|
||||
privateIPs = append(privateIPs, ip.String())
|
||||
}
|
||||
} else {
|
||||
publicIPs = append(publicIPs, ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dnshostname dns.Domain
|
||||
if hostname == "" {
|
||||
// Gather IP addresses for public and private listeners.
|
||||
// If we cannot find addresses for a category we fallback to all ips or localhost ips.
|
||||
// We look at each network interface. If an interface has a private address, we
|
||||
// conservatively assume all addresses on that interface are private.
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
fatalf("listing network interfaces: %s", err)
|
||||
}
|
||||
parseAddrIP := func(s string) net.IP {
|
||||
if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
ip, _, _ := net.ParseCIDR(s)
|
||||
return ip
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
fatalf("listing address for network interface: %s", err)
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// todo: should we detect temporary/ephemeral ipv6 addresses and not add them?
|
||||
var nonpublic bool
|
||||
for _, addr := range addrs {
|
||||
ip := parseAddrIP(addr.String())
|
||||
if ip.IsInterfaceLocalMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsMulticast() {
|
||||
continue
|
||||
}
|
||||
if ip.IsLoopback() || ip.IsPrivate() {
|
||||
nonpublic = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ip := parseAddrIP(addr.String())
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if ip.IsInterfaceLocalMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsMulticast() {
|
||||
continue
|
||||
}
|
||||
if nonpublic {
|
||||
privateIPs = append(privateIPs, ip.String())
|
||||
} else {
|
||||
publicIPs = append(publicIPs, ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(publicIPs) > 0 {
|
||||
publicListenerIPs = publicIPs
|
||||
}
|
||||
if len(privateIPs) > 0 {
|
||||
privateListenerIPs = privateIPs
|
||||
}
|
||||
|
||||
hostnameStr, err := os.Hostname()
|
||||
if err != nil {
|
||||
fatalf("hostname: %s", err)
|
||||
|
@ -311,9 +309,13 @@ again with the -hostname flag.
|
|||
ips, err := resolver.LookupIPAddr(ipctx, dnshostname.ASCII+".")
|
||||
ipcancel()
|
||||
var xips []net.IPAddr
|
||||
var xipstrs []string
|
||||
var hostIPs []string
|
||||
var dnswarned bool
|
||||
hostPrivate := len(ips) > 0
|
||||
for _, ip := range ips {
|
||||
if !ip.IP.IsPrivate() {
|
||||
hostPrivate = false
|
||||
}
|
||||
// During linux install, you may get an alias for you full hostname in /etc/hosts
|
||||
// resolving to 127.0.1.1, which would result in a false positive about the
|
||||
// hostname having a record. Filter it out. It is a bit surprising that hosts don't
|
||||
|
@ -324,17 +326,60 @@ again with the -hostname flag.
|
|||
continue
|
||||
}
|
||||
xips = append(xips, ip)
|
||||
xipstrs = append(xipstrs, ip.String())
|
||||
hostIPs = append(hostIPs, ip.String())
|
||||
}
|
||||
if err == nil && len(xips) == 0 {
|
||||
// todo: possibly check this by trying to resolve without using /etc/hosts?
|
||||
err = errors.New("hostname not in dns, probably only in /etc/hosts")
|
||||
}
|
||||
ips = xips
|
||||
if hostname != "" {
|
||||
// Host name was specified, assume we will run on a machine with those IPs.
|
||||
publicListenerIPs = xipstrs
|
||||
publicIPs = xipstrs
|
||||
|
||||
// We may have found private and public IPs on the machine, and IPs for the host
|
||||
// name we think we should use. They may not match with each other. E.g. the public
|
||||
// IPs on interfaces could be different from the IPs for the host. We don't try to
|
||||
// detect all possible configs, but just generate what makes sense given whether we
|
||||
// found public/private/hostname IPs. If the user is doing sensible things, it
|
||||
// should be correct. But they should be checking the generated config file anyway.
|
||||
// And we do log which host name we are using, and whether we detected a NAT setup.
|
||||
// In the future, we may do an interactive setup that can guide the user better.
|
||||
|
||||
if !hostPrivate && len(publicIPs) == 0 && len(privateIPs) > 0 {
|
||||
// We only have private IPs, assume we are behind a NAT and put the IPs of the host in NATIPs.
|
||||
publicListenerIPs = privateIPs
|
||||
publicNATIPs = hostIPs
|
||||
defaultPublicListenerIPs = false
|
||||
if len(loopbackIPs) > 0 {
|
||||
privateListenerIPs = loopbackIPs
|
||||
}
|
||||
} else {
|
||||
if len(hostIPs) > 0 {
|
||||
publicListenerIPs = hostIPs
|
||||
defaultPublicListenerIPs = false
|
||||
|
||||
// Only keep private IPs that are not in host-based publicListenerIPs. For
|
||||
// internal-only setups, including integration tests.
|
||||
m := map[string]bool{}
|
||||
for _, ip := range hostIPs {
|
||||
m[ip] = true
|
||||
}
|
||||
var npriv []string
|
||||
for _, ip := range privateIPs {
|
||||
if !m[ip] {
|
||||
npriv = append(npriv, ip)
|
||||
}
|
||||
}
|
||||
sort.Strings(npriv)
|
||||
privateIPs = npriv
|
||||
} else if len(publicIPs) > 0 {
|
||||
publicListenerIPs = publicIPs
|
||||
defaultPublicListenerIPs = false
|
||||
hostIPs = publicIPs // For DNSBL check below.
|
||||
}
|
||||
if len(privateIPs) > 0 {
|
||||
privateListenerIPs = append(privateIPs, loopbackIPs...)
|
||||
} else if len(loopbackIPs) > 0 {
|
||||
privateListenerIPs = loopbackIPs
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if !dnswarned {
|
||||
|
@ -422,11 +467,11 @@ This likely means one of two things:
|
|||
{ASCII: "sbl.spamhaus.org"},
|
||||
{ASCII: "bl.spamcop.net"},
|
||||
}
|
||||
if len(publicIPs) > 0 {
|
||||
fmt.Printf("Checking whether your public IPs are listed in popular DNS block lists...")
|
||||
if len(hostIPs) > 0 {
|
||||
fmt.Printf("Checking whether host name IPs are listed in popular DNS block lists...")
|
||||
var listed bool
|
||||
for _, zone := range zones {
|
||||
for _, ip := range publicIPs {
|
||||
for _, ip := range hostIPs {
|
||||
dnsblctx, dnsblcancel := context.WithTimeout(resolveCtx, 5*time.Second)
|
||||
status, expl, err := dnsbl.Lookup(dnsblctx, resolver, zone, net.ParseIP(ip))
|
||||
dnsblcancel()
|
||||
|
@ -449,7 +494,7 @@ email. Your IP may be in block lists only temporarily. To see if your IPs are
|
|||
listed in more DNS block lists, visit:
|
||||
|
||||
`)
|
||||
for _, ip := range publicIPs {
|
||||
for _, ip := range hostIPs {
|
||||
fmt.Printf("- https://multirbl.valli.org/lookup/%s.html\n", url.PathEscape(ip))
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
@ -458,20 +503,28 @@ listed in more DNS block lists, visit:
|
|||
}
|
||||
}
|
||||
|
||||
if len(publicIPs) == 0 {
|
||||
log.Printf(`WARNING: Could not find your public IP address(es). The "public" listener is
|
||||
if defaultPublicListenerIPs {
|
||||
log.Printf(`
|
||||
WARNING: Could not find your public IP address(es). The "public" listener is
|
||||
configured to listen on 0.0.0.0 (IPv4) and :: (IPv6). If you don't change these
|
||||
to your actual public IP addresses, you will likely get "address in use" errors
|
||||
when starting mox because the "internal" listener binds to a specific IP
|
||||
address on the same port(s). If you are behind a NAT, instead configure the
|
||||
actual public IPs in the listener's "NATIPs" option.
|
||||
|
||||
If you are behind a NAT that does not preserve the remote IPs of connections,
|
||||
you will likely experience problems accepting email due to IP-based policies.
|
||||
For example, SPF is a mechanism that checks if an IP address is allowed to send
|
||||
`)
|
||||
}
|
||||
if len(publicNATIPs) > 0 {
|
||||
log.Printf(`
|
||||
NOTE: Quickstart used the IPs of the host name of the mail server, but only
|
||||
found private IPs on the machine. This indicates this machine is behind a NAT,
|
||||
so the host IPs were configured in the NATIPs field of the public listeners. If
|
||||
you are behind a NAT that does not preserve the remote IPs of connections, you
|
||||
will likely experience problems accepting email due to IP-based policies. For
|
||||
example, SPF is a mechanism that checks if an IP address is allowed to send
|
||||
email for a domain, and mox uses IP-based (non)junk classification, and IP-based
|
||||
rate-limiting both for accepting email and blocking bad actors (such as with
|
||||
too many authentication failures).
|
||||
rate-limiting both for accepting email and blocking bad actors (such as with too
|
||||
many authentication failures).
|
||||
|
||||
`)
|
||||
}
|
||||
|
@ -510,7 +563,8 @@ too many authentication failures).
|
|||
fmt.Printf("Admin password: %s\n", adminpw)
|
||||
|
||||
public := config.Listener{
|
||||
IPs: publicListenerIPs,
|
||||
IPs: publicListenerIPs,
|
||||
NATIPs: publicNATIPs,
|
||||
}
|
||||
public.SMTP.Enabled = true
|
||||
public.Submissions.Enabled = true
|
||||
|
|
3
testdata/integration/moxacmepebble.sh
vendored
3
testdata/integration/moxacmepebble.sh
vendored
|
@ -9,7 +9,8 @@ mkdir /tmp/mox
|
|||
cd /tmp/mox
|
||||
mox quickstart moxtest1@mox1.example "$MOX_UID" > output.txt
|
||||
|
||||
sed -i -e '/- 172.28.1.10/d' -e 's/- 0.0.0.0/- 172.28.1.10/' -e '/- ::/d' -e 's/letsencrypt:/pebble:/g' -e 's/: letsencrypt/: pebble/g' -e 's,DirectoryURL: https://acme-v02.api.letsencrypt.org/directory,DirectoryURL: https://acmepebble.example:14000/dir,' -e 's/SMTP:$/SMTP:\n\t\t\tFirstTimeSenderDelay: 1s/' config/mox.conf
|
||||
cp config/mox.conf config/mox.conf.orig
|
||||
sed -i -e 's/letsencrypt:/pebble:/g' -e 's/: letsencrypt/: pebble/g' -e 's,DirectoryURL: https://acme-v02.api.letsencrypt.org/directory,DirectoryURL: https://acmepebble.example:14000/dir,' -e 's/SMTP:$/SMTP:\n\t\t\tFirstTimeSenderDelay: 1s/' config/mox.conf
|
||||
cat <<EOF >>config/mox.conf
|
||||
|
||||
TLS:
|
||||
|
|
3
testdata/integration/moxmail2.sh
vendored
3
testdata/integration/moxmail2.sh
vendored
|
@ -9,7 +9,8 @@ mkdir /tmp/mox
|
|||
cd /tmp/mox
|
||||
mox quickstart moxtest2@mox2.example "$MOX_UID" > output.txt
|
||||
|
||||
sed -i -e '/- 172.28.1.20/d' -e 's/- 0.0.0.0/- 172.28.1.20/' -e '/- ::/d' -e 's,ACME: .*$,KeyCerts:\n\t\t\t\t-\n\t\t\t\t\tCertFile: /integration/tls/moxmail2.pem\n\t\t\t\t\tKeyFile: /integration/tls/moxmail2-key.pem\n\t\t\t\t-\n\t\t\t\t\tCertFile: /integration/tls/mox2-autoconfig.pem\n\t\t\t\t\tKeyFile: /integration/tls/mox2-autoconfig-key.pem\n\t\t\t\t-\n\t\t\t\t\tCertFile: /integration/tls/mox2-mtasts.pem\n\t\t\t\t\tKeyFile: /integration/tls/mox2-mtasts-key.pem\n,' -e 's/SMTP:$/SMTP:\n\t\t\tFirstTimeSenderDelay: 1s/' config/mox.conf
|
||||
cp config/mox.conf config/mox.conf.orig
|
||||
sed -i -e 's,ACME: .*$,KeyCerts:\n\t\t\t\t-\n\t\t\t\t\tCertFile: /integration/tls/moxmail2.pem\n\t\t\t\t\tKeyFile: /integration/tls/moxmail2-key.pem\n\t\t\t\t-\n\t\t\t\t\tCertFile: /integration/tls/mox2-autoconfig.pem\n\t\t\t\t\tKeyFile: /integration/tls/mox2-autoconfig-key.pem\n\t\t\t\t-\n\t\t\t\t\tCertFile: /integration/tls/mox2-mtasts.pem\n\t\t\t\t\tKeyFile: /integration/tls/mox2-mtasts-key.pem\n,' -e 's/SMTP:$/SMTP:\n\t\t\tFirstTimeSenderDelay: 1s/' config/mox.conf
|
||||
cat <<EOF >>config/mox.conf
|
||||
|
||||
TLS:
|
||||
|
|
Loading…
Reference in a new issue