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-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) (2.21)
217
220
# ----------------------------------------------------------------------------------------
218
221
219
222
PROGNAME=${0##*/ }
220
- VERSION=" 2.20 "
223
+ VERSION=" 2.21 "
221
224
222
225
# defaults
223
226
ACCOUNT_KEY_LENGTH=4096
@@ -233,7 +236,7 @@ CSR_SUBJECT="/"
233
236
CURL_USERAGENT=" ${PROGNAME} /${VERSION} "
234
237
DEACTIVATE_AUTH=" false"
235
238
DEFAULT_REVOKE_CA=" https://acme-v02.api.letsencrypt.org"
236
- DNS_EXTRA_WAIT=" "
239
+ DNS_EXTRA_WAIT=60
237
240
DNS_WAIT=10
238
241
DOMAIN_KEY_LENGTH=4096
239
242
DUAL_RSA_ECDSA=" false"
@@ -749,13 +752,37 @@ create_order() {
749
752
OrderLink=$( echo " $responseHeaders " | grep -i location | awk ' {print $2}' | tr -d ' \r\n ' )
750
753
debug " Order link $OrderLink "
751
754
FinalizeLink=$( json_get " $response " " finalize" )
752
- dn=0
753
- for d in $alldomains ; do
754
- # get authorizations link
755
- AuthLink[$dn ]=$( json_get " $response " " identifiers" " value" " $d " " authorizations" " x" )
756
- debug " authorizations link for $d - ${AuthLink[$dn]} "
757
- (( dn++ ))
758
- done
755
+
756
+ if [[ $API -eq 1 ]]; then
757
+ dn=0
758
+ for d in $alldomains ; do
759
+ # get authorizations link
760
+ AuthLink[$dn ]=$( json_get " $response " " identifiers" " value" " $d " " authorizations" " x" )
761
+ debug " authorizations link for $d - ${AuthLink[$dn]} "
762
+ (( dn++ ))
763
+ done
764
+ else
765
+ # Authorization links are unsorted, so fetch the authorization link, find the domain, save response in the correct array position
766
+ AuthLinks=$( json_get " $response " " authorizations" )
767
+ AuthLinkResponse=()
768
+ AuthLinkResponseHeader=()
769
+ for l in $AuthLinks ; do
770
+ debug " Requesting authorizations link for $l "
771
+ send_signed_request " $l " " "
772
+ # Get domain from response
773
+ authdomain=$( json_get " $response " " identifier" " value" )
774
+ # find array position (This is O(n2) but that doubt we'll see performance issues)
775
+ dn=0
776
+ for d in $alldomains ; do
777
+ if [ " $d " == " $authdomain " ]; then
778
+ debug " Saving authorization response for $authdomain for domain alldomains[$dn ]"
779
+ AuthLinkResponse[$dn ]=$response
780
+ AuthLinkResponseHeader[$dn ]=$responseHeaders
781
+ fi
782
+ (( dn++ ))
783
+ done
784
+ done
785
+ fi
759
786
}
760
787
761
788
date_epoc () { # convert the date into epoch time
@@ -800,6 +827,29 @@ error_exit() { # give error message on error exit
800
827
exit 1
801
828
}
802
829
830
+ find_dns_utils () {
831
+ HAS_NSLOOKUP=false
832
+ HAS_DIG_OR_DRILL=" "
833
+ HAS_HOST=false
834
+ if [[ -n " $( command -v nslookup) " ]]; then
835
+ debug " HAS NSLOOKUP=true"
836
+ HAS_NSLOOKUP=true
837
+ fi
838
+
839
+ if [[ -n " $( command -v drill) " ]]; then
840
+ debug " HAS DIG_OR_DRILL=drill"
841
+ HAS_DIG_OR_DRILL=" drill"
842
+ elif [[ -n " $( command -v dig) " ]]; then
843
+ debug " HAS DIG_OR_DRILL=dig"
844
+ HAS_DIG_OR_DRILL=" dig"
845
+ fi
846
+
847
+ if [[ -n " $( command -v host) " ]]; then
848
+ debug " HAS HOST=true"
849
+ HAS_HOST=true
850
+ fi
851
+ }
852
+
803
853
fulfill_challenges () {
804
854
dn=0
805
855
for d in $alldomains ; do
@@ -822,7 +872,9 @@ for d in $alldomains; do
822
872
error_exit " new-authz error: $response "
823
873
fi
824
874
else
825
- send_signed_request " ${AuthLink[$dn]} " " "
875
+ response=${AuthLinkResponse[$dn]}
876
+ responseHeaders=${AuthLinkResponseHeader[$dn]}
877
+ response_status=$( json_get " $response " status)
826
878
fi
827
879
828
880
if [[ $response_status == " valid" ]]; then
@@ -840,16 +892,14 @@ for d in $alldomains; do
840
892
if [[ $VALIDATE_VIA_DNS == " true" ]]; then # set up the correct DNS token for verification
841
893
if [[ $API -eq 1 ]]; then
842
894
# get the dns component of the ACME response
843
- # get the token from the dns component
895
+ # get the token and uri from the dns component
844
896
token=$( json_get " $response " " token" " dns-01" )
845
- # get the uri from the dns component
846
897
uri=$( json_get " $response " " uri" " dns-01" )
847
898
debug uri " $uri "
848
899
else # APIv2
849
900
debug " authlink response = $response "
850
- # get the token from the http -01 component
901
+ # get the token and uri from the dns -01 component
851
902
token=$( json_get " $response " " challenges" " type" " dns-01" " token" )
852
- # get the uri from the http component
853
903
uri=$( json_get " $response " " challenges" " type" " dns-01" " url" )
854
904
debug uri " $uri "
855
905
fi
@@ -900,7 +950,6 @@ for d in $alldomains; do
900
950
uri=$( json_get " $response " " uri" " http-01" )
901
951
debug uri " $uri "
902
952
else # APIv2
903
- send_signed_request " ${AuthLink[$dn]} " " "
904
953
debug " authlink response = $response "
905
954
# get the token from the http-01 component
906
955
token=$( json_get " $response " " challenges" " type" " http-01" " token" )
@@ -997,8 +1046,9 @@ if [[ $VALIDATE_VIA_DNS == "true" ]]; then
997
1046
| grep ^_acme -A2\
998
1047
| grep ' "' | awk -F' "' ' { print $2}' )
999
1048
elif [[ " $DNS_CHECK_FUNC " == " drill" ]] || [[ " $DNS_CHECK_FUNC " == " dig" ]]; then
1049
+ debug " $DNS_CHECK_FUNC " TXT " _acme-challenge.${d} " " @${ns} "
1000
1050
check_result=$( $DNS_CHECK_FUNC TXT " _acme-challenge.${d} " " @${ns} " \
1001
- | grep ' IN TXT ' | awk -F' "' ' { print $2}' )
1051
+ | grep ' IN\WTXT ' | awk -F' "' ' { print $2}' )
1002
1052
elif [[ " $DNS_CHECK_FUNC " == " host" ]]; then
1003
1053
check_result=$( $DNS_CHECK_FUNC -t TXT " _acme-challenge.${d} " " ${ns} " \
1004
1054
| grep ' descriptive text' | awk -F' "' ' { print $2}' )
@@ -1053,10 +1103,11 @@ fi
1053
1103
}
1054
1104
1055
1105
get_auth_dns () { # get the authoritative dns server for a domain (sets primary_ns )
1056
- gad_d =" $1 " # domain name
1106
+ orig_gad_d =" $1 " # domain name
1057
1107
gad_s=" $PUBLIC_DNS_SERVER " # start with PUBLIC_DNS_SERVER
1058
1108
1059
1109
if [[ " $os " == " cygwin" ]]; then
1110
+ gad_d=" $orig_gad_d "
1060
1111
all_auth_dns_servers=$( nslookup -type=soa " ${d} " ${PUBLIC_DNS_SERVER} 2> /dev/null \
1061
1112
| grep " primary name server" \
1062
1113
| awk ' {print $NF}' )
@@ -1067,85 +1118,125 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n
1067
1118
return
1068
1119
fi
1069
1120
1070
- if [[ " $DNS_CHECK_FUNC " == " drill" ]] || [[ " $DNS_CHECK_FUNC " == " dig" ]]; then
1071
- if [[ -z " $gad_s " ]]; then # checking for CNAMEs
1072
- res=$( $DNS_CHECK_FUNC CNAME " $gad_d " | grep " ^$gad_d " )
1073
- else
1074
- res=$( $DNS_CHECK_FUNC CNAME " $gad_d " " @$gad_s " | grep " ^$gad_d " )
1075
- fi
1076
- if [[ -n " $res " ]]; then # domain is a CNAME so get main domain
1077
- gad_d=$( echo " $res " | awk ' {print $5}' | sed ' s/\.$//g' )
1078
- fi
1079
- if [[ -z " $gad_s " ]]; then # checking for CNAMEs
1080
- res=$( $DNS_CHECK_FUNC NS " $gad_d " | grep " ^$gad_d " )
1121
+ if [[ -n " $HAS_DIG_OR_DRILL " ]]; then
1122
+ gad_d=" $orig_gad_d "
1123
+ debug Using " $HAS_DIG_OR_DRILL SOA +trace +nocomments $gad_d @$gad_s " to find primary nameserver
1124
+ # Use SOA +trace to find the name server
1125
+ if [[ -z " $gad_s " ]]; then
1126
+ res=$( $HAS_DIG_OR_DRILL SOA +trace +nocomments " $gad_d " 2> /dev/null | grep " IN\WNS\W" | tail -1)
1081
1127
else
1082
- res=$( $DNS_CHECK_FUNC NS " $gad_d " " @$gad_s " | grep " ^ $gad_d " )
1128
+ res=$( $HAS_DIG_OR_DRILL SOA +trace +nocomments " $gad_d " " @$gad_s " 2> /dev/null | grep " IN\WNS\W " | tail -1 )
1083
1129
fi
1130
+
1131
+ # fallback to existing code
1084
1132
if [[ -z " $res " ]]; then
1085
- error_exit " couldn't find primary DNS server - please set AUTH_DNS_SERVER in config"
1086
- else
1087
- all_auth_dns_servers=$( echo " $res " | awk ' $4 ~ "NS" {print $5}' | sed ' s/\.$//g' | tr ' \n' ' ' )
1133
+ debug Checking for CNAME using " $HAS_DIG_OR_DRILL CNAME $gad_d @$gad_s "
1134
+ if [[ -z " $gad_s " ]]; then # checking for CNAMEs (need grep as dig 9.11 sometimes returns everything not just CNAME entries)
1135
+ res=$( $HAS_DIG_OR_DRILL CNAME " $gad_d " | grep " ^$gad_d " | grep CNAME)
1136
+ else
1137
+ res=$( $HAS_DIG_OR_DRILL CNAME " $gad_d " " @$gad_s " | grep " ^$gad_d " | grep CNAME)
1138
+ fi
1139
+ if [[ -n " $res " ]]; then # domain is a CNAME so get main domain
1140
+ gad_d=$( echo " $res " | awk ' {print $5}' | sed ' s/\.$//g' )
1141
+ debug Domain is a CNAME, actual domain is " $gad_d "
1142
+ fi
1143
+ # If gad_d is an A record then this returns the SOA for the root domain, e.g. without the www
1144
+ # dig NS ubuntu.getssl.text
1145
+ # > getssl.test. IN SOA ns1.duckdns.org
1146
+ # If gad_d is a CNAME record then this returns the NS for the domain pointed to by $gad_d
1147
+ # dig NS www.getssl.text
1148
+ # > www.getssl.test. IN CNAME getssl.test
1149
+ # > getssl.test. IN NS ns1.duckdns.org
1150
+ debug Using " $HAS_DIG_OR_DRILL NS $gad_d @$gad_s " to find primary nameserver
1151
+ if [[ -z " $gad_s " ]]; then
1152
+ res=$( $HAS_DIG_OR_DRILL NS " $gad_d " | grep -E " IN\W(NS|SOA)\W" | tail -1)
1153
+ else
1154
+ res=$( $HAS_DIG_OR_DRILL NS " $gad_d " " @$gad_s " | grep -E " IN\W(NS|SOA)\W" | tail -1)
1155
+ fi
1088
1156
fi
1089
- if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1090
- primary_ns=" $all_auth_dns_servers "
1091
- else
1092
- primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1157
+ if [[ -n " $res " ]]; then
1158
+ all_auth_dns_servers=$( echo " $res " | awk ' $4 ~ "NS" {print $5}' | sed ' s/\.$//g' | tr ' \n' ' ' )
1159
+ if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1160
+ primary_ns=" $all_auth_dns_servers "
1161
+ else
1162
+ primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1163
+ fi
1164
+ return
1093
1165
fi
1094
- return
1095
1166
fi
1096
1167
1097
- if [[ " $DNS_CHECK_FUNC " == " host" ]]; then
1168
+ if [[ " $HAS_HOST " == true ]]; then
1169
+ gad_d=" $orig_gad_d "
1170
+ debug Using " host -t NS" to find primary name server for " $gad_d "
1098
1171
if [[ -z " $gad_s " ]]; then
1099
- res=$( $DNS_CHECK_FUNC -t NS " $gad_d " | grep " name server" )
1172
+ res=$( host -t NS " $gad_d " | grep " name server" )
1100
1173
else
1101
- res=$( $DNS_CHECK_FUNC -t NS " $gad_d " " $gad_s " | grep " name server" )
1174
+ res=$( host -t NS " $gad_d " " $gad_s " | grep " name server" )
1102
1175
fi
1103
- if [[ -z " $res " ]]; then
1104
- error_exit " couldn't find primary DNS server - please set AUTH_DNS_SERVER in config"
1105
- else
1176
+ if [[ -n " $res " ]]; then
1106
1177
all_auth_dns_servers=$( echo " $res " | awk ' {print $4}' | sed ' s/\.$//g' | tr ' \n' ' ' )
1178
+ if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1179
+ primary_ns=" $all_auth_dns_servers "
1180
+ else
1181
+ primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1182
+ fi
1183
+ return
1107
1184
fi
1108
- if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1109
- primary_ns=" $all_auth_dns_servers "
1110
- else
1111
- primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1112
- fi
1113
- return
1114
1185
fi
1115
1186
1116
- res=$( nslookup -debug -type=soa -type=ns " $gad_d " ${gad_s} )
1187
+ if [[ " $HAS_NSLOOKUP " == true ]]; then
1188
+ gad_d=" $orig_gad_d "
1189
+ debug Using " nslookup -debug -type=soa -type=ns $gad_d $gad_s " to find primary name server
1190
+ res=$( nslookup -debug -type=soa -type=ns " $gad_d " ${gad_s} )
1191
+
1192
+ if [[ " $( echo " $res " | grep -c " Non-authoritative" ) " -gt 0 ]]; then
1193
+ # this is a Non-authoritative server, need to check for an authoritative one.
1194
+ gad_s=$( echo " $res " | awk ' $2 ~ "nameserver" {print $4; exit }' | sed ' s/\.$//g' )
1195
+ if [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1196
+ # if domain name doesn't exist, then find auth servers for next level up
1197
+ gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1198
+ gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1199
+ # handle scenario where awk returns nothing
1200
+ if [[ -z " $gad_d " ]]; then
1201
+ gad_d=" $orig_gad_d "
1202
+ fi
1203
+ fi
1117
1204
1118
- if [[ " $( echo " $res " | grep -c " Non-authoritative" ) " -gt 0 ]]; then
1119
- # this is a Non-authoritative server, need to check for an authoritative one.
1120
- gad_s=$( echo " $res " | awk ' $2 ~ "nameserver" {print $4; exit }' | sed ' s/\.$//g' )
1121
- if [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1122
- # if domain name doesn't exist, then find auth servers for next level up
1123
- gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1124
- gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1205
+ # shellcheck disable=SC2086
1206
+ res=$( nslookup -debug -type=soa -type=ns " $gad_d " ${gad_s} )
1125
1207
fi
1126
- fi
1127
1208
1128
- if [[ -z " $gad_s " ]]; then
1129
- res=$( nslookup -debug -type=soa -type=ns " $gad_d " )
1130
- else
1131
- res=$( nslookup -debug -type=soa -type=ns " $gad_d " " ${gad_s} " )
1132
- fi
1209
+ if [[ " $( echo " $res " | grep -c " canonical name" ) " -gt 0 ]]; then
1210
+ gad_d=$( echo " $res " | awk ' $2 ~ "canonical" {print $5; exit }' | sed ' s/\.$//g' )
1211
+ elif [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1212
+ gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1213
+ gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1214
+ # handle scenario where awk returns nothing
1215
+ if [[ -z " $gad_d " ]]; then
1216
+ gad_d=" $orig_gad_d "
1217
+ fi
1218
+ fi
1133
1219
1134
- if [[ " $( echo " $res " | grep -c " canonical name" ) " -gt 0 ]]; then
1135
- gad_d=$( echo " $res " | awk ' $2 ~ "canonical" {print $5; exit }' | sed ' s/\.$//g' )
1136
- elif [[ " $( echo " $res " | grep -c " an't find" ) " -gt 0 ]]; then
1137
- gad_s=$( echo " $res " | awk ' $1 ~ "origin" {print $3; exit }' )
1138
- gad_d=$( echo " $res " | awk ' $1 ~ "->" {print $2; exit}' )
1139
- fi
1220
+ # shellcheck disable=SC2086
1221
+ # not quoting gad_s fixes the nslookup: couldn't get address for '': not found warning (#332)
1222
+ all_auth_dns_servers=$( nslookup -debug -type=soa -type=ns " $gad_d " $gad_s \
1223
+ | awk ' $1 ~ "nameserver" {print $3}' \
1224
+ | sed ' s/\.$//g' | tr ' \n' ' ' )
1140
1225
1141
- all_auth_dns_servers=$( nslookup -type=soa -type=ns " $gad_d " " $gad_s " \
1142
- | awk ' $2 ~ "nameserver" {print $4}' \
1143
- | sed ' s/\.$//g' | tr ' \n' ' ' )
1144
- if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1145
- primary_ns=" $all_auth_dns_servers "
1146
- else
1147
- primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1226
+ if [[ -n " $all_auth_dns_servers " ]]; then
1227
+ if [[ $CHECK_ALL_AUTH_DNS == " true" ]]; then
1228
+ primary_ns=" $all_auth_dns_servers "
1229
+ else
1230
+ primary_ns=$( echo " $all_auth_dns_servers " | awk ' {print $1}' )
1231
+ fi
1232
+ return
1233
+ fi
1148
1234
fi
1235
+
1236
+ # nslookup on alpine/ubuntu containers doesn't support -debug, print a warning in this case
1237
+ # This means getssl cannot check that the DNS record has been updated on the primary name server
1238
+ info " Warning: Couldn't find primary DNS server - please set PUBLIC_DNS_SERVER or AUTH_DNS_SERVER in config"
1239
+ info " This means getssl cannot check the DNS entry has been updated"
1149
1240
}
1150
1241
1151
1242
get_certificate () { # get certificate for csr, if all domains validated.
@@ -2247,6 +2338,9 @@ set_server_type
2247
2338
# check config for typical errors.
2248
2339
check_config
2249
2340
2341
+ # check what dns utils are installed
2342
+ find_dns_utils
2343
+
2250
2344
if [[ -e " $DOMAIN_DIR /FORCE_RENEWAL" ]]; then
2251
2345
rm -f " $DOMAIN_DIR /FORCE_RENEWAL" || error_exit " problem deleting file $DOMAIN_DIR /FORCE_RENEWAL"
2252
2346
_FORCE_RENEW=1
0 commit comments