214214# 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20)
215215# 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424)
216216# 2020-02-23 Add dig to config check for systems without drill (ubuntu)
217- # 2020-03-23 Fix staging server URL in domain template
217+ # 2020-03-11 Use dig +trace to find primary name server and improve dig parsing of CNAME
218+ # 2020-03-12 Fix bug with DNS validation and multiple domains (#524)
219+ # 2020-03-24 Find primary ns using all dns utils (dig, host, nslookup)
220+ # 2020-03-23 Fix staging server URL in domain template (2.21)
218221# ----------------------------------------------------------------------------------------
219222
220223PROGNAME=${0##*/ }
@@ -234,7 +237,7 @@ CSR_SUBJECT="/"
234237CURL_USERAGENT=" ${PROGNAME} /${VERSION} "
235238DEACTIVATE_AUTH=" false"
236239DEFAULT_REVOKE_CA=" https://acme-v02.api.letsencrypt.org"
237- DNS_EXTRA_WAIT=" "
240+ DNS_EXTRA_WAIT=60
238241DNS_WAIT=10
239242DOMAIN_KEY_LENGTH=4096
240243DUAL_RSA_ECDSA=" false"
@@ -750,13 +753,37 @@ create_order() {
750753 OrderLink=$( echo " $responseHeaders " | grep -i location | awk ' {print $2}' | tr -d ' \r\n ' )
751754 debug " Order link $OrderLink "
752755 FinalizeLink=$( json_get " $response " " finalize" )
753- dn=0
754- for d in $alldomains ; do
755- # get authorizations link
756- AuthLink[$dn ]=$( json_get " $response " " identifiers" " value" " $d " " authorizations" " x" )
757- debug " authorizations link for $d - ${AuthLink[$dn]} "
758- (( dn++ ))
759- done
756+
757+ if [[ $API -eq 1 ]]; then
758+ dn=0
759+ for d in $alldomains ; do
760+ # get authorizations link
761+ AuthLink[$dn ]=$( json_get " $response " " identifiers" " value" " $d " " authorizations" " x" )
762+ debug " authorizations link for $d - ${AuthLink[$dn]} "
763+ (( dn++ ))
764+ done
765+ else
766+ # Authorization links are unsorted, so fetch the authorization link, find the domain, save response in the correct array position
767+ AuthLinks=$( json_get " $response " " authorizations" )
768+ AuthLinkResponse=()
769+ AuthLinkResponseHeader=()
770+ for l in $AuthLinks ; do
771+ debug " Requesting authorizations link for $l "
772+ send_signed_request " $l " " "
773+ # Get domain from response
774+ authdomain=$( json_get " $response " " identifier" " value" )
775+ # find array position (This is O(n2) but that doubt we'll see performance issues)
776+ dn=0
777+ for d in $alldomains ; do
778+ if [ " $d " == " $authdomain " ]; then
779+ debug " Saving authorization response for $authdomain for domain alldomains[$dn ]"
780+ AuthLinkResponse[$dn ]=$response
781+ AuthLinkResponseHeader[$dn ]=$responseHeaders
782+ fi
783+ (( dn++ ))
784+ done
785+ done
786+ fi
760787}
761788
762789date_epoc () { # convert the date into epoch time
@@ -801,6 +828,29 @@ error_exit() { # give error message on error exit
801828 exit 1
802829}
803830
831+ find_dns_utils () {
832+ HAS_NSLOOKUP=false
833+ HAS_DIG_OR_DRILL=" "
834+ HAS_HOST=false
835+ if [[ -n " $( command -v nslookup) " ]]; then
836+ debug " HAS NSLOOKUP=true"
837+ HAS_NSLOOKUP=true
838+ fi
839+
840+ if [[ -n " $( command -v drill) " ]]; then
841+ debug " HAS DIG_OR_DRILL=drill"
842+ HAS_DIG_OR_DRILL=" drill"
843+ elif [[ -n " $( command -v dig) " ]]; then
844+ debug " HAS DIG_OR_DRILL=dig"
845+ HAS_DIG_OR_DRILL=" dig"
846+ fi
847+
848+ if [[ -n " $( command -v host) " ]]; then
849+ debug " HAS HOST=true"
850+ HAS_HOST=true
851+ fi
852+ }
853+
804854fulfill_challenges () {
805855dn=0
806856for d in $alldomains ; do
@@ -823,7 +873,9 @@ for d in $alldomains; do
823873 error_exit " new-authz error: $response "
824874 fi
825875 else
826- send_signed_request " ${AuthLink[$dn]} " " "
876+ response=${AuthLinkResponse[$dn]}
877+ responseHeaders=${AuthLinkResponseHeader[$dn]}
878+ response_status=$( json_get " $response " status)
827879 fi
828880
829881 if [[ $response_status == " valid" ]]; then
@@ -841,16 +893,14 @@ for d in $alldomains; do
841893 if [[ $VALIDATE_VIA_DNS == " true" ]]; then # set up the correct DNS token for verification
842894 if [[ $API -eq 1 ]]; then
843895 # get the dns component of the ACME response
844- # get the token from the dns component
896+ # get the token and uri from the dns component
845897 token=$( json_get " $response " " token" " dns-01" )
846- # get the uri from the dns component
847898 uri=$( json_get " $response " " uri" " dns-01" )
848899 debug uri " $uri "
849900 else # APIv2
850901 debug " authlink response = $response "
851- # get the token from the http -01 component
902+ # get the token and uri from the dns -01 component
852903 token=$( json_get " $response " " challenges" " type" " dns-01" " token" )
853- # get the uri from the http component
854904 uri=$( json_get " $response " " challenges" " type" " dns-01" " url" )
855905 debug uri " $uri "
856906 fi
@@ -901,7 +951,6 @@ for d in $alldomains; do
901951 uri=$( json_get " $response " " uri" " http-01" )
902952 debug uri " $uri "
903953 else # APIv2
904- send_signed_request " ${AuthLink[$dn]} " " "
905954 debug " authlink response = $response "
906955 # get the token from the http-01 component
907956 token=$( json_get " $response " " challenges" " type" " http-01" " token" )
@@ -998,8 +1047,9 @@ if [[ $VALIDATE_VIA_DNS == "true" ]]; then
9981047 | grep ^_acme -A2\
9991048 | grep ' "' | awk -F' "' ' { print $2}' )
10001049 elif [[ " $DNS_CHECK_FUNC " == " drill" ]] || [[ " $DNS_CHECK_FUNC " == " dig" ]]; then
1050+ debug " $DNS_CHECK_FUNC " TXT " _acme-challenge.${d} " " @${ns} "
10011051 check_result=$( $DNS_CHECK_FUNC TXT " _acme-challenge.${d} " " @${ns} " \
1002- | grep ' IN TXT ' | awk -F' "' ' { print $2}' )
1052+ | grep ' IN\WTXT ' | awk -F' "' ' { print $2}' )
10031053 elif [[ " $DNS_CHECK_FUNC " == " host" ]]; then
10041054 check_result=$( $DNS_CHECK_FUNC -t TXT " _acme-challenge.${d} " " ${ns} " \
10051055 | grep ' descriptive text' | awk -F' "' ' { print $2}' )
@@ -1054,10 +1104,11 @@ fi
10541104}
10551105
10561106get_auth_dns () { # get the authoritative dns server for a domain (sets primary_ns )
1057- gad_d =" $1 " # domain name
1107+ orig_gad_d =" $1 " # domain name
10581108 gad_s=" $PUBLIC_DNS_SERVER " # start with PUBLIC_DNS_SERVER
10591109
10601110 if [[ " $os " == " cygwin" ]]; then
1111+ gad_d=" $orig_gad_d "
10611112 all_auth_dns_servers=$( nslookup -type=soa " ${d} " ${PUBLIC_DNS_SERVER} 2> /dev/null \
10621113 | grep " primary name server" \
10631114 | awk ' {print $NF}' )
@@ -1068,85 +1119,125 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n
10681119 return
10691120 fi
10701121
1071- if [[ " $DNS_CHECK_FUNC " == " drill" ]] || [[ " $DNS_CHECK_FUNC " == " dig" ]]; then
1072- if [[ -z " $gad_s " ]]; then # checking for CNAMEs
1073- res=$( $DNS_CHECK_FUNC CNAME " $gad_d " | grep " ^$gad_d " )
1074- else
1075- res=$( $DNS_CHECK_FUNC CNAME " $gad_d " " @$gad_s " | grep " ^$gad_d " )
1076- fi
1077- if [[ -n " $res " ]]; then # domain is a CNAME so get main domain
1078- gad_d=$( echo " $res " | awk ' {print $5}' | sed ' s/\.$//g' )
1079- fi
1080- if [[ -z " $gad_s " ]]; then # checking for CNAMEs
1081- res=$( $DNS_CHECK_FUNC NS " $gad_d " | grep " ^$gad_d " )
1122+ if [[ -n " $HAS_DIG_OR_DRILL " ]]; then
1123+ gad_d=" $orig_gad_d "
1124+ debug Using " $HAS_DIG_OR_DRILL SOA +trace +nocomments $gad_d @$gad_s " to find primary nameserver
1125+ # Use SOA +trace to find the name server
1126+ if [[ -z " $gad_s " ]]; then
1127+ res=$( $HAS_DIG_OR_DRILL SOA +trace +nocomments " $gad_d " 2> /dev/null | grep " IN\WNS\W" | tail -1)
10821128 else
1083- res=$( $DNS_CHECK_FUNC NS " $gad_d " " @$gad_s " | grep " ^ $gad_d " )
1129+ res=$( $HAS_DIG_OR_DRILL SOA +trace +nocomments " $gad_d " " @$gad_s " 2> /dev/null | grep " IN\WNS\W " | tail -1 )
10841130 fi
1131+
1132+ # fallback to existing code
10851133 if [[ -z " $res " ]]; then
1086- error_exit " couldn't find primary DNS server - please set AUTH_DNS_SERVER in config"
1087- else
1088- all_auth_dns_servers=$( echo " $res " | awk ' $4 ~ "NS" {print $5}' | sed ' s/\.$//g' | tr ' \n' ' ' )
1134+ debug Checking for CNAME using " $HAS_DIG_OR_DRILL CNAME $gad_d @$gad_s "
1135+ if [[ -z " $gad_s " ]]; then # checking for CNAMEs (need grep as dig 9.11 sometimes returns everything not just CNAME entries)
1136+ res=$( $HAS_DIG_OR_DRILL CNAME " $gad_d " | grep " ^$gad_d " | grep CNAME)
1137+ else
1138+ res=$( $HAS_DIG_OR_DRILL CNAME " $gad_d " " @$gad_s " | grep " ^$gad_d " | grep CNAME)
1139+ fi
1140+ if [[ -n " $res " ]]; then # domain is a CNAME so get main domain
1141+ gad_d=$( echo " $res " | awk ' {print $5}' | sed ' s/\.$//g' )
1142+ debug Domain is a CNAME, actual domain is " $gad_d "
1143+ fi
1144+ # If gad_d is an A record then this returns the SOA for the root domain, e.g. without the www
1145+ # dig NS ubuntu.getssl.text
1146+ # > getssl.test. IN SOA ns1.duckdns.org
1147+ # If gad_d is a CNAME record then this returns the NS for the domain pointed to by $gad_d
1148+ # dig NS www.getssl.text
1149+ # > www.getssl.test. IN CNAME getssl.test
1150+ # > getssl.test. IN NS ns1.duckdns.org
1151+ debug Using " $HAS_DIG_OR_DRILL NS $gad_d @$gad_s " to find primary nameserver
1152+ if [[ -z " $gad_s " ]]; then
1153+ res=$( $HAS_DIG_OR_DRILL NS " $gad_d " | grep -E " IN\W(NS|SOA)\W" | tail -1)
1154+ else
1155+ res=$( $HAS_DIG_OR_DRILL NS " $gad_d " " @$gad_s " | grep -E " IN\W(NS|SOA)\W" | tail -1)
1156+ fi
10891157 fi
1090- if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1091- primary_ns=" $all_auth_dns_servers "
1092- else
1093- primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1158+ if [[ -n " $res " ]]; then
1159+ all_auth_dns_servers=$( echo " $res " | awk ' $4 ~ "NS" {print $5}' | sed ' s/\.$//g' | tr ' \n' ' ' )
1160+ if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1161+ primary_ns=" $all_auth_dns_servers "
1162+ else
1163+ primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1164+ fi
1165+ return
10941166 fi
1095- return
10961167 fi
10971168
1098- if [[ " $DNS_CHECK_FUNC " == " host" ]]; then
1169+ if [[ " $HAS_HOST " == true ]]; then
1170+ gad_d=" $orig_gad_d "
1171+ debug Using " host -t NS" to find primary name server for " $gad_d "
10991172 if [[ -z " $gad_s " ]]; then
1100- res=$( $DNS_CHECK_FUNC -t NS " $gad_d " | grep " name server" )
1173+ res=$( host -t NS " $gad_d " | grep " name server" )
11011174 else
1102- res=$( $DNS_CHECK_FUNC -t NS " $gad_d " " $gad_s " | grep " name server" )
1175+ res=$( host -t NS " $gad_d " " $gad_s " | grep " name server" )
11031176 fi
1104- if [[ -z " $res " ]]; then
1105- error_exit " couldn't find primary DNS server - please set AUTH_DNS_SERVER in config"
1106- else
1177+ if [[ -n " $res " ]]; then
11071178 all_auth_dns_servers=$( echo " $res " | awk ' {print $4}' | sed ' s/\.$//g' | tr ' \n' ' ' )
1179+ if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1180+ primary_ns=" $all_auth_dns_servers "
1181+ else
1182+ primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1183+ fi
1184+ return
11081185 fi
1109- if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1110- primary_ns=" $all_auth_dns_servers "
1111- else
1112- primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1113- fi
1114- return
11151186 fi
11161187
1117- res=$( nslookup -debug -type=soa -type=ns " $gad_d " ${gad_s} )
1188+ if [[ " $HAS_NSLOOKUP " == true ]]; then
1189+ gad_d=" $orig_gad_d "
1190+ debug Using " nslookup -debug -type=soa -type=ns $gad_d $gad_s " to find primary name server
1191+ res=$( nslookup -debug -type=soa -type=ns " $gad_d " ${gad_s} )
1192+
1193+ if [[ " $( echo " $res " | grep -c " Non-authoritative" ) " -gt 0 ]]; then
1194+ # this is a Non-authoritative server, need to check for an authoritative one.
1195+ gad_s=$( echo " $res " | awk ' $2 ~ "nameserver" {print $4; exit }' | sed ' s/\.$//g' )
1196+ if [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1197+ # if domain name doesn't exist, then find auth servers for next level up
1198+ gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1199+ gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1200+ # handle scenario where awk returns nothing
1201+ if [[ -z " $gad_d " ]]; then
1202+ gad_d=" $orig_gad_d "
1203+ fi
1204+ fi
11181205
1119- if [[ " $( echo " $res " | grep -c " Non-authoritative" ) " -gt 0 ]]; then
1120- # this is a Non-authoritative server, need to check for an authoritative one.
1121- gad_s=$( echo " $res " | awk ' $2 ~ "nameserver" {print $4; exit }' | sed ' s/\.$//g' )
1122- if [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1123- # if domain name doesn't exist, then find auth servers for next level up
1124- gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1125- gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1206+ # shellcheck disable=SC2086
1207+ res=$( nslookup -debug -type=soa -type=ns " $gad_d " ${gad_s} )
11261208 fi
1127- fi
11281209
1129- if [[ -z " $gad_s " ]]; then
1130- res=$( nslookup -debug -type=soa -type=ns " $gad_d " )
1131- else
1132- res=$( nslookup -debug -type=soa -type=ns " $gad_d " " ${gad_s} " )
1133- fi
1210+ if [[ " $( echo " $res " | grep -c " canonical name" ) " -gt 0 ]]; then
1211+ gad_d=$( echo " $res " | awk ' $2 ~ "canonical" {print $5; exit }' | sed ' s/\.$//g' )
1212+ elif [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1213+ gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1214+ gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1215+ # handle scenario where awk returns nothing
1216+ if [[ -z " $gad_d " ]]; then
1217+ gad_d=" $orig_gad_d "
1218+ fi
1219+ fi
11341220
1135- if [[ " $( echo " $res " | grep -c " canonical name" ) " -gt 0 ]]; then
1136- gad_d=$( echo " $res " | awk ' $2 ~ "canonical" {print $5; exit }' | sed ' s/\.$//g' )
1137- elif [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1138- gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1139- gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1140- fi
1221+ # shellcheck disable=SC2086
1222+ # not quoting gad_s fixes the nslookup: couldn't get address for '': not found warning (#332)
1223+ all_auth_dns_servers=$( nslookup -debug -type=soa -type=ns " $gad_d " $gad_s \
1224+ | awk ' $1 ~ "nameserver" {print $3}' \
1225+ | sed ' s/\.$//g' | tr ' \n' ' ' )
11411226
1142- all_auth_dns_servers=$( nslookup -type=soa -type=ns " $gad_d " " $gad_s " \
1143- | awk ' $2 ~ "nameserver" {print $4}' \
1144- | sed ' s/\.$//g' | tr ' \n' ' ' )
1145- if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1146- primary_ns=" $all_auth_dns_servers "
1147- else
1148- primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1227+ if [[ -n " $all_auth_dns_servers " ]]; then
1228+ if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1229+ primary_ns=" $all_auth_dns_servers "
1230+ else
1231+ primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1232+ fi
1233+ return
1234+ fi
11491235 fi
1236+
1237+ # nslookup on alpine/ubuntu containers doesn't support -debug, print a warning in this case
1238+ # This means getssl cannot check that the DNS record has been updated on the primary name server
1239+ info " Warning: Couldn't find primary DNS server - please set PUBLIC_DNS_SERVER or AUTH_DNS_SERVER in config"
1240+ info " This means getssl cannot check the DNS entry has been updated"
11501241}
11511242
11521243get_certificate () { # get certificate for csr, if all domains validated.
@@ -2248,6 +2339,9 @@ set_server_type
22482339# check config for typical errors.
22492340check_config
22502341
2342+ # check what dns utils are installed
2343+ find_dns_utils
2344+
22512345if [[ -e " $DOMAIN_DIR /FORCE_RENEWAL" ]]; then
22522346 rm -f " $DOMAIN_DIR /FORCE_RENEWAL" || error_exit " problem deleting file $DOMAIN_DIR /FORCE_RENEWAL"
22532347 _FORCE_RENEW=1
0 commit comments