264264# 2021-07-12 Do not redirect outputs on remote commands when the debug option is used (atisne)
265265# 2021-07-20 Use +noidnout to enable certificates for IDN domains (#679)(2.37)
266266# 2021-07-22 Only pass +noidnout param to dig/drill(#682)(2.38)
267+ # 2021-07-25 Fix copy_file_to_location failures with ssh when suffix applied to file lacking an extension (tlhackque)(#686)
268+ # 2021-07-27 Support ftps://, FTPS_OPTIONS, remove default --insecure parameter to ftpes. Report caller(s) of error_exit in debug and test modes (tlhackque)(#687)(2.39)
269+ # 2021-07-30 Prefer API V2 when both offered (tlhackque) (#690) (2.40)
270+ # 2021-07-30 Run tests with -d to catch intermittent failures, Use fork's repo for upgrade tests. (tlhackque) (#692) (2.41)
267271# ----------------------------------------------------------------------------------------
268272
269273case :$SHELLOPTS : in
272276
273277PROGNAME=${0##*/ }
274278PROGDIR=" $( cd " $( dirname " $0 " ) " || exit ; pwd -P; ) "
275- VERSION=" 2.38 "
279+ VERSION=" 2.41 "
276280
277281# defaults
278282ACCOUNT_KEY_LENGTH=4096
@@ -282,14 +286,19 @@ CA="https://acme-staging-v02.api.letsencrypt.org/directory"
282286CHALLENGE_CHECK_TYPE=" http"
283287CHECK_REMOTE_WAIT=0
284288CHECK_REMOTE=" true"
285- CODE_LOCATION=" https://raw.githubusercontent.com/srvrco/getssl/master/getssl"
289+ if [[ -n " ${GITHUB_REPOSITORY} " ]] ; then
290+ CODE_LOCATION=" https://raw.githubusercontent.com/${GITHUB_REPOSITORY} /master/getssl"
291+ else
292+ CODE_LOCATION=" https://raw.githubusercontent.com/srvrco/getssl/master/getssl"
293+ fi
286294CSR_SUBJECT=" /"
287295CURL_USERAGENT=" ${PROGNAME} /${VERSION} "
288296DEACTIVATE_AUTH=" false"
289297DEFAULT_REVOKE_CA=" https://acme-v02.api.letsencrypt.org"
290298DOMAIN_KEY_LENGTH=4096
291299DUAL_RSA_ECDSA=" false"
292300FTP_OPTIONS=" "
301+ FTPS_OPTIONS=" "
293302FULL_CHAIN_INCLUDE_ROOT=" false"
294303GETSSL_IGNORE_CP_PRESERVE=" false"
295304HTTP_TOKEN_CHECK_WAIT=0
@@ -327,10 +336,12 @@ DNS_WAIT_RETRY_ADD="false" # Try the dns_add_command again if the DNS recor
327336# Private variables
328337_CHECK_ALL=0
329338_CREATE_CONFIG=0
339+ _CURL_VERSION=" "
330340_FORCE_RENEW=0
331341_KEEP_VERSIONS=" "
332342_MUTE=0
333343_NOTIFY_VALID=0
344+ _NOMETER=" "
334345_QUIET=0
335346_RECREATE_CSR=0
336347_REDIRECT_OUTPUT=" 1>/dev/null 2>&1"
@@ -559,15 +570,15 @@ check_challenge_completion_dns() { # perform validation via DNS challenge
559570 check_result=$( $DNS_CHECK_FUNC $DNS_CHECK_OPTIONS TXT " ${rr} " " @${ns} " \
560571 | grep -i " ^${rr} " \
561572 | grep ' IN\WTXT' | awk -F' "' ' { print $2}' )
562- debug " check_result=$check_result "
573+ debug " check_result=\" $check_result \" "
563574 if [[ -z " $check_result " ]]; then
564575 # shellcheck disable=SC2086
565576 debug " $DNS_CHECK_FUNC " $DNS_CHECK_OPTIONS ANY " ${rr} " " @${ns} "
566577 # shellcheck disable=SC2086
567578 check_result=$( $DNS_CHECK_FUNC $DNS_CHECK_OPTIONS ANY " ${rr} " " @${ns} " \
568579 | grep -i " ^${rr} " \
569580 | grep ' IN\WTXT' | awk -F' "' ' { print $2}' )
570- debug " check_result=$check_result "
581+ debug " check_result=\" $check_result \" "
571582 fi
572583 elif [[ " $DNS_CHECK_FUNC " == " host" ]]; then
573584 check_result=$( $DNS_CHECK_FUNC -t TXT " ${rr} " " ${ns} " \
@@ -580,8 +591,8 @@ check_challenge_completion_dns() { # perform validation via DNS challenge
580591 | grep ' text =' | awk -F' "' ' { print $2}' )
581592 fi
582593 fi
583- debug " expecting $auth_key "
584- debug " ${ns} gave ... $check_result "
594+ debug " expecting \" $auth_key \" "
595+ debug " ${ns} gave ... \" $check_result \" "
585596
586597 if [[ " $check_result " == * " $auth_key " * ]]; then
587598 check_dns=" success"
@@ -603,7 +614,7 @@ check_challenge_completion_dns() { # perform validation via DNS challenge
603614 debug " dns check failed - removing existing value"
604615 del_dns_rr " ${d} " " ${auth_key} "
605616
606- error_exit " checking ${rr} gave $check_result not $auth_key "
617+ error_exit " checking \" ${rr} \" gave \" $check_result \" not \" $auth_key \" "
607618 fi
608619 fi
609620 done
@@ -755,7 +766,7 @@ check_getssl_upgrade() { # check if a more recent version of code is available a
755766 if [ " $TEMP_UPGRADE_FILE " == " " ]; then
756767 error_exit " mktemp failed"
757768 fi
758- curl --user-agent " $CURL_USERAGENT " --silent " $CODE_LOCATION " --output " $TEMP_UPGRADE_FILE "
769+ curl ${_NOMETER} --user-agent " $CURL_USERAGENT " --silent " $CODE_LOCATION " --output " $TEMP_UPGRADE_FILE "
759770 errcode=$?
760771 if [[ $errcode -eq 60 ]]; then
761772 error_exit " curl needs updating, your version does not support SNI (multiple SSL domains on a single IP)"
@@ -850,7 +861,12 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required.
850861 IFS=\; read -r -a copy_locations <<< " $3"
851862 for to in " ${copy_locations[@]} " ; do
852863 if [[ -n " $suffix " ]]; then
853- to=" ${to% .* } .${suffix} .${to##* .} "
864+ bname=" $( basename " $to " ) "
865+ if [[ " ${bname##* .} " == " $bname " ]]; then
866+ to=" ${to} .${suffix} "
867+ else
868+ to=" ${to% .* } .${suffix} .${to##* .} "
869+ fi
854870 fi
855871 info " copying $cert to $to "
856872 if [[ " ${to: 0: 4} " == " ssh:" ]] ; then
@@ -933,8 +949,8 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required.
933949 fromfile=$( basename " $from " )
934950 debug " davs user=$davsuser - pass=$davspass - host=$davshost port=$davsport dir=$davsdirn file=$davsfile "
935951 debug " from dir=$fromdir file=$fromfile "
936- curl -u " ${davsuser} :${davspass} " -T " ${fromdir} /${fromfile} " " https://${davshost} :${davsport}${davsdirn}${davsfile} "
937- elif [[ " ${to: 0: 6} " == " ftpes:" ]] ; then
952+ curl ${_NOMETER} -u " ${davsuser} :${davspass} " -T " ${fromdir} /${fromfile} " " https://${davshost} :${davsport}${davsdirn}${davsfile} "
953+ elif [[ " ${to: 0: 6} " == " ftpes:" ]] || [[ " ${to : 0 : 5} " == " ftps: " ]] ; then
938954 debug " using ftp to copy the file from $from "
939955 ftpuser=$( echo " $to " | awk -F: ' {print $2}' )
940956 ftppass=$( echo " $to " | awk -F: ' {print $3}' )
@@ -946,7 +962,13 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required.
946962 fromfile=$( basename " $from " )
947963 debug " ftp user=$ftpuser - pass=$ftppass - host=$ftphost dir=$ftpdirn file=$ftpfile "
948964 debug " from dir=$fromdir file=$fromfile "
949- curl --insecure --ftp-ssl -u " ${ftpuser} :${ftppass} " -T " ${fromdir} /${fromfile} " " ftp://${ftphost}${ftpdirn} /"
965+ if [[ " ${to: 0: 5} " == " ftps:" ]] ; then
966+ # shellcheck disable=SC2086
967+ curl ${_NOMETER} $FTPS_OPTIONS --ftp-ssl --ftp-ssl-reqd -u " ${ftpuser} :${ftppass} " -T " ${fromdir} /${fromfile} " " ftp://${ftphost}${ftpdirn} :990/"
968+ else
969+ # shellcheck disable=SC2086
970+ curl ${_NOMETER} $FTPS_OPTIONS --ftp-ssl --ftp-ssl-reqd -u " ${ftpuser} :${ftppass} " -T " ${fromdir} /${fromfile} " " ftp://${ftphost}${ftpdirn} /"
971+ fi
950972 else
951973 if ! mkdir -p " $( dirname " $to " ) " ; then
952974 error_exit " cannot create ACL directory $( basename " $to " ) "
@@ -1146,6 +1168,9 @@ test_output() { # write out debug output for testing
11461168
11471169error_exit () { # give error message on error exit
11481170 echo -e " ${PROGNAME} : ${1:- " Unknown Error" } " >&2
1171+ if [[ ${_RUNNING_TEST} -eq 1 ]] || [[ ${_USE_DEBUG} -eq 1 ]] ; then
1172+ traceback
1173+ fi
11491174 clean_up
11501175 exit 1
11511176}
@@ -1357,7 +1382,10 @@ for d in "${alldomains[@]}"; do
13571382 else
13581383 sleep " $HTTP_TOKEN_CHECK_WAIT "
13591384 # check that we can reach the challenge ourselves, if not, then error
1360- if [[ ! " $( curl --user-agent " $CURL_USERAGENT " -k --silent --location " $wellknown_url " ) " == " $keyauthorization " ]]; then
1385+ # ACME only allows port 80 (http), but redirects may use https. --insecure is used in case
1386+ # those certificates are being renewed. Let's Encrypt does the same. In this case, we verify
1387+ # that the correct data is returned, so this is safe.
1388+ if [[ ! " $( curl ${_NOMETER} --user-agent " $CURL_USERAGENT " --insecure --silent --location " $wellknown_url " ) " == " $keyauthorization " ]]; then
13611389 error_exit " for some reason could not reach $wellknown_url - please check it manually"
13621390 fi
13631391 fi
@@ -1601,7 +1629,7 @@ get_certificate() { # get certificate for csr, if all domains validated.
16011629 CertData=$( awk ' $1 ~ "^Location" {print $2}' " $CURL_HEADER " | tr -d ' \r' )
16021630 if [[ " $CertData " ]] ; then
16031631 echo -----BEGIN CERTIFICATE----- > " $gc_certfile "
1604- curl --user-agent " $CURL_USERAGENT " --silent " $CertData " | openssl base64 -e >> " $gc_certfile "
1632+ curl ${_NOMETER} --user-agent " $CURL_USERAGENT " --silent " $CertData " | openssl base64 -e >> " $gc_certfile "
16051633 echo -----END CERTIFICATE----- >> " $gc_certfile "
16061634 info " Certificate saved in $CERT_FILE "
16071635 fi
@@ -1621,7 +1649,7 @@ get_certificate() { # get certificate for csr, if all domains validated.
16211649 | sed ' s/>//g' )
16221650 if [[ " $IssuerData " ]] ; then
16231651 echo -----BEGIN CERTIFICATE----- > " $gc_cafile "
1624- curl --user-agent " $CURL_USERAGENT " --silent " $IssuerData " | openssl base64 -e >> " $gc_cafile "
1652+ curl ${_NOMETER} --user-agent " $CURL_USERAGENT " --silent " $IssuerData " | openssl base64 -e >> " $gc_cafile "
16251653 echo -----END CERTIFICATE----- >> " $gc_cafile "
16261654 info " The intermediate CA cert is in $gc_cafile "
16271655 fi
@@ -1679,7 +1707,7 @@ get_certificate() { # get certificate for csr, if all domains validated.
16791707 cp " $gc_certfile " " $gc_fullchain "
16801708 while [[ -n " $issuer_url " ]]; do
16811709 debug Fetching certificate issuer from " $issuer_url "
1682- issuer_cert=$( curl --user-agent " $CURL_USERAGENT " --silent " $issuer_url " | openssl x509 -inform der -outform pem)
1710+ issuer_cert=$( curl ${_NOMETER} --user-agent " $CURL_USERAGENT " --silent " $issuer_url " | openssl x509 -inform der -outform pem)
16831711 debug Fetched issuer certificate " $( echo " $issuer_cert " | openssl x509 -inform pem -noout -text | awk ' BEGIN {FS="Subject: "} NF==2 {print $2; exit}' ) "
16841712 echo " $issuer_cert " >> " $gc_fullchain "
16851713
@@ -1694,7 +1722,7 @@ get_certificate() { # get certificate for csr, if all domains validated.
16941722get_cr () { # get curl response
16951723 url=" $1 "
16961724 debug url " $url "
1697- response=$( curl --user-agent " $CURL_USERAGENT " --silent " $url " )
1725+ response=$( curl ${_NOMETER} --user-agent " $CURL_USERAGENT " --silent " $url " )
16981726 ret=$?
16991727 debug response " ${response// [$'\t\r\n']} "
17001728 code=$( json_get " $response " status)
@@ -1777,7 +1805,7 @@ get_signing_params() { # get signing parameters from key
17771805}
17781806
17791807graceful_exit () { # normal exit function.
1780- exit_code=$1
1808+ exit_code=" ${1-0} "
17811809 clean_up
17821810 # shellcheck disable=SC2086
17831811 exit $exit_code
@@ -2035,7 +2063,7 @@ obtain_ca_resource_locations()
20352063 for suffix in " " " /directory" " /dir" ;
20362064 do
20372065 # Obtain CA resource locations
2038- ca_all_loc=$( curl --user-agent " $CURL_USERAGENT " " ${CA}${suffix} " 2> /dev/null)
2066+ ca_all_loc=$( curl ${_NOMETER} --user-agent " $CURL_USERAGENT " " ${CA}${suffix} " 2> /dev/null)
20392067 debug " ca_all_loc from ${CA}${suffix} gives $ca_all_loc "
20402068 # APIv1
20412069 URL_new_reg=$( echo " $ca_all_loc " | grep " new-reg" | awk -F' "' ' {print $4}' )
@@ -2052,10 +2080,11 @@ obtain_ca_resource_locations()
20522080 fi
20532081 done
20542082
2055- if [[ -n " $URL_new_reg " ]]; then
2056- API=1
2057- elif [[ -n " $URL_newAccount " ]]; then
2083+ # If a directory offers both versions, select V2.
2084+ if [[ -n " $URL_newAccount " ]]; then
20582085 API=2
2086+ elif [[ -n " $URL_new_reg " ]]; then
2087+ API=1
20592088 else
20602089 error_exit " unknown API version"
20612090 fi
@@ -2216,9 +2245,9 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p
22162245 CURL_HEADER=" $TEMP_DIR /curl.header"
22172246 dp=" $TEMP_DIR /curl.dump"
22182247
2219- CURL=" curl "
2248+ CURL=" curl ${_NOMETER} "
22202249 # shellcheck disable=SC2072
2221- if [[ " $( $CURL -V | head -1 | cut -d ' ' -f2 ) " > " 7.33" ]]; then
2250+ if [[ ! " ${_CURL_VERSION} " < " 7.33" ]]; then
22222251 CURL=" $CURL --http1.1 "
22232252 fi
22242253
@@ -2294,7 +2323,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p
22942323 fi
22952324
22962325 if [[ $errcode -gt 0 || ( " $response " == " " && $url ! = * " revoke" * ) ]]; then
2297- error_exit " ERROR curl \" $url \" failed with $errcode and returned $response "
2326+ error_exit " ERROR curl \" $url \" failed with $errcode and returned \" $response \" "
22982327 fi
22992328
23002329 responseHeaders=$( cat " $CURL_HEADER " )
@@ -2393,6 +2422,17 @@ signal_exit() { # Handle trapped signals
23932422 esac
23942423}
23952424
2425+ traceback () { # Print function traceback
2426+ local i d=1 lbl=" called"
2427+ debug " Traceback"
2428+ for (( i= $((${# FUNCNAME[@]} - 1 )) ; i> 0; i--)); do
2429+ if [[ ${i} -eq 1 ]] ; then lbl=" called traceback" ; fi
2430+ debug " $( printf " %*s%s() line %d%s\n" " $d " ' ' " ${FUNCNAME[$i]} " " ${BASH_LINENO[$((i-1))]} " " $lbl " ) "
2431+ (( d++ ))
2432+ done
2433+ return 0
2434+ }
2435+
23962436urlbase64 () { # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_'
23972437 openssl base64 -e | tr -d ' \n\r' | os_esed -e ' s:=*$::g' -e ' y:+/:-_:'
23982438}
@@ -2448,9 +2488,11 @@ write_domain_template() { # write out a template file for a domain.
24482488 # An ssh key will be needed to provide you with access to the remote server.
24492489 # Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign.
24502490 # If left blank, the username on the local server will be used to authenticate against the remote server.
2451- # If these start with ftp:/ftpes: then the next variables are ftpuserid:ftppassword:servername:ACL_location
2491+ # If these start with ftp:/ftpes:/ftps: then the next variables are ftpuserid:ftppassword:servername:ACL_location
24522492 # These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge"
24532493 # where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain.
2494+ # ftp: uses regular ftp; ftpes: ftp over explicit TLS (port 21); ftps: ftp over implicit TLS (port 990).
2495+ # ftps/ftpes support FTPS_OPTIONS, e.g. to add "--insecure" to the curl command for hosts with self-signed certificates.
24542496 # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username,
24552497 # password, host, port (explicitly needed even if using default port 443) and path on the server.
24562498 # Multiple locations can be defined for a file by separating the locations with a semi-colon.
@@ -2459,6 +2501,7 @@ write_domain_template() { # write out a template file for a domain.
24592501 # 'ssh:sshuserid@server5:/var/www/${DOMAIN} /web/.well-known/acme-challenge'
24602502 # 'ftp:ftpuserid:ftppassword:${DOMAIN} :/web/.well-known/acme-challenge'
24612503 # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge'
2504+ # 'ftps:ftpuserid:ftppassword:${DOMAIN} :/web/.well-known/acme-challenge'
24622505 # 'ftpes:ftpuserid:ftppassword:${DOMAIN} :/web/.well-known/acme-challenge')
24632506
24642507 # Specify SSH options, e.g. non standard port in SSH_OPTS
@@ -2592,6 +2635,12 @@ write_openssl_conf() { # write out a minimal openssl conf
25922635trap " signal_exit TERM" TERM HUP
25932636trap " signal_exit INT" INT
25942637
2638+ # When running tests, use debug mode to capture intermittent faults
2639+ # Test harness will Save output in a temporary file, which is displayed if an error occurs
2640+ if [[ ${_RUNNING_TEST} -eq 1 ]] ; then
2641+ _USE_DEBUG=1
2642+ fi
2643+
25952644# Parse command-line
25962645while [[ -n ${1+defined} ]]; do
25972646 case $1 in
@@ -2687,6 +2736,17 @@ requires sed
26872736requires sort
26882737requires mktemp
26892738
2739+ # Make sure cURL doesn't display a progress meter (if it's new enough)
2740+ # --silent also does this, but suppresses warnings and informational messages too.
2741+ # TODO: see where --silent can be removed (if _NOMETER defaults to --silent for old versions?)
2742+ # This would help with debugging transfer errors.
2743+
2744+ _CURL_VERSION= " $( curl -V | head -1 | cut -d' ' -f2 ) "
2745+ # shellcheck disable=SC2072
2746+ if [[ ! " ${_CURL_VERSION} " < " 7.67" ]]; then
2747+ _NOMETER=" --no-progress-meter"
2748+ fi
2749+
26902750# Check if upgrades are available (unless they have specified -U to ignore Upgrade checks)
26912751if [[ $_UPGRADE_CHECK -eq 1 ]]; then
26922752 check_getssl_upgrade
0 commit comments