Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | |
| 3 | log_f() { |
| 4 | if [[ ${2} == "no_nl" ]]; then |
| 5 | echo -n "$(date) - ${1}" |
| 6 | elif [[ ${2} == "no_date" ]]; then |
| 7 | echo "${1}" |
| 8 | elif [[ ${2} != "redis_only" ]]; then |
| 9 | echo "$(date) - ${1}" |
| 10 | fi |
| 11 | if [[ ${3} == "b64" ]]; then |
| 12 | ${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}")\"}" > /dev/null |
| 13 | else |
| 14 | ${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}" | \ |
| 15 | tr '%&;$"[]{}-\r\n' ' ')\"}" > /dev/null |
| 16 | fi |
| 17 | } |
| 18 | |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 19 | verify_email(){ |
| 20 | regex="^(([A-Za-z0-9]+((\.|\-|\_|\+)?[A-Za-z0-9]?)*[A-Za-z0-9]+)|[A-Za-z0-9]+)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" |
| 21 | if [[ $1 =~ ${regex} ]]; then |
| 22 | return 0 |
| 23 | else |
| 24 | return 1 |
| 25 | fi |
| 26 | } |
| 27 | |
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 28 | verify_hash_match(){ |
| 29 | CERT_HASH=$(openssl x509 -in "${1}" -noout -pubkey | openssl md5) |
| 30 | KEY_HASH=$(openssl pkey -in "${2}" -pubout | openssl md5) |
| 31 | if [[ ${CERT_HASH} != ${KEY_HASH} ]]; then |
| 32 | log_f "Certificate and key hashes do not match!" |
| 33 | return 1 |
| 34 | else |
| 35 | log_f "Verified hashes." |
| 36 | return 0 |
| 37 | fi |
| 38 | } |
| 39 | |
| 40 | get_ipv4(){ |
| 41 | local IPV4= |
| 42 | local IPV4_SRCS= |
| 43 | local TRY= |
| 44 | IPV4_SRCS[0]="ip4.mailcow.email" |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 45 | IPV4_SRCS[1]="ip4.nevondo.com" |
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 46 | until [[ ! -z ${IPV4} ]] || [[ ${TRY} -ge 10 ]]; do |
| 47 | IPV4=$(curl --connect-timeout 3 -m 10 -L4s ${IPV4_SRCS[$RANDOM % ${#IPV4_SRCS[@]} ]} | grep -E "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$") |
| 48 | [[ ! -z ${TRY} ]] && sleep 1 |
| 49 | TRY=$((TRY+1)) |
| 50 | done |
| 51 | echo ${IPV4} |
| 52 | } |
| 53 | |
| 54 | get_ipv6(){ |
| 55 | local IPV6= |
| 56 | local IPV6_SRCS= |
| 57 | local TRY= |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 58 | IPV6_SRCS[0]="ip6.mailcow.email" |
| 59 | IPV6_SRCS[1]="ip6.nevondo.com" |
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 60 | until [[ ! -z ${IPV6} ]] || [[ ${TRY} -ge 10 ]]; do |
| 61 | IPV6=$(curl --connect-timeout 3 -m 10 -L6s ${IPV6_SRCS[$RANDOM % ${#IPV6_SRCS[@]} ]} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") |
| 62 | [[ ! -z ${TRY} ]] && sleep 1 |
| 63 | TRY=$((TRY+1)) |
| 64 | done |
| 65 | echo ${IPV6} |
| 66 | } |
| 67 | |
| 68 | check_domain(){ |
| 69 | DOMAIN=$1 |
| 70 | A_DOMAIN=$(dig A ${DOMAIN} +short | tail -n 1) |
| 71 | AAAA_DOMAIN=$(dig AAAA ${DOMAIN} +short | tail -n 1) |
Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 72 | # Hard-fail on CAA errors for MAILCOW_HOSTNAME |
| 73 | PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-) |
| 74 | CAAS=( $(dig CAA ${PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) |
| 75 | if [[ ! -z ${CAAS} ]]; then |
| 76 | if [[ ${CAAS[@]} =~ "letsencrypt.org" ]]; then |
| 77 | log_f "Validated CAA for parent domain ${PARENT_DOMAIN}" |
| 78 | else |
| 79 | log_f "Lets Encrypt disallowed for ${PARENT_DOMAIN} by CAA record" |
| 80 | return 1 |
| 81 | fi |
| 82 | fi |
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 83 | # Check if CNAME without v6 enabled target |
| 84 | if [[ ! -z ${AAAA_DOMAIN} ]] && [[ -z $(echo ${AAAA_DOMAIN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then |
| 85 | AAAA_DOMAIN= |
| 86 | fi |
| 87 | if [[ ! -z ${AAAA_DOMAIN} ]]; then |
| 88 | log_f "Found AAAA record for ${DOMAIN}: ${AAAA_DOMAIN} - skipping A record check" |
| 89 | if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_DOMAIN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]] || [[ ${SNAT6_TO_SOURCE} != "n" ]]; then |
| 90 | if verify_challenge_path "${DOMAIN}" 6; then |
| 91 | log_f "Confirmed AAAA record with IP $(expand ${AAAA_DOMAIN})" |
| 92 | return 0 |
| 93 | else |
| 94 | log_f "Confirmed AAAA record with IP $(expand ${AAAA_DOMAIN}), but HTTP validation failed" |
| 95 | fi |
| 96 | else |
| 97 | log_f "Cannot match your IP $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) against hostname ${DOMAIN} (DNS returned $(expand ${AAAA_DOMAIN}))" |
| 98 | fi |
| 99 | elif [[ ! -z ${A_DOMAIN} ]]; then |
| 100 | log_f "Found A record for ${DOMAIN}: ${A_DOMAIN}" |
| 101 | if [[ ${IPV4:-ERR} == ${A_DOMAIN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]] || [[ ${SNAT_TO_SOURCE} != "n" ]]; then |
| 102 | if verify_challenge_path "${DOMAIN}" 4; then |
| 103 | log_f "Confirmed A record ${A_DOMAIN}" |
| 104 | return 0 |
| 105 | else |
| 106 | log_f "Confirmed A record with IP ${A_DOMAIN}, but HTTP validation failed" |
| 107 | fi |
| 108 | else |
| 109 | log_f "Cannot match your IP ${IPV4} against hostname ${DOMAIN} (DNS returned ${A_DOMAIN})" |
| 110 | fi |
| 111 | else |
| 112 | log_f "No A or AAAA record found for hostname ${DOMAIN}" |
| 113 | fi |
| 114 | return 1 |
| 115 | } |
| 116 | |
| 117 | verify_challenge_path(){ |
| 118 | if [[ ${SKIP_HTTP_VERIFICATION} == "y" ]]; then |
| 119 | echo '(skipping check, returning 0)' |
| 120 | return 0 |
| 121 | fi |
| 122 | # verify_challenge_path URL 4|6 |
| 123 | RANDOM_N=${RANDOM}${RANDOM}${RANDOM} |
| 124 | echo ${RANDOM_N} > /var/www/acme/${RANDOM_N} |
| 125 | if [[ "$(curl --insecure -${2} -L http://${1}/.well-known/acme-challenge/${RANDOM_N} --silent)" == "${RANDOM_N}" ]]; then |
| 126 | rm /var/www/acme/${RANDOM_N} |
| 127 | return 0 |
| 128 | else |
| 129 | rm /var/www/acme/${RANDOM_N} |
| 130 | return 1 |
| 131 | fi |
| 132 | } |