|
|
|
@ -256,7 +256,11 @@ |
|
|
|
# 2021-02-07 Allow -u --upgrade without any domain, so that one can only update the script (Benno-K)(2.34) |
|
|
|
# 2021-02-09 Prevent listing the complete file if version tag missing (#637)(softins) |
|
|
|
# 2021-02-12 Add PREFERRED_CHAIN |
|
|
|
# 2021-02-15 ADD ftp explicit SSL with curl for upload the challenge |
|
|
|
# 2021-02-15 ADD ftp explicit SSL with curl for upload the challenge (CoolMischa) |
|
|
|
# 2021-02-18 Add FULL_CHAIN_INCLUDE_ROOT |
|
|
|
# 2021-03-25 Fix DNS challenge completion check if CNAMEs on different NS are used (sideeffect42)(2.35) |
|
|
|
# 2021-05-08 Merge from tlhackque/getssl: GoDaddy, split-view, tempfile permissions fixes, --version(2.36) |
|
|
|
# 2021-05-26 Fix 'date -j' error on Mac fixed. Mac OS BigSur uses version: date (GNU coreutils) 8.32 (CooMischa) |
|
|
|
# ---------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
case :$SHELLOPTS: in |
|
|
|
@ -265,7 +269,7 @@ esac |
|
|
|
|
|
|
|
PROGNAME=${0##*/} |
|
|
|
PROGDIR="$(cd "$(dirname "$0")" || exit; pwd -P;)" |
|
|
|
VERSION="2.34" |
|
|
|
VERSION="2.36" |
|
|
|
|
|
|
|
# defaults |
|
|
|
ACCOUNT_KEY_LENGTH=4096 |
|
|
|
@ -283,6 +287,7 @@ DEFAULT_REVOKE_CA="https://acme-v02.api.letsencrypt.org" |
|
|
|
DOMAIN_KEY_LENGTH=4096 |
|
|
|
DUAL_RSA_ECDSA="false" |
|
|
|
FTP_OPTIONS="" |
|
|
|
FULL_CHAIN_INCLUDE_ROOT="false" |
|
|
|
GETSSL_IGNORE_CP_PRESERVE="false" |
|
|
|
HTTP_TOKEN_CHECK_WAIT=0 |
|
|
|
IGNORE_DIRECTORY_DOMAIN="false" |
|
|
|
@ -300,12 +305,13 @@ OCSP_MUST_STAPLE="false" |
|
|
|
TEMP_UPGRADE_FILE="" |
|
|
|
TOKEN_USER_ID="" |
|
|
|
USE_SINGLE_ACL="false" |
|
|
|
WORKING_DIR_CANDIDATES=("/etc/getssl/" "${PROGDIR}/conf" "${PROGDIR}/.getssl" "${HOME}/.getssl") |
|
|
|
WORKING_DIR_CANDIDATES=("/etc/getssl" "${PROGDIR}/conf" "${PROGDIR}/.getssl" "${HOME}/.getssl") |
|
|
|
|
|
|
|
# Variables used when validating using a DNS entry |
|
|
|
VALIDATE_VIA_DNS="" # Set this to "true" to enable DNS validation |
|
|
|
AUTH_DNS_SERVER="" # Use this DNS server to check the challenge token has been set |
|
|
|
PUBLIC_DNS_SERVER="" # Use this DNS server to find the authoritative DNS servers for the domain |
|
|
|
export AUTH_DNS_SERVER="" # Use this DNS server to check the challenge token has been set |
|
|
|
export DNS_CHECK_OPTIONS="" # Options (such as TSIG file) required by DNS_CHECK_FUNC |
|
|
|
export PUBLIC_DNS_SERVER="" # Use this DNS server to find the authoritative DNS servers for the domain |
|
|
|
CHECK_ALL_AUTH_DNS="false" # Check the challenge token has been set on all authoritative DNS servers |
|
|
|
CHECK_PUBLIC_DNS_SERVER="true" # Check the public DNS server as well as the authoritative DNS servers |
|
|
|
DNS_ADD_COMMAND="" # Use this command/script to add the challenge token to the DNS entries for the domain |
|
|
|
@ -333,7 +339,7 @@ _UPGRADE_CHECK=1 |
|
|
|
_USE_DEBUG=0 |
|
|
|
_ONLY_CHECK_CONFIG=0 |
|
|
|
config_errors="false" |
|
|
|
LANG=C |
|
|
|
export LANG=C |
|
|
|
API=1 |
|
|
|
|
|
|
|
# store copy of original command in case of upgrading script and re-running |
|
|
|
@ -516,48 +522,42 @@ check_challenge_completion() { # checks with the ACME server if our challenge is |
|
|
|
} |
|
|
|
|
|
|
|
check_challenge_completion_dns() { # perform validation via DNS challenge |
|
|
|
token=$1 |
|
|
|
uri=$2 |
|
|
|
keyauthorization=$3 |
|
|
|
d=$4 |
|
|
|
primary_ns=$5 |
|
|
|
auth_key=$6 |
|
|
|
|
|
|
|
# Always use lowercase domain name when querying DNS servers |
|
|
|
# shellcheck disable=SC2018,SC2019 |
|
|
|
lower_d=$(echo "${d##\*.}" | tr A-Z a-z) |
|
|
|
d=${1} |
|
|
|
rr=${2} |
|
|
|
primary_ns=${3} |
|
|
|
auth_key=${4} |
|
|
|
|
|
|
|
# check for token at public dns server, waiting for a valid response. |
|
|
|
for ns in $primary_ns; do |
|
|
|
info "checking dns at $ns" |
|
|
|
info "checking DNS at $ns" |
|
|
|
ntries=0 |
|
|
|
check_dns="fail" |
|
|
|
while [[ "$check_dns" == "fail" ]]; do |
|
|
|
if [[ "$os" == "cygwin" ]]; then |
|
|
|
check_result=$(nslookup -type=txt "_acme-challenge.${lower_d}" "${ns}" \ |
|
|
|
check_result=$(nslookup -type=txt "${rr}" "${ns}" \ |
|
|
|
| grep ^_acme -A2\ |
|
|
|
| grep '"'|awk -F'"' '{ print $2}') |
|
|
|
elif [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then |
|
|
|
debug "$DNS_CHECK_FUNC" TXT "_acme-challenge.${lower_d}" "@${ns}" |
|
|
|
check_result=$($DNS_CHECK_FUNC TXT "_acme-challenge.${lower_d}" "@${ns}" \ |
|
|
|
| grep -i "^_acme-challenge.${lower_d}" \ |
|
|
|
debug "$DNS_CHECK_FUNC" TXT "${rr}" "@${ns}" |
|
|
|
check_result=$($DNS_CHECK_FUNC TXT "${rr}" "@${ns}" \ |
|
|
|
| grep -i "^${rr}" \ |
|
|
|
| grep 'IN\WTXT'|awk -F'"' '{ print $2}') |
|
|
|
debug "check_result=$check_result" |
|
|
|
if [[ -z "$check_result" ]]; then |
|
|
|
debug "$DNS_CHECK_FUNC" ANY "_acme-challenge.${lower_d}" "@${ns}" |
|
|
|
check_result=$($DNS_CHECK_FUNC ANY "_acme-challenge.${lower_d}" "@${ns}" \ |
|
|
|
| grep -i "^_acme-challenge.${lower_d}" \ |
|
|
|
debug "$DNS_CHECK_FUNC" ANY "${rr}" "@${ns}" |
|
|
|
check_result=$($DNS_CHECK_FUNC ANY "${rr}" "@${ns}" \ |
|
|
|
| grep -i "^${rr}" \ |
|
|
|
| grep 'IN\WTXT'|awk -F'"' '{ print $2}') |
|
|
|
debug "check_result=$check_result" |
|
|
|
fi |
|
|
|
elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then |
|
|
|
check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${lower_d}" "${ns}" \ |
|
|
|
check_result=$($DNS_CHECK_FUNC -t TXT "${rr}" "${ns}" \ |
|
|
|
| grep 'descriptive text'|awk -F'"' '{ print $2}') |
|
|
|
else |
|
|
|
check_result=$(nslookup -type=txt "_acme-challenge.${lower_d}" "${ns}" \ |
|
|
|
check_result=$(nslookup -type=txt "${rr}" "${ns}" \ |
|
|
|
| grep 'text ='|awk -F'"' '{ print $2}') |
|
|
|
if [[ -z "$check_result" ]]; then |
|
|
|
check_result=$(nslookup -type=any "_acme-challenge.${lower_d}" "${ns}" \ |
|
|
|
check_result=$(nslookup -type=any "${rr}" "${ns}" \ |
|
|
|
| grep 'text ='|awk -F'"' '{ print $2}') |
|
|
|
fi |
|
|
|
fi |
|
|
|
@ -571,22 +571,20 @@ check_challenge_completion_dns() { # perform validation via DNS challenge |
|
|
|
ntries=$(( ntries + 1 )) |
|
|
|
|
|
|
|
if [[ $DNS_WAIT_RETRY_ADD == "true" && $(( ntries % 10 )) == 0 ]]; then |
|
|
|
debug "Retrying adding dns via command: $DNS_ADD_COMMAND $lower_d $auth_key" |
|
|
|
test_output "Retrying adding dns via command: $DNS_ADD_COMMAND" |
|
|
|
eval "$DNS_DEL_COMMAND" "$lower_d" "$auth_key" |
|
|
|
if ! eval "$DNS_ADD_COMMAND" "$lower_d" "$auth_key" ; then |
|
|
|
error_exit "DNS_ADD_COMMAND failed for domain $d" |
|
|
|
fi |
|
|
|
|
|
|
|
test_output "Deleting DNS RR via command: ${DNS_DEL_COMMAND}" |
|
|
|
del_dns_rr "${d}" "${auth_key}" |
|
|
|
test_output "Retrying adding DNS via command: ${DNS_ADD_COMMAND}" |
|
|
|
add_dns_rr "${d}" "${auth_key}" \ |
|
|
|
|| error_exit "DNS_ADD_COMMAND failed for domain ${d}" |
|
|
|
fi |
|
|
|
info "checking DNS at ${ns} for ${lower_d}. Attempt $ntries/${DNS_WAIT_COUNT} gave wrong result, "\ |
|
|
|
info "checking DNS at ${ns} for ${rr}. Attempt $ntries/${DNS_WAIT_COUNT} gave wrong result, "\ |
|
|
|
"waiting $DNS_WAIT secs before checking again" |
|
|
|
sleep $DNS_WAIT |
|
|
|
else |
|
|
|
debug "dns check failed - removing existing value" |
|
|
|
eval "$DNS_DEL_COMMAND" "$lower_d" "$auth_key" |
|
|
|
del_dns_rr "${d}" "${auth_key}" |
|
|
|
|
|
|
|
error_exit "checking _acme-challenge.${lower_d} gave $check_result not $auth_key" |
|
|
|
error_exit "checking ${rr} gave $check_result not $auth_key" |
|
|
|
fi |
|
|
|
fi |
|
|
|
done |
|
|
|
@ -596,13 +594,6 @@ check_challenge_completion_dns() { # perform validation via DNS challenge |
|
|
|
info "sleeping $DNS_EXTRA_WAIT seconds before asking the ACME server to check the dns" |
|
|
|
sleep "$DNS_EXTRA_WAIT" |
|
|
|
fi |
|
|
|
|
|
|
|
check_challenge_completion "$uri" "$d" "$keyauthorization" |
|
|
|
|
|
|
|
debug "remove DNS entry" |
|
|
|
# shellcheck disable=SC2018,SC2019 |
|
|
|
lower_d=$(echo "${d##\*.}" | tr A-Z a-z) |
|
|
|
eval "$DNS_DEL_COMMAND" "$lower_d" "$auth_key" |
|
|
|
} |
|
|
|
# end of ... perform validation if via DNS challenge |
|
|
|
|
|
|
|
@ -625,7 +616,7 @@ check_config() { # check the config files for all obvious errors |
|
|
|
rsa|prime256v1|secp384r1|secp521r1) |
|
|
|
debug "checked PRIVATE_KEY_ALG " ;; |
|
|
|
*) |
|
|
|
info "${DOMAIN}: invalid PRIVATE_KEY_ALG - $PRIVATE_KEY_ALG" |
|
|
|
info "${DOMAIN}: invalid PRIVATE_KEY_ALG - '$PRIVATE_KEY_ALG'" |
|
|
|
config_errors=true ;; |
|
|
|
esac |
|
|
|
if [[ "$DUAL_RSA_ECDSA" == "true" ]] && [[ "$PRIVATE_KEY_ALG" == "rsa" ]]; then |
|
|
|
@ -681,32 +672,32 @@ check_config() { # check the config files for all obvious errors |
|
|
|
config_errors=true |
|
|
|
fi |
|
|
|
|
|
|
|
# check domain exists using all DNS utilities |
|
|
|
# check domain exists using all DNS utilities. DNS_CHECK_OPTIONS may bind IP address or provide TSIG |
|
|
|
found_ip=false |
|
|
|
if [[ -n "$HAS_DIG_OR_DRILL" ]]; then |
|
|
|
debug "DNS lookup using $HAS_DIG_OR_DRILL ${d}" |
|
|
|
if [[ "$($HAS_DIG_OR_DRILL -t SOA "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
debug "DNS lookup using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS ${d}" |
|
|
|
if [[ "$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -t SOA "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
found_ip=true |
|
|
|
elif [[ "$($HAS_DIG_OR_DRILL -t A "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
elif [[ "$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -t A "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
found_ip=true |
|
|
|
elif [[ "$($HAS_DIG_OR_DRILL -t AAAA "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
elif [[ "$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -t AAAA "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
found_ip=true |
|
|
|
fi |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ "$HAS_HOST" == "true" ]]; then |
|
|
|
debug "DNS lookup using host ${d}" |
|
|
|
if [[ "$(host "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
if [[ "$(host $DNS_CHECK_OPTIONS "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then |
|
|
|
found_ip=true |
|
|
|
fi |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ "$HAS_NSLOOKUP" == "true" ]]; then |
|
|
|
debug "DNS lookup using nslookup -query AAAA ${d}" |
|
|
|
if [[ "$(nslookup -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then |
|
|
|
if [[ "$(nslookup $DNS_CHECK_OPTIONS -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then |
|
|
|
debug "found IPv6 record for ${d}" |
|
|
|
found_ip=true |
|
|
|
elif [[ "$(nslookup "${d}"| grep -c ^Name)" -ge 1 ]]; then |
|
|
|
elif [[ "$(nslookup $DNS_CHECK_OPTIONS "${d}"| grep -c ^Name)" -ge 1 ]]; then |
|
|
|
debug "found IPv4 record for ${d}" |
|
|
|
found_ip=true |
|
|
|
fi |
|
|
|
@ -805,7 +796,7 @@ clean_up() { # Perform pre-exit housekeeping |
|
|
|
# shellcheck source=/dev/null |
|
|
|
. "$dnsfile" |
|
|
|
debug "attempting to clean up DNS entry for $d" |
|
|
|
eval "$DNS_DEL_COMMAND" "${d##\*.}" "$auth_key" |
|
|
|
del_dns_rr "${d}" "${auth_key}" |
|
|
|
done |
|
|
|
shopt -u nullglob |
|
|
|
fi |
|
|
|
@ -1079,8 +1070,9 @@ create_order() { |
|
|
|
date_epoc() { # convert the date into epoch time |
|
|
|
if [[ "$os" == "bsd" ]]; then |
|
|
|
date -j -f "%b %d %T %Y %Z" "$1" +%s |
|
|
|
elif [[ "$os" == "mac" ]]; then |
|
|
|
date -j -f "%b %d %T %Y %Z" "$1" +%s |
|
|
|
# elif [[ "$os" == "mac" ]]; then |
|
|
|
# date -j -f "%b %d %T %Y %Z" "$1" +%s |
|
|
|
# date -d "$1" +%s |
|
|
|
elif [[ "$os" == "busybox" ]]; then |
|
|
|
de_ld=$(echo "$1" | awk '{print $1 " " $2 " " $3 " " $4}') |
|
|
|
date -D "%b %d %T %Y" -d "$de_ld" +%s |
|
|
|
@ -1091,10 +1083,11 @@ date_epoc() { # convert the date into epoch time |
|
|
|
} |
|
|
|
|
|
|
|
date_fmt() { # format date from epoc time to YYYY-MM-DD |
|
|
|
if [[ "$os" == "bsd" ]]; then #uses older style date function. |
|
|
|
date -j -f "%s" "$1" +%F |
|
|
|
elif [[ "$os" == "mac" ]]; then # macOS uses older BSD style date. |
|
|
|
if [[ "$os" == "bsd" ]]; then # uses older style date function. |
|
|
|
date -j -f "%s" "$1" +%F |
|
|
|
# elif [[ "$os" == "mac" ]]; then # macOS uses older BSD style date. |
|
|
|
# date -j -f "%s" "$1" +%F |
|
|
|
# date -d "@$1" +%F |
|
|
|
else |
|
|
|
date -d "@$1" +%F |
|
|
|
fi |
|
|
|
@ -1169,6 +1162,26 @@ find_ftp_command() { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
add_dns_rr() { |
|
|
|
d=${1} |
|
|
|
auth_key=${2} |
|
|
|
|
|
|
|
# shellcheck disable=SC2018,SC2019 |
|
|
|
lower_d=$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z') |
|
|
|
debug "adding DNS RR via command: ${DNS_ADD_COMMAND} ${lower_d} ${auth_key}" |
|
|
|
eval "${DNS_ADD_COMMAND}" "${lower_d}" "${auth_key}" |
|
|
|
} |
|
|
|
|
|
|
|
del_dns_rr() { |
|
|
|
d=${1} |
|
|
|
auth_key=${2} |
|
|
|
|
|
|
|
# shellcheck disable=SC2018,SC2019 |
|
|
|
lower_d=$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z') |
|
|
|
debug "removing DNS RR via command: ${DNS_DEL_COMMAND} ${lower_d} ${auth_key}" |
|
|
|
eval "${DNS_DEL_COMMAND}" "${lower_d}" "${auth_key}" |
|
|
|
} |
|
|
|
|
|
|
|
fulfill_challenges() { |
|
|
|
dn=0 |
|
|
|
for d in "${alldomains[@]}"; do |
|
|
|
@ -1234,16 +1247,24 @@ for d in "${alldomains[@]}"; do |
|
|
|
| sed -e 's:=*$::g' -e 'y:+/:-_:') |
|
|
|
debug auth_key "$auth_key" |
|
|
|
|
|
|
|
add_dns_rr "${d}" "${auth_key}" \ |
|
|
|
|| error_exit "DNS_ADD_COMMAND failed for domain $d" |
|
|
|
|
|
|
|
# shellcheck disable=SC2018,SC2019 |
|
|
|
lower_d=$(echo "${d##\*.}" | tr A-Z a-z) |
|
|
|
debug "adding dns via command: $DNS_ADD_COMMAND $lower_d $auth_key" |
|
|
|
if ! eval "$DNS_ADD_COMMAND" "$lower_d" "$auth_key" ; then |
|
|
|
error_exit "DNS_ADD_COMMAND failed for domain $d" |
|
|
|
fi |
|
|
|
rr="_acme-challenge.$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z')" |
|
|
|
|
|
|
|
# find a primary / authoritative DNS server for the domain |
|
|
|
if [[ -z "$AUTH_DNS_SERVER" ]]; then |
|
|
|
get_auth_dns "$d" |
|
|
|
# Find authorative dns server for _acme-challenge.{domain} (for CNAMES/acme-dns) |
|
|
|
get_auth_dns "${rr}" |
|
|
|
if test -n "${cname}"; then |
|
|
|
rr=${cname} |
|
|
|
fi |
|
|
|
|
|
|
|
# If no authorative dns server found, try again for {domain} |
|
|
|
if [[ -z "$primary_ns" ]]; then |
|
|
|
get_auth_dns "$d" |
|
|
|
fi |
|
|
|
elif [[ "$CHECK_PUBLIC_DNS_SERVER" == "true" ]]; then |
|
|
|
primary_ns="$AUTH_DNS_SERVER $PUBLIC_DNS_SERVER" |
|
|
|
else |
|
|
|
@ -1251,7 +1272,13 @@ for d in "${alldomains[@]}"; do |
|
|
|
fi |
|
|
|
debug set primary_ns = "$primary_ns" |
|
|
|
|
|
|
|
check_challenge_completion_dns "${token}" "${uri}" "${keyauthorization}" "${d}" "${primary_ns}" "${auth_key}" |
|
|
|
# internal check |
|
|
|
check_challenge_completion_dns "${d}" "${rr}" "${primary_ns}" "${auth_key}" |
|
|
|
|
|
|
|
# let Let's Encrypt check |
|
|
|
check_challenge_completion "${uri}" "${d}" "${keyauthorization}" |
|
|
|
|
|
|
|
del_dns_rr "${d}" "${auth_key}" |
|
|
|
else # set up the correct http token for verification |
|
|
|
if [[ $API -eq 1 ]]; then |
|
|
|
# get the token from the http component |
|
|
|
@ -1364,45 +1391,45 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n |
|
|
|
gad_s="@$gad_s" |
|
|
|
fi |
|
|
|
|
|
|
|
# Use SOA +trace to find the name server |
|
|
|
if [[ $_TEST_SKIP_SOA_CALL == 0 ]]; then |
|
|
|
if [[ "$HAS_DIG_OR_DRILL" == "drill" ]]; then |
|
|
|
debug Using "$HAS_DIG_OR_DRILL -T $gad_d $gad_s" to find primary nameserver |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL SOA" |
|
|
|
res=$($HAS_DIG_OR_DRILL -T SOA "$gad_d" $gad_s 2>/dev/null | grep "IN\WNS\W") |
|
|
|
else |
|
|
|
debug Using "$HAS_DIG_OR_DRILL SOA +trace +nocomments $gad_d $gad_s" to find primary nameserver |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL SOA" |
|
|
|
res=$($HAS_DIG_OR_DRILL SOA +trace +nocomments "$gad_d" $gad_s 2>/dev/null | grep "IN\WNS\W") |
|
|
|
fi |
|
|
|
fi |
|
|
|
# Check if domain is a CNAME, first |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME" |
|
|
|
|
|
|
|
# Check if domain is a CNAME |
|
|
|
if [[ -z "$res" ]]; then |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL CNAME" |
|
|
|
# Two options here; either dig CNAME will return the CNAME and the NS or just the CNAME |
|
|
|
debug Checking for CNAME using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME $gad_d $gad_s" |
|
|
|
res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME "$gad_d" $gad_s| grep "^$gad_d") |
|
|
|
cname=$(echo "$res"| awk '$4 ~ "CNAME" {print $5}' |sed 's/\.$//g') |
|
|
|
|
|
|
|
# Two options here; either dig CNAME will return the CNAME and the NS or just the CNAME |
|
|
|
debug Checking for CNAME using "$HAS_DIG_OR_DRILL CNAME $gad_d $gad_s" |
|
|
|
res=$($HAS_DIG_OR_DRILL CNAME "$gad_d" $gad_s| grep "^$gad_d") |
|
|
|
cname=$(echo "$res"| awk '$4 ~ "CNAME" {print $5}' |sed 's/\.$//g') |
|
|
|
if [[ $_TEST_SKIP_CNAME_CALL == 0 ]]; then |
|
|
|
debug Checking if CNAME result contains NS records |
|
|
|
res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME "$gad_d" $gad_s| grep -E "IN\W(NS|SOA)\W") |
|
|
|
else |
|
|
|
res= |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ $_TEST_SKIP_CNAME_CALL == 0 ]]; then |
|
|
|
debug Checking if CNAME result contains NS records |
|
|
|
res=$($HAS_DIG_OR_DRILL CNAME "$gad_d" $gad_s| grep -E "IN\W(NS|SOA)\W") |
|
|
|
else |
|
|
|
res="" |
|
|
|
fi |
|
|
|
if [[ -n "${cname}" ]]; then |
|
|
|
# domain is a CNAME: resolve it and continue with that |
|
|
|
debug Domain is a CNAME, actual domain is "$cname" |
|
|
|
gad_d=${cname} |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ -n "$cname" ]]; then # domain is a CNAME so get main domain |
|
|
|
debug Domain is a CNAME, actual domain is "$cname" |
|
|
|
# Use SOA +trace to find the name server |
|
|
|
if [[ -z "$res" ]] && [[ $_TEST_SKIP_SOA_CALL == 0 ]]; then |
|
|
|
if [[ "$HAS_DIG_OR_DRILL" == "drill" ]]; then |
|
|
|
debug Using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -T $gad_d $gad_s" to find primary nameserver |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS SOA" |
|
|
|
res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -T SOA "$gad_d" $gad_s 2>/dev/null | grep "IN\WNS\W") |
|
|
|
else |
|
|
|
debug Using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS SOA +trace +nocomments $gad_d $gad_s" to find primary nameserver |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS SOA" |
|
|
|
res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS SOA +trace +nocomments "$gad_d" $gad_s 2>/dev/null | grep "IN\WNS\W") |
|
|
|
fi |
|
|
|
fi |
|
|
|
|
|
|
|
# Query for NS records |
|
|
|
if [[ -z "$res" ]]; then |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL NS" |
|
|
|
debug Using "$HAS_DIG_OR_DRILL NS $gad_d $gad_s" to find primary nameserver |
|
|
|
res=$($HAS_DIG_OR_DRILL NS "$gad_d" $gad_s | grep -E "IN\W(NS|SOA)\W") |
|
|
|
test_output "Using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS NS" |
|
|
|
debug Using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS NS $gad_d $gad_s" to find primary nameserver |
|
|
|
res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS NS "$gad_d" $gad_s | grep -E "IN\W(NS|SOA)\W") |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ -n "$res" ]]; then |
|
|
|
@ -1442,10 +1469,10 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n |
|
|
|
gad_d="$orig_gad_d" |
|
|
|
debug Using "host -t NS" to find primary name server for "$gad_d" |
|
|
|
if [[ -z "$gad_s" ]]; then |
|
|
|
res=$(host -t NS "$gad_d"| grep "name server") |
|
|
|
res=$(host $DNS_CHECK_OPTIONS -t NS "$gad_d"| grep "name server") |
|
|
|
else |
|
|
|
# shellcheck disable=SC2086 |
|
|
|
res=$(host -t NS "$gad_d" $gad_s| grep "name server") |
|
|
|
res=$(host $DNS_CHECK_OPTIONS -t NS "$gad_d" $gad_s| grep "name server") |
|
|
|
fi |
|
|
|
if [[ -n "$res" ]]; then |
|
|
|
all_auth_dns_servers=$(echo "$res" | awk '{print $4}' | sed 's/\.$//g'|tr '\n' ' ') |
|
|
|
@ -1465,9 +1492,9 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n |
|
|
|
|
|
|
|
if [[ "$HAS_NSLOOKUP" == "true" ]]; then |
|
|
|
gad_d="$orig_gad_d" |
|
|
|
debug Using "nslookup -debug -type=soa -type=ns $gad_d $gad_s" to find primary name server |
|
|
|
debug Using "nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns $gad_d $gad_s" to find primary name server |
|
|
|
# shellcheck disable=SC2086 |
|
|
|
res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) |
|
|
|
res=$(nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns "$gad_d" ${gad_s}) |
|
|
|
|
|
|
|
if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then |
|
|
|
# this is a Non-authoritative server, need to check for an authoritative one. |
|
|
|
@ -1483,7 +1510,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n |
|
|
|
fi |
|
|
|
|
|
|
|
# shellcheck disable=SC2086 |
|
|
|
res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) |
|
|
|
res=$(nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns "$gad_d" ${gad_s}) |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then |
|
|
|
@ -1499,7 +1526,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n |
|
|
|
|
|
|
|
# shellcheck disable=SC2086 |
|
|
|
# not quoting gad_s fixes the nslookup: couldn't get address for '': not found warning (#332) |
|
|
|
all_auth_dns_servers=$(nslookup -debug -type=soa -type=ns "$gad_d" $gad_s \ |
|
|
|
all_auth_dns_servers=$(nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns "$gad_d" $gad_s \ |
|
|
|
| awk '$1 ~ "nameserver" {print $3}' \ |
|
|
|
| sed 's/\.$//g'| tr '\n' ' ') |
|
|
|
|
|
|
|
@ -1527,6 +1554,7 @@ get_certificate() { # get certificate for csr, if all domains validated. |
|
|
|
gc_fullchain=$4 # The filename for the fullchain |
|
|
|
|
|
|
|
der=$(openssl req -in "$gc_csr" -outform DER | urlbase64) |
|
|
|
|
|
|
|
if [[ $API -eq 1 ]]; then |
|
|
|
send_signed_request "$URL_new_cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" |
|
|
|
# convert certificate information into correct format and save to file. |
|
|
|
@ -1580,7 +1608,7 @@ get_certificate() { # get certificate for csr, if all domains validated. |
|
|
|
cp "$gc_fullchain" "$cert_to_check" |
|
|
|
i=0 |
|
|
|
while [[ $i -le ${#alternate_links[@]} ]]; do |
|
|
|
cert_issuer=$(openssl crl2pkcs7 -nocrl -certfile "$cert_to_check" | openssl pkcs7 -print_certs -text -noout | grep 'Issuer:' | tail -1 | cut -d= -f2) |
|
|
|
cert_issuer=$(openssl crl2pkcs7 -nocrl -certfile "$cert_to_check" | openssl pkcs7 -print_certs -text -noout | grep 'Issuer:' | tail -1 | awk -F"CN=" '{ print $2 }') |
|
|
|
debug Certificate issued by "$cert_issuer" |
|
|
|
if [[ $cert_issuer = *${PREFERRED_CHAIN}* ]]; then |
|
|
|
debug "Found required certificate" |
|
|
|
@ -1598,7 +1626,27 @@ get_certificate() { # get certificate for csr, if all domains validated. |
|
|
|
# tidy up |
|
|
|
rm -f "$cert_to_check" |
|
|
|
fi |
|
|
|
|
|
|
|
awk -v CERT_FILE="$gc_certfile" -v CA_CERT="$gc_cafile" 'BEGIN {outfile=CERT_FILE} split_after==1 {outfile=CA_CERT;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > outfile}' "$gc_fullchain" |
|
|
|
if [[ "$FULL_CHAIN_INCLUDE_ROOT" = "true" ]]; then |
|
|
|
# Some of the code below was copied from zakjan/cert-chain-resolver |
|
|
|
|
|
|
|
# Download the certificate for the issuer using the "CA Issuers" attribute from the AIA x509 extension |
|
|
|
issuer_url=$(openssl x509 -inform pem -noout -text -in "$gc_certfile" | awk 'BEGIN {FS="CA Issuers - URI:"} NF==2 {print $2; exit}') |
|
|
|
debug Issuer for "$gc_certfile" is "$issuer_url" |
|
|
|
|
|
|
|
# Keep downloading issuer certficates until we find the root certificate (which doesn't have a "CA Issuers" attribure) |
|
|
|
cp "$gc_certfile" "$gc_fullchain" |
|
|
|
while [[ -n "$issuer_url" ]]; do |
|
|
|
debug Fetching certificate issuer from "$issuer_url" |
|
|
|
issuer_cert=$(curl --user-agent "$CURL_USERAGENT" --silent "$issuer_url" | openssl x509 -inform der -outform pem) |
|
|
|
debug Fetched issuer certificate "$(echo "$issuer_cert" | openssl x509 -inform pem -noout -text | awk 'BEGIN {FS="Subject: "} NF==2 {print $2; exit}')" |
|
|
|
echo "$issuer_cert" >> "$gc_fullchain" |
|
|
|
|
|
|
|
# get issuer for the certificate that's just been downloaded |
|
|
|
issuer_url=$(echo "$issuer_cert" | openssl x509 -inform pem -noout -text | awk 'BEGIN {FS="CA Issuers - URI:"} NF==2 {print $2; exit}') |
|
|
|
done |
|
|
|
fi |
|
|
|
info "Certificate saved in $gc_certfile" |
|
|
|
fi |
|
|
|
} |
|
|
|
@ -1715,6 +1763,7 @@ help_message() { # print out the help message |
|
|
|
-u, --upgrade Upgrade getssl if a more recent version is available - can be used with or without domain(s) |
|
|
|
-k, --keep "#" Maximum number of old getssl versions to keep when upgrading |
|
|
|
-U, --nocheck Do not check if a more recent version is available |
|
|
|
-v --version Display current version of $PROGNAME |
|
|
|
-w working_dir "Working directory" |
|
|
|
--preferred-chain "chain" Use an alternate chain for the certificate |
|
|
|
|
|
|
|
@ -2051,16 +2100,18 @@ revoke_certificate() { # revoke a certificate |
|
|
|
} |
|
|
|
|
|
|
|
requires() { # check if required function is available |
|
|
|
args=("${@}") |
|
|
|
lastarg=${args[${#args[@]}-1]} |
|
|
|
if [[ "$#" -gt 1 ]]; then # if more than 1 value, check list |
|
|
|
for i in "$@"; do |
|
|
|
if [[ "$i" == "${!#}" ]]; then # if on last variable then exit as not found |
|
|
|
if [[ "$i" == "$lastarg" ]]; then # if on last variable then exit as not found |
|
|
|
error_exit "this script requires one of: ${*:1:$(($#-1))}" |
|
|
|
fi |
|
|
|
res=$(command -v "$i" 2>/dev/null) |
|
|
|
debug "checking for $i ... $res" |
|
|
|
if [[ -n "$res" ]]; then # if function found, then set variable to function and return |
|
|
|
debug "function $i found at $res - setting ${!#} to $i" |
|
|
|
eval "${!#}=\$i" |
|
|
|
debug "function $i found at $res - setting ${lastarg} to $i" |
|
|
|
eval "${lastarg}=\$i" |
|
|
|
return |
|
|
|
fi |
|
|
|
done |
|
|
|
@ -2377,10 +2428,14 @@ write_domain_template() { # write out a template file for a domain. |
|
|
|
# Set USE_SINGLE_ACL="true" to use a single ACL for all checks |
|
|
|
#USE_SINGLE_ACL="false" |
|
|
|
|
|
|
|
# Preferred Chain - use an different certificate root from the default |
|
|
|
# Staging options are: "Fake LE Root X1" and "Fake LE Root X2" |
|
|
|
# Production options are: "ISRG Root X1" and "ISRG Root X2" |
|
|
|
#PREFERRED_CHAIN="" |
|
|
|
# Preferred Chain - use an different certificate root from the default |
|
|
|
# This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters |
|
|
|
# Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" |
|
|
|
# Production options are: "ISRG Root X1" and "ISRG Root X2" |
|
|
|
#PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" |
|
|
|
|
|
|
|
# Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) |
|
|
|
#FULL_CHAIN_INCLUDE_ROOT="true" |
|
|
|
|
|
|
|
# Location for all your certs, these can either be on the server (full path name) |
|
|
|
# or using ssh /sftp as for the ACL |
|
|
|
@ -2436,10 +2491,14 @@ write_getssl_template() { # write out the main template file |
|
|
|
PRIVATE_KEY_ALG="rsa" |
|
|
|
#REUSE_PRIVATE_KEY="true" |
|
|
|
|
|
|
|
# Preferred Chain - use an different certificate root from the default |
|
|
|
# Staging options are: "Fake LE Root X1" and "Fake LE Root X2" |
|
|
|
# Production options are: "ISRG Root X1" and "ISRG Root X2" |
|
|
|
#PREFERRED_CHAIN="" |
|
|
|
# Preferred Chain - use an different certificate root from the default |
|
|
|
# This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters |
|
|
|
# Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" |
|
|
|
# Production options are: "ISRG Root X1" and "ISRG Root X2" |
|
|
|
#PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" |
|
|
|
|
|
|
|
# Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) |
|
|
|
#FULL_CHAIN_INCLUDE_ROOT="true" |
|
|
|
|
|
|
|
# The command needed to reload apache / nginx or whatever you use. |
|
|
|
# Several (ssh) commands may be given using a bash array: |
|
|
|
@ -2463,6 +2522,19 @@ write_getssl_template() { # write out the main template file |
|
|
|
#VALIDATE_VIA_DNS="true" |
|
|
|
#DNS_ADD_COMMAND= |
|
|
|
#DNS_DEL_COMMAND= |
|
|
|
|
|
|
|
# Unusual configurations (especially split views) may require these. |
|
|
|
# If you have a mixture, these can go in the per-domain getssl.cfg. |
|
|
|
# |
|
|
|
# If you must use an external DNS Server (e.g. due to split views) |
|
|
|
# Specify it here. Otherwise, the default is to find the zone master. |
|
|
|
# The default will usually work. |
|
|
|
# PUBLIC_DNS_SERVER="8.8.8.8" |
|
|
|
|
|
|
|
# If getssl is unable to determine the authoritative nameserver for a domain |
|
|
|
# it will as you to enter AUTH_DNS_SERVER. This is a server that |
|
|
|
# can answer queries for the zone - a master or a slave, not a recursive server. |
|
|
|
# AUTH_DNS_SERVER="10.0.0.14" |
|
|
|
_EOF_getssl_ |
|
|
|
} |
|
|
|
|
|
|
|
@ -2485,6 +2557,8 @@ while [[ -n ${1+defined} ]]; do |
|
|
|
case $1 in |
|
|
|
-h | --help) |
|
|
|
help_message; graceful_exit ;; |
|
|
|
-v | --version) |
|
|
|
echo "$PROGNAME V$VERSION"; graceful_exit ;; |
|
|
|
-d | --debug) |
|
|
|
_USE_DEBUG=1 ;; |
|
|
|
-c | --create) |
|
|
|
@ -2629,6 +2703,12 @@ if [[ -s "$WORKING_DIR/getssl.cfg" ]]; then |
|
|
|
. "$WORKING_DIR/getssl.cfg" |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ -n "$DNS_CHECK_FUNC" ]]; then |
|
|
|
requires "${DNS_CHECK_FUNC}" |
|
|
|
else |
|
|
|
requires nslookup drill dig host DNS_CHECK_FUNC |
|
|
|
fi |
|
|
|
|
|
|
|
# Define defaults for variables not set in the main config. |
|
|
|
ACCOUNT_KEY="${ACCOUNT_KEY:=$WORKING_DIR/account.key}" |
|
|
|
DOMAIN_STORAGE="${DOMAIN_STORAGE:=$WORKING_DIR}" |
|
|
|
@ -2719,6 +2799,7 @@ if [[ ${_CREATE_CONFIG} -eq 1 ]]; then |
|
|
|
info "Adding SANS=$EX_SANS from certificate installed on ${DOMAIN##\*.} to new configuration file" |
|
|
|
fi |
|
|
|
write_domain_template "$DOMAIN_DIR/getssl.cfg" |
|
|
|
info "created domain config file in $DOMAIN_DIR/getssl.cfg" |
|
|
|
fi |
|
|
|
TEMP_DIR="$DOMAIN_DIR/tmp" |
|
|
|
# end of "-c|--create" option, so exit |
|
|
|
@ -2825,7 +2906,7 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then |
|
|
|
else |
|
|
|
# check if the certificate is for the right domain |
|
|
|
EX_CERT_DOMAIN=$(echo "$EX_CERT" | openssl x509 -text \ |
|
|
|
| sed -n -e 's/^ *Subject: .* CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ |
|
|
|
| sed -n -e 's/^ *Subject: .*CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ |
|
|
|
| sort -u | grep "^$DOMAIN\$") |
|
|
|
if [[ "$EX_CERT_DOMAIN" == "$DOMAIN" ]]; then |
|
|
|
# check renew-date on ex_cert and compare to local ( if local exists) |
|
|
|
@ -2858,18 +2939,26 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then |
|
|
|
copy_file_to_location "full pem" \ |
|
|
|
"$TEMP_DIR/${DOMAIN}_chain.pem" \ |
|
|
|
"$DOMAIN_CHAIN_LOCATION" |
|
|
|
umask 077 |
|
|
|
cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" |
|
|
|
umask "$ORIG_UMASK" |
|
|
|
copy_file_to_location "private key and domain cert pem" \ |
|
|
|
"$TEMP_DIR/${DOMAIN}_K_C.pem" \ |
|
|
|
"$DOMAIN_KEY_CERT_LOCATION" |
|
|
|
umask 077 |
|
|
|
cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" |
|
|
|
umask "$ORIG_UMASK" |
|
|
|
copy_file_to_location "full pem" \ |
|
|
|
"$TEMP_DIR/${DOMAIN}.pem" \ |
|
|
|
"$DOMAIN_PEM_LOCATION" |
|
|
|
reload_service |
|
|
|
fi |
|
|
|
else |
|
|
|
info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate" |
|
|
|
# Get the domain from the existing certificate for the error message |
|
|
|
EX_CERT_DOMAIN=$(echo "$EX_CERT" | openssl x509 -text \ |
|
|
|
| sed -n -e 's/^ *Subject: .*CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ |
|
|
|
| sort -u | head -1) |
|
|
|
info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate ($EX_CERT_DOMAIN != $real_d)" |
|
|
|
fi |
|
|
|
fi |
|
|
|
else |
|
|
|
|