mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +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.
|
// 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.
|
// 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"}
|
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
|
// If we find IPs based on network interfaces, {public,private}ListenerIPs are set
|
||||||
// based on these values.
|
// 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
|
var dnshostname dns.Domain
|
||||||
if hostname == "" {
|
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()
|
hostnameStr, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("hostname: %s", err)
|
fatalf("hostname: %s", err)
|
||||||
|
@ -311,9 +309,13 @@ again with the -hostname flag.
|
||||||
ips, err := resolver.LookupIPAddr(ipctx, dnshostname.ASCII+".")
|
ips, err := resolver.LookupIPAddr(ipctx, dnshostname.ASCII+".")
|
||||||
ipcancel()
|
ipcancel()
|
||||||
var xips []net.IPAddr
|
var xips []net.IPAddr
|
||||||
var xipstrs []string
|
var hostIPs []string
|
||||||
var dnswarned bool
|
var dnswarned bool
|
||||||
|
hostPrivate := len(ips) > 0
|
||||||
for _, ip := range ips {
|
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
|
// 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
|
// 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
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
xips = append(xips, ip)
|
xips = append(xips, ip)
|
||||||
xipstrs = append(xipstrs, ip.String())
|
hostIPs = append(hostIPs, ip.String())
|
||||||
}
|
}
|
||||||
if err == nil && len(xips) == 0 {
|
if err == nil && len(xips) == 0 {
|
||||||
// todo: possibly check this by trying to resolve without using /etc/hosts?
|
// todo: possibly check this by trying to resolve without using /etc/hosts?
|
||||||
err = errors.New("hostname not in dns, probably only in /etc/hosts")
|
err = errors.New("hostname not in dns, probably only in /etc/hosts")
|
||||||
}
|
}
|
||||||
ips = xips
|
ips = xips
|
||||||
if hostname != "" {
|
|
||||||
// Host name was specified, assume we will run on a machine with those IPs.
|
// We may have found private and public IPs on the machine, and IPs for the host
|
||||||
publicListenerIPs = xipstrs
|
// name we think we should use. They may not match with each other. E.g. the public
|
||||||
publicIPs = xipstrs
|
// 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 err != nil {
|
||||||
if !dnswarned {
|
if !dnswarned {
|
||||||
|
@ -422,11 +467,11 @@ This likely means one of two things:
|
||||||
{ASCII: "sbl.spamhaus.org"},
|
{ASCII: "sbl.spamhaus.org"},
|
||||||
{ASCII: "bl.spamcop.net"},
|
{ASCII: "bl.spamcop.net"},
|
||||||
}
|
}
|
||||||
if len(publicIPs) > 0 {
|
if len(hostIPs) > 0 {
|
||||||
fmt.Printf("Checking whether your public IPs are listed in popular DNS block lists...")
|
fmt.Printf("Checking whether host name IPs are listed in popular DNS block lists...")
|
||||||
var listed bool
|
var listed bool
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
for _, ip := range publicIPs {
|
for _, ip := range hostIPs {
|
||||||
dnsblctx, dnsblcancel := context.WithTimeout(resolveCtx, 5*time.Second)
|
dnsblctx, dnsblcancel := context.WithTimeout(resolveCtx, 5*time.Second)
|
||||||
status, expl, err := dnsbl.Lookup(dnsblctx, resolver, zone, net.ParseIP(ip))
|
status, expl, err := dnsbl.Lookup(dnsblctx, resolver, zone, net.ParseIP(ip))
|
||||||
dnsblcancel()
|
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:
|
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("- https://multirbl.valli.org/lookup/%s.html\n", url.PathEscape(ip))
|
||||||
}
|
}
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
|
@ -458,20 +503,28 @@ listed in more DNS block lists, visit:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(publicIPs) == 0 {
|
if defaultPublicListenerIPs {
|
||||||
log.Printf(`WARNING: Could not find your public IP address(es). The "public" listener is
|
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
|
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
|
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
|
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
|
address on the same port(s). If you are behind a NAT, instead configure the
|
||||||
actual public IPs in the listener's "NATIPs" option.
|
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
|
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
|
rate-limiting both for accepting email and blocking bad actors (such as with too
|
||||||
too many authentication failures).
|
many authentication failures).
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -510,7 +563,8 @@ too many authentication failures).
|
||||||
fmt.Printf("Admin password: %s\n", adminpw)
|
fmt.Printf("Admin password: %s\n", adminpw)
|
||||||
|
|
||||||
public := config.Listener{
|
public := config.Listener{
|
||||||
IPs: publicListenerIPs,
|
IPs: publicListenerIPs,
|
||||||
|
NATIPs: publicNATIPs,
|
||||||
}
|
}
|
||||||
public.SMTP.Enabled = true
|
public.SMTP.Enabled = true
|
||||||
public.Submissions.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
|
cd /tmp/mox
|
||||||
mox quickstart moxtest1@mox1.example "$MOX_UID" > output.txt
|
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
|
cat <<EOF >>config/mox.conf
|
||||||
|
|
||||||
TLS:
|
TLS:
|
||||||
|
|
3
testdata/integration/moxmail2.sh
vendored
3
testdata/integration/moxmail2.sh
vendored
|
@ -9,7 +9,8 @@ mkdir /tmp/mox
|
||||||
cd /tmp/mox
|
cd /tmp/mox
|
||||||
mox quickstart moxtest2@mox2.example "$MOX_UID" > output.txt
|
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
|
cat <<EOF >>config/mox.conf
|
||||||
|
|
||||||
TLS:
|
TLS:
|
||||||
|
|
Loading…
Reference in a new issue