214
214
# 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20)
215
215
# 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424)
216
216
# 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)
218
221
# ----------------------------------------------------------------------------------------
219
222
220
223
PROGNAME=${0##*/ }
@@ -234,7 +237,7 @@ CSR_SUBJECT="/"
234
237
CURL_USERAGENT=" ${PROGNAME} /${VERSION} "
235
238
DEACTIVATE_AUTH=" false"
236
239
DEFAULT_REVOKE_CA=" https://acme-v02.api.letsencrypt.org"
237
- DNS_EXTRA_WAIT=" "
240
+ DNS_EXTRA_WAIT=60
238
241
DNS_WAIT=10
239
242
DOMAIN_KEY_LENGTH=4096
240
243
DUAL_RSA_ECDSA=" false"
@@ -750,13 +753,37 @@ create_order() {
750
753
OrderLink=$( echo " $responseHeaders " | grep -i location | awk ' {print $2}' | tr -d ' \r\n ' )
751
754
debug " Order link $OrderLink "
752
755
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
760
787
}
761
788
762
789
date_epoc () { # convert the date into epoch time
@@ -801,6 +828,29 @@ error_exit() { # give error message on error exit
801
828
exit 1
802
829
}
803
830
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
+
804
854
fulfill_challenges () {
805
855
dn=0
806
856
for d in $alldomains ; do
@@ -823,7 +873,9 @@ for d in $alldomains; do
823
873
error_exit " new-authz error: $response "
824
874
fi
825
875
else
826
- send_signed_request " ${AuthLink[$dn]} " " "
876
+ response=${AuthLinkResponse[$dn]}
877
+ responseHeaders=${AuthLinkResponseHeader[$dn]}
878
+ response_status=$( json_get " $response " status)
827
879
fi
828
880
829
881
if [[ $response_status == " valid" ]]; then
@@ -841,16 +893,14 @@ for d in $alldomains; do
841
893
if [[ $VALIDATE_VIA_DNS == " true" ]]; then # set up the correct DNS token for verification
842
894
if [[ $API -eq 1 ]]; then
843
895
# 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
845
897
token=$( json_get " $response " " token" " dns-01" )
846
- # get the uri from the dns component
847
898
uri=$( json_get " $response " " uri" " dns-01" )
848
899
debug uri " $uri "
849
900
else # APIv2
850
901
debug " authlink response = $response "
851
- # get the token from the http -01 component
902
+ # get the token and uri from the dns -01 component
852
903
token=$( json_get " $response " " challenges" " type" " dns-01" " token" )
853
- # get the uri from the http component
854
904
uri=$( json_get " $response " " challenges" " type" " dns-01" " url" )
855
905
debug uri " $uri "
856
906
fi
@@ -901,7 +951,6 @@ for d in $alldomains; do
901
951
uri=$( json_get " $response " " uri" " http-01" )
902
952
debug uri " $uri "
903
953
else # APIv2
904
- send_signed_request " ${AuthLink[$dn]} " " "
905
954
debug " authlink response = $response "
906
955
# get the token from the http-01 component
907
956
token=$( json_get " $response " " challenges" " type" " http-01" " token" )
@@ -998,8 +1047,9 @@ if [[ $VALIDATE_VIA_DNS == "true" ]]; then
998
1047
| grep ^_acme -A2\
999
1048
| grep ' "' | awk -F' "' ' { print $2}' )
1000
1049
elif [[ " $DNS_CHECK_FUNC " == " drill" ]] || [[ " $DNS_CHECK_FUNC " == " dig" ]]; then
1050
+ debug " $DNS_CHECK_FUNC " TXT " _acme-challenge.${d} " " @${ns} "
1001
1051
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}' )
1003
1053
elif [[ " $DNS_CHECK_FUNC " == " host" ]]; then
1004
1054
check_result=$( $DNS_CHECK_FUNC -t TXT " _acme-challenge.${d} " " ${ns} " \
1005
1055
| grep ' descriptive text' | awk -F' "' ' { print $2}' )
@@ -1054,10 +1104,11 @@ fi
1054
1104
}
1055
1105
1056
1106
get_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
1058
1108
gad_s=" $PUBLIC_DNS_SERVER " # start with PUBLIC_DNS_SERVER
1059
1109
1060
1110
if [[ " $os " == " cygwin" ]]; then
1111
+ gad_d=" $orig_gad_d "
1061
1112
all_auth_dns_servers=$( nslookup -type=soa " ${d} " ${PUBLIC_DNS_SERVER} 2> /dev/null \
1062
1113
| grep " primary name server" \
1063
1114
| awk ' {print $NF}' )
@@ -1068,85 +1119,125 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n
1068
1119
return
1069
1120
fi
1070
1121
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)
1082
1128
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 )
1084
1130
fi
1131
+
1132
+ # fallback to existing code
1085
1133
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
1089
1157
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
1094
1166
fi
1095
- return
1096
1167
fi
1097
1168
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 "
1099
1172
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" )
1101
1174
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" )
1103
1176
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
1107
1178
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
1108
1185
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
1115
1186
fi
1116
1187
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
1118
1205
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} )
1126
1208
fi
1127
- fi
1128
1209
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
1134
1220
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' ' ' )
1141
1226
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
1149
1235
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"
1150
1241
}
1151
1242
1152
1243
get_certificate () { # get certificate for csr, if all domains validated.
@@ -2248,6 +2339,9 @@ set_server_type
2248
2339
# check config for typical errors.
2249
2340
check_config
2250
2341
2342
+ # check what dns utils are installed
2343
+ find_dns_utils
2344
+
2251
2345
if [[ -e " $DOMAIN_DIR /FORCE_RENEWAL" ]]; then
2252
2346
rm -f " $DOMAIN_DIR /FORCE_RENEWAL" || error_exit " problem deleting file $DOMAIN_DIR /FORCE_RENEWAL"
2253
2347
_FORCE_RENEW=1
0 commit comments