From ae67409603c49b7fa73c227449264ddd10aae6a9 Mon Sep 17 00:00:00 2001 From: Michael Kropat Date: Sun, 8 Jun 2014 18:32:52 -0400 Subject: [PATCH 1/2] Support dual-stack IPv4/IPv6 mail servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses #3 Added support by adding parallel code wherever `$PUBLIC_IP` was used. Providing an IPv6 address is completely optional. Playing around on my IPv6-enabled mail server revealed that — before this change — mailinabox might try to use an IPv6 address as the value for `$PUBLIC_IP`, which wouldn't work out well. --- Vagrantfile | 1 + management/dns_update.py | 4 ++++ setup/functions.sh | 34 ++++++++++++++++++++++++++++++++-- setup/start.sh | 23 +++++++++++++++++++++-- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 861f276c..9576c27e 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -19,6 +19,7 @@ Vagrant.configure("2") do |config| # subdomain on our justtesting.email domain so we can get # started quickly. export PUBLIC_IP=auto + export PUBLIC_IPV6=auto export PUBLIC_HOSTNAME=auto-easy export CSR_COUNTRY=US diff --git a/management/dns_update.py b/management/dns_update.py index 7ac9fb2f..66a2bf6c 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -60,9 +60,13 @@ def build_zone(domain, env): records.append((None, "NS", "ns1.%s." % env["PUBLIC_HOSTNAME"])) records.append((None, "NS", "ns2.%s." % env["PUBLIC_HOSTNAME"])) records.append((None, "A", env["PUBLIC_IP"])) + if env.get('PUBLIC_IPV6'): + records.append((None, "AAAA", env["PUBLIC_IPV6"])) records.append((None, "MX", "10 %s." % env["PUBLIC_HOSTNAME"])) records.append((None, "TXT", '"v=spf1 mx -all"')) records.append(("www", "A", env["PUBLIC_IP"])) + if env.get('PUBLIC_IPV6'): + records.append(("www", "AAAA", env["PUBLIC_IPV6"])) # In PUBLIC_HOSTNAME, also define ns1 and ns2. if domain == env["PUBLIC_HOSTNAME"]: diff --git a/setup/functions.sh b/setup/functions.sh index 1a18b28d..210113ac 100644 --- a/setup/functions.sh +++ b/setup/functions.sh @@ -39,11 +39,19 @@ function get_default_publicip { get_publicip_from_web_service || get_publicip_fallback } +function get_default_publicipv6 { + get_publicipv6_from_web_service || get_publicipv6_fallback +} + function get_publicip_from_web_service { # This seems to be the most reliable way to determine the # machine's public IP address: asking a very nice web API # for how they see us. Thanks go out to icanhazip.com. - curl --fail --silent icanhazip.com 2>/dev/null + curl -4 --fail --silent icanhazip.com 2>/dev/null +} + +function get_publicipv6_from_web_service { + curl -6 --fail --silent icanhazip.com 2>/dev/null } function get_publicip_fallback { @@ -53,17 +61,39 @@ function get_publicip_fallback { # have multiple addresses if it has multiple network adapters. set -- $(hostname --ip-address 2>/dev/null) \ $(hostname --all-ip-addresses 2>/dev/null) - while (( $# )) && is_loopback_ip "$1"; do + while (( $# )) && { ! is_ipv4 "$1" || is_loopback_ip "$1"; }; do shift done printf '%s\n' "$1" # return this value } +function get_publicipv6_fallback { + set -- $(hostname --ip-address 2>/dev/null) \ + $(hostname --all-ip-addresses 2>/dev/null) + while (( $# )) && { ! is_ipv6 "$1" || is_loopback_ipv6 "$1"; }; do + shift + done + printf '%s\n' "$1" # return this value +} + +function is_ipv4 { + # helper for get_publicip_fallback + [[ "$1" == *.*.*.* ]] +} + +function is_ipv6 { + [[ "$1" == *:*:* ]] +} + function is_loopback_ip { # helper for get_publicip_fallback [[ "$1" == 127.* ]] } +function is_loopback_ipv6 { + [[ "$1" == ::1 ]] +} + function ufw_allow { if [ -z "$DISABLE_FIREWALL" ]; then # ufw has completely unhelpful output diff --git a/setup/start.sh b/setup/start.sh index 51a1ebdd..6cda9561 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -54,6 +54,19 @@ if [ -z "$PUBLIC_IP" ]; then read -e -i "$DEFAULT_PUBLIC_IP" -p "Public IP: " PUBLIC_IP fi +if [ -z "$PUBLIC_IPV6" ]; then + echo + echo "(Optional) Enter the IPv6 address of this machine. Leave blank" + echo " if the machine does not have an IPv6 address." + + if [ -z "$DEFAULT_PUBLIC_IPV6" ]; then + # set a default on first run + DEFAULT_PUBLIC_IPV6=`get_default_publicipv6` + fi + + read -e -i "$DEFAULT_PUBLIC_IPV6" -p "Public IPv6: " PUBLIC_IPV6 +fi + if [ -z "$CSR_COUNTRY" ]; then echo echo "Enter the two-letter, uppercase country code for where you" @@ -70,12 +83,17 @@ if [ -z "$CSR_COUNTRY" ]; then fi # Automatic configuration, e.g. as used in our Vagrant configuration. -if [ "$PUBLIC_IP" == "auto" ]; then +if [ "$PUBLIC_IP" = "auto" ]; then # Use a public API to get our public IP address. PUBLIC_IP=`get_default_publicip` echo "IP Address: $PUBLIC_IP" fi -if [ "$PUBLIC_HOSTNAME" == "auto-easy" ]; then +if [ "$PUBLIC_IPV6" = "auto" ]; then + # Use a public API to get our public IP address. + PUBLIC_IPV6=`get_default_publicipv6` + echo "IPv6 Address: $PUBLIC_IPV6" +fi +if [ "$PUBLIC_HOSTNAME" = "auto-easy" ]; then # Generate a probably-unique subdomain under our justtesting.email domain. PUBLIC_HOSTNAME=m`get_default_publicip | sha1sum | cut -c1-5`.justtesting.email echo "Public Hostname: $PUBLIC_HOSTNAME" @@ -97,6 +115,7 @@ cat > /etc/mailinabox.conf << EOF; STORAGE_ROOT=$STORAGE_ROOT PUBLIC_HOSTNAME=$PUBLIC_HOSTNAME PUBLIC_IP=$PUBLIC_IP +PUBLIC_IPV6=$PUBLIC_IPV6 CSR_COUNTRY=$CSR_COUNTRY EOF From fb957d2de74517d28dc062a29d9d14eeba969d47 Mon Sep 17 00:00:00 2001 From: Michael Kropat Date: Sun, 8 Jun 2014 18:44:08 -0400 Subject: [PATCH 2/2] Populate default values before echoing help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testing showed that it may take a few seconds for the default values to populate. If the help text is shown, “Enter the public IP address…,” but no prompt is shown, the user may get confused and try to enter the IP address before mailinabox has had a chance to figure out and display a suitable default value. --- setup/start.sh | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/setup/start.sh b/setup/start.sh index 6cda9561..301adead 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -24,6 +24,11 @@ fi # Gather information from the user about the hostname and public IP # address of this host. if [ -z "$PUBLIC_HOSTNAME" ]; then + if [ -z "$DEFAULT_PUBLIC_HOSTNAME" ]; then + # set a default on first run + DEFAULT_PUBLIC_HOSTNAME=`get_default_hostname` + fi + echo echo "Enter the hostname you want to assign to this machine." echo "We've guessed a value. Just backspace it if it's wrong." @@ -31,39 +36,34 @@ if [ -z "$PUBLIC_HOSTNAME" ]; then echo "be similar." echo - if [ -z "$DEFAULT_PUBLIC_HOSTNAME" ]; then - # set a default on first run - DEFAULT_PUBLIC_HOSTNAME=`get_default_hostname` - fi - read -e -i "$DEFAULT_PUBLIC_HOSTNAME" -p "Hostname: " PUBLIC_HOSTNAME fi if [ -z "$PUBLIC_IP" ]; then + if [ -z "$DEFAULT_PUBLIC_IP" ]; then + # set a default on first run + DEFAULT_PUBLIC_IP=`get_default_publicip` + fi + echo echo "Enter the public IP address of this machine, as given to" echo "you by your ISP. We've guessed a value, but just backspace" echo "it if it's wrong." echo - if [ -z "$DEFAULT_PUBLIC_IP" ]; then - # set a default on first run - DEFAULT_PUBLIC_IP=`get_default_publicip` - fi - read -e -i "$DEFAULT_PUBLIC_IP" -p "Public IP: " PUBLIC_IP fi if [ -z "$PUBLIC_IPV6" ]; then - echo - echo "(Optional) Enter the IPv6 address of this machine. Leave blank" - echo " if the machine does not have an IPv6 address." - - if [ -z "$DEFAULT_PUBLIC_IPV6" ]; then + if [ -z "$DEFAULT_PUBLIC_IPV6" ]; then # set a default on first run DEFAULT_PUBLIC_IPV6=`get_default_publicipv6` fi + echo + echo "(Optional) Enter the IPv6 address of this machine. Leave blank" + echo " if the machine does not have an IPv6 address." + read -e -i "$DEFAULT_PUBLIC_IPV6" -p "Public IPv6: " PUBLIC_IPV6 fi