diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml index a72c18ab..f72a1f06 100644 --- a/.github/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -61,7 +61,7 @@ jobs: // Handled by the Blacklist / Revert Command workflows. return; } - if (issue.title.startsWith("Report bugs to")) { + if (/^report\s+(bugs?|issues?)\b/i.test(issue.title)) { // Tracking issue for a third-party dns/deploy/notify api: // no upgrade boilerplate; assign it to the opener and label it. await github.rest.issues.addAssignees({ diff --git a/Dockerfile b/Dockerfile index 55a9cc67..229e4830 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,8 +81,8 @@ if [ \"\$1\" = \"daemon\" ]; then \n \ echo \"\$LE_CONFIG_HOME/crontab not found, generating one\" \n \ time=\$(date -u \"+%s\") \n \ random_minute=\$((\$time % 60)) \n \ - random_hour=\$((\$time / 60 % 24)) \n \ - echo \"\$random_minute \$random_hour * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \ + random_hour=\$((\$time / 60 % 6)) \n \ + echo \"\$random_minute \$random_hour,\$((\$random_hour + 6)),\$((\$random_hour + 12)),\$((\$random_hour + 18)) * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \ fi \n \ echo \"Running Supercronic using crontab at \$LE_CONFIG_HOME/crontab\" \n \ exec -- /usr/bin/supercronic \"\$LE_CONFIG_HOME/crontab\" \n \ diff --git a/acme.sh b/acme.sh index 4f1c0336..04002193 100755 --- a/acme.sh +++ b/acme.sh @@ -1443,13 +1443,13 @@ _readSubjectAltNamesFromCSR() { _debug _dnsAltnames "$_dnsAltnames" # escape the wildcard '*' so it is not taken as a regex operator by grep/sed below - _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')" - _debug _excapedAlgnames "$_excapedAlgnames" + _escapedAltnames="$(echo "$_dnsAltnames" | tr '*' '#')" + _debug _escapedAltnames "$_escapedAltnames" _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')" _debug _escapedSubject "$_escapedSubject" - if _contains "$_excapedAlgnames," "DNS:$_escapedSubject,"; then + if _contains "$_escapedAltnames," "DNS:$_escapedSubject,"; then _debug "AltNames contains subject" - _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" + _dnsAltnames="$(echo "$_escapedAltnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" _debug _dnsAltnames "$_dnsAltnames" else _debug "AltNames doesn't contain subject" @@ -2594,6 +2594,13 @@ _savedeployconf() { _cleardomainconf "$1" } +#key +_cleardeployconf() { + _cleardomainconf "SAVED_$1" + #remove later + _cleardomainconf "$1" +} + #key _getdeployconf() { _rac_key="$1" @@ -5343,6 +5350,8 @@ $_authorizations_map" fi _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")" test "$_d_alias" = "$NO_VALUE" && _d_alias="" + # strip the trailing dot of a fully-qualified alias domain + _d_alias="${_d_alias%.}" _alias_index="$(_math "$_alias_index" + 1)" _debug "_d_alias" "$_d_alias" if [ "$_d_alias" ]; then @@ -7411,9 +7420,9 @@ _precheck() { fi if ! _exists "socat" && ! _exists "python" && ! _exists "python2" && ! _exists "python3"; then - _err "It is recommended to install socat or python first." - _err "We use socat or python for the standalone server, which is used for standalone mode." - _err "If you don't want to use standalone mode, you may ignore this warning." + _info "It is recommended to install socat or python first." + _info "We use socat or python for the standalone server, which is used for standalone mode." + _info "If you don't want to use standalone mode, you may ignore this warning." fi return 0 @@ -7756,6 +7765,14 @@ _send_notify() { continue fi if ! ( + # The dns/deploy hooks export _H1.._H5 in the main process, so the + # values are inherited here. Clear them: a stale Authorization header + # from another service must not leak into the notify request. + export _H1="" + export _H2="" + export _H3="" + export _H4="" + export _H5="" if ! . "$_n_hook_file"; then _err "Error loading file $_n_hook_file. Please check your API file and try again." return 1 diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index ef920f64..375668ec 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -210,7 +210,7 @@ _clear_envs() { echo "$env_pairs" | while IFS='=' read -r _key _value; do _debug3 "Deleting key" "$_key" - _cleardomainconf "SAVED_$_key" + _cleardeployconf "$_key" unset -v "$_key" done } diff --git a/deploy/ssh.sh b/deploy/ssh.sh index 82b0382c..0bf3ee48 100644 --- a/deploy/ssh.sh +++ b/deploy/ssh.sh @@ -25,7 +25,8 @@ # export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value # export DEPLOY_SSH_USE_SCP="" yes or no, default to no # export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q" -# +# export DEPLOY_SSH_REMOTE_SHELL="" # defaults to sh -c +# export DEPLOY_SSH_REMOTE_CMD_QUOTE="" # yes or no, defaults to yes ######## Public functions ##################### #domain keyfile certfile cafile fullchain @@ -71,6 +72,24 @@ ssh_deploy() { fi _savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD" + # REMOTE_SHELL is optional. If not provided then use sh + _migratedeployconf Le_Deploy_ssh_remote_shell DEPLOY_SSH_REMOTE_SHELL + _getdeployconf DEPLOY_SSH_REMOTE_SHELL + _debug2 DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL" + if [ -z "$DEPLOY_SSH_REMOTE_SHELL" ]; then + DEPLOY_SSH_REMOTE_SHELL="sh -c" + fi + _savedeployconf DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL" + + # REMOTE_CMD_QUOTE is optional. If not provided then yes + _migratedeployconf Le_Deploy_ssh_remote_cmd_quote DEPLOY_SSH_REMOTE_CMD_QUOTE + _getdeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE + _debug2 DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE" + if [ -z "$DEPLOY_SSH_REMOTE_CMD_QUOTE" ]; then + DEPLOY_SSH_REMOTE_CMD_QUOTE="yes" + fi + _savedeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE" + # BACKUP is optional. If not provided then default to previously saved value or yes. _migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP _getdeployconf DEPLOY_SSH_BACKUP @@ -434,9 +453,13 @@ _ssh_remote_cmd() { _secure_debug "Remote commands to execute: $_cmd" _info "Submitting sequence of commands to remote server by $_ssh_cmd" - # quotations in bash cmd below intended. Squash travis spellcheck error - # shellcheck disable=SC2029 - $_ssh_cmd "$DEPLOY_SSH_USER@$_host" sh -c "'$_cmd'" + if [ "$DEPLOY_SSH_REMOTE_CMD_QUOTE" = "yes" ]; then + # quotations in bash cmd below intended. Squash travis spellcheck error + # shellcheck disable=SC2029 + $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "'$_cmd'" + else + $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "$_cmd" + fi _err_code="$?" if [ "$_err_code" != "0" ]; then diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index 502bc59b..75497671 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -72,7 +72,7 @@ synology_dsm_deploy() { if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then - _err "Missing required tools to creat temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead." + _err "Missing required tools to create temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead." _err "Notice: temp admin user authorization method only supports local deployment on DSM." return 1 fi @@ -234,7 +234,7 @@ synology_dsm_deploy() { fi fi - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') + error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') _debug2 error_code "$error_code" # Account has 2FA-OTP enabled, since error 403 reported. # https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf @@ -269,7 +269,7 @@ synology_dsm_deploy() { _secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID" fi fi - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') + error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') _debug2 error_code "$error_code" fi @@ -336,7 +336,7 @@ synology_dsm_deploy() { id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p") _debug2 id "$id" - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') + error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') _debug2 error_code "$error_code" if [ -n "$error_code" ]; then if [ "$error_code" -eq 105 ]; then @@ -424,11 +424,6 @@ _temp_admin_cleanup() { fi } -#_cleardeployconf key -_cleardeployconf() { - _cleardomainconf "SAVED_$1" -} - # key _check2cleardeployconfexp() { _key="$1" diff --git a/dnsapi/dns_cloudns.sh b/dnsapi/dns_cloudns.sh index 23a219da..2c543271 100755 --- a/dnsapi/dns_cloudns.sh +++ b/dnsapi/dns_cloudns.sh @@ -135,7 +135,7 @@ _dns_cloudns_init_check() { _dns_cloudns_http_api_call "dns/login.json" "" if ! _contains "$response" "\"status\":\"Success\""; then - _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials." + _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Server response: $response" return 1 fi diff --git a/dnsapi/dns_cpanel.sh b/dnsapi/dns_cpanel.sh index a6991403..6939c3f1 100755 --- a/dnsapi/dns_cpanel.sh +++ b/dnsapi/dns_cpanel.sh @@ -38,7 +38,7 @@ dns_cpanel_add() { fi # adding entry _info "Adding the entry" - stripped_fulldomain=$(echo "$fulldomain" | sed "s/.$_domain//") + stripped_fulldomain="${fulldomain%."$_domain"}" _debug "Adding $stripped_fulldomain to $_domain zone" _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1" if _successful_update; then return 0; fi @@ -128,13 +128,27 @@ _get_root() { _err "Primary domain list not found!" return 1 fi - for _domain in $_domains; do - _debug "Checking if $fulldomain ends with $_domain" - if (_endswith "$fulldomain" "$_domain"); then - _debug "Root domain: $_domain" - return 0 - fi + # Pick the LONGEST matching zone, dot-anchored: with both domain.tld and + # sub.domain.tld zones on the account, cPanel stores the record in the + # most specific zone, so add and rm must both resolve to that one. + _domain="" + for d in $_domains; do + _debug "Checking if $fulldomain ends with $d" + # case with quoted patterns gives an exact literal suffix match; + # _endswith treats the needle as a regex, so its dots would let + # xdomain.tld wrongly match zone domain.tld + case "$fulldomain" in + "$d" | *".$d") + if [ "${#d}" -gt "${#_domain}" ]; then + _domain="$d" + fi + ;; + esac done + if [ -n "$_domain" ]; then + _debug "Root domain: $_domain" + return 0 + fi return 1 } diff --git a/dnsapi/dns_creoline.sh b/dnsapi/dns_creoline.sh new file mode 100644 index 00000000..9d04af4f --- /dev/null +++ b/dnsapi/dns_creoline.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_creoline_info='creoline +Site: https://www.creoline.com/de +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_creoline +Help: https://help.creoline.com +Options: + creolineApiToken + creolineApiSecret +Issues: github.com/acmesh-official/acme.sh/issues/7103 +' + +creolineApi="https://api.creoline.com/v1" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPB8" +dns_creoline_add() { + fulldomain=$1 + txtvalue=$2 + + creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}" + creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}" + + if [ -z "$creolineApiToken" ] || [ -z "$creolineApiSecret" ]; then + _err "Error required creoline API Token or creoline API Secret not specified." + _err "Please set it with the Command 'export creolineApiToken=' and 'export creolineApiSecret='." + return 1 + else + _saveaccountconf_mutable creolineApiToken "$creolineApiToken" + _saveaccountconf_mutable creolineApiSecret "$creolineApiSecret" + fi + + _debug "Detecting the root dns zone." + if ! _get_root "$fulldomain"; then + _err "Error on detecting the root dns zone." + return 1 + fi + + _info "Adding record" + if _creoline_rest POST "dns/zone/$_domain/record" "{\"type\":\"TXT\",\"host\":\"$_sub_domain\",\"record\":\"$txtvalue\",\"ttl\":\"60\"}"; then + if _contains "$response" "$txtvalue"; then + _info "Added, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + return 1 +} + +#fulldomain txtvalue +dns_creoline_rm() { + fulldomain=$1 + txtvalue=$2 + + creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}" + creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}" + + _debug "Detecting the root dns zone." + if ! _get_root "$fulldomain"; then + _err "Error on detecting the root dns zone." + return 1 + fi + + _info "Getting earlier created txt record." + if ! _creoline_rest GET "dns/zone/$_domain/record/type/TXT/record/$txtvalue"; then + if _contains "$response" "errors" || _contains "$response" "message"; then + _err "Error on getting earlier created txt record." + return 1 + fi + _err "Error on getting earlier created txt record." + return 1 + fi + + record_id=$(echo "$response" | _egrep_o "\"id\"[[:space:]]*:[[:space:]]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _debug "record_id" "$record_id" + + if [ -z "$record_id" ]; then + _err "Error on deleting earlier created txt record. No record id found in response." + return 1 + fi + + _info "Deleting earlier created txt record." + if ! _creoline_rest DELETE "dns/zone/$_domain/record/$record_id"; then + if _contains "$response" "errors" || _contains "$response" "message"; then + _err "Error on deleting earlier created txt record." + return 1 + fi + _err "Error on deleting earlier created txt record." + return 1 + fi + + _info "Deleted, OK" + return 0 +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + if ! _creoline_rest GET "dns/zone/root/$domain"; then + return 1 + fi + + _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _debug _sub_domain "$_sub_domain" + + _domain=$(echo "$response" | _egrep_o "\"domain\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + _debug _domain "$_domain" + + if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then + return 1 + fi +} + +_creoline_rest() { + method=$1 + uri="$2" + data="$3" + timestamp=$(_time) + canonical_request="${timestamp}.${creolineApi}/${uri}" + signature_hash=$(printf "%s" "$canonical_request" | _hmac sha256 "$(printf "%s" "$creolineApiSecret" | _hex_dump | tr -d " ")" hex) + + _debug method "$method" + _debug uri "$uri" + _debug data "$data" + + _debug2 timestamp "$timestamp" + _debug2 canonical_request "$canonical_request" + _debug2 signature_hash "$signature_hash" + + token_trimmed=$(echo "$creolineApiToken" | tr -d '"') + hmac_trimmed=$(echo "$signature_hash" | tr -d '"') + + export _H1="Content-Type: application/json" + + if [ "$token_trimmed" ]; then + export _H2="X-Api-Token: $token_trimmed" + fi + + if [ "$hmac_trimmed" ]; then + export _H3="X-Creoline-Api-Signature: $hmac_trimmed" + fi + + if [ "$timestamp" ]; then + export _H4="X-Creoline-Api-Timestamp: $timestamp" + fi + + if [ "$method" != "GET" ]; then + response="$(_post "$data" "$creolineApi/$uri" "" "$method")" + else + response="$(_get "$creolineApi/$uri")" + fi + + if [ "$?" != "0" ]; then + _err "error $uri" + return 1 + fi + + _debug response "$response" + + if _contains "$response" "errors"; then + error=$(echo "$response" | _egrep_o "\"errors\":[[]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | tr -d "[") + _err "Error: $error" + _err "URI:$uri" + return 1 + elif _contains "$response" "message"; then + message=$(echo "$response" | _egrep_o "\"message\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \") + _err "Error: $message" + _err "URI:$uri" + return 1 + fi + + return 0 +} diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 13d9f68b..8ea86c24 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -305,7 +305,7 @@ _freedns_domain_id() { fi domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' | - grep "$search_domain\|$search_domain(.*)" | + grep -E "$search_domain|$search_domain\(.*\)" | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' | cut -d = -f 2)" # The above beauty extracts domain ID from the html page... diff --git a/dnsapi/dns_hostinger.sh b/dnsapi/dns_hostinger.sh new file mode 100755 index 00000000..665c65da --- /dev/null +++ b/dnsapi/dns_hostinger.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_hostinger_info='Hostinger +Site: Hostinger.com +Domains: hostinger.nl +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hostinger +Options: + HOSTINGER_Token API Key +Issues: https://github.com/acmesh-official/acme.sh/issues/6831 +Author: Sasha Reid +' + +HOSTINGER_Api="https://developers.hostinger.com/api/dns/v1/zones" + +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_hostinger_add() { + fulldomain=$1 + txtvalue=$2 + + HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}" + + if [ -z "$HOSTINGER_Token" ]; then + HOSTINGER_Token="" + _err "You didn't specify a Hostinger API Key yet." + _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication" + return 1 + fi + _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting existing records" + _hostinger_rest GET "${_domain}" + + if [ -z "$response" ]; then + _err "Error" + return 1 + fi + + # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so + # we can not use updating anymore. + # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) + # _debug count "$count" + # if [ "$count" = "0" ]; then + _info "Adding record" + if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [{\"content\":\"$txtvalue\"}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":false}"; then + if _contains "$response" "Request accepted"; then + _info "Added, OK" + return 0 + elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" || + _contains "$response" 'DNS:4008'; then + _info "Already exists, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + return 1 + +} + +#fulldomain txtvalue +dns_hostinger_rm() { + fulldomain=$1 + txtvalue=$2 + + HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}" + + if [ -z "$HOSTINGER_Token" ]; then + HOSTINGER_Token="" + _err "You didn't specify a Hostinger API Key yet." + _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication" + return 1 + fi + _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting existing records" + _hostinger_rest GET "${_domain}" + + if [ -z "$response" ]; then + _err "Error" + return 1 + fi + + if _contains "$response" "\"name\":\"$_sub_domain\""; then + # Match the record, and make certain it is a TXT record for the domain not another type. Then remove our target record from the list + remaining_records=$(echo "$response" | _normalizeJson | _egrep_o '{"name":"'"$_sub_domain"'","records":\[[^]]+\],"ttl":[0-9]+,"type":"TXT"\}' | _egrep_o "\[.*\]" | sed -E 's#\{"content":"\\"'"$txtvalue"'\\"","is_disabled":false\},?##g') + if [ "$remaining_records" != "[]" ]; then + remaining_json=$(echo "$remaining_records" | _egrep_o '"content":"\\"[^}]+\\""' | sed -E 's/^(.*)$/{\1},/g' | tr -d '\n' | sed 's/,$//') + # We need to set the remaining records back to Hostinger, as we can't partially delete + _info "Removing $txtvalue from $_sub_domain by setting records to ${remaining_json}" + if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [${remaining_json}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":true}"; then + if _contains "$response" "Request accepted"; then + _info "Updated remaining records, OK" + return 0 + elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" || + _contains "$response" 'DNS:4008'; then + _info "Already exists, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + # Otherwise delete the TXT record that matches the subdomain + else + if ! _hostinger_rest DELETE "$_domain" "{\"filters\":[{\"name\":\"$_sub_domain\",\"type\":\"TXT\"}]}"; then + _err "Delete record error." + return 1 + fi + fi + echo "$response" | grep "Request accepted" >/dev/null + else + _info "Don't need to remove." + fi + +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + i=1 + p=1 + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + _hostinger_rest GET "$h" + if _contains "$response" "records"; then + if [ "$response" = "[]" ]; then + _debug "Valid subdomains are not the root" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain=$h + return 0 + fi + fi + + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_hostinger_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + token_trimmed=$(echo "$HOSTINGER_Token" | tr -d '"') + + export _H1="Content-Type: application/json" + export _H2="Authorization: Bearer $token_trimmed" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$HOSTINGER_Api/$ep" "" "$m")" + else + response="$(_get "$HOSTINGER_Api/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index edc789e1..bd6bfb28 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/dnsapi/dns_ispconfig.sh @@ -136,7 +136,7 @@ _ISPC_getZoneInfo() { curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")" _debug "Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'" _debug "Result of _ISPC_ClientGetID: '$curResult'" - client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | tr -d '{}') + client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d '[' -f 1 | tr -d '{}') _debug "Client ID: '${client_id}'" case "${client_id}" in '' | *[!0-9]*) diff --git a/dnsapi/dns_me.sh b/dnsapi/dns_me.sh index 163fe8db..0966c5f1 100644 --- a/dnsapi/dns_me.sh +++ b/dnsapi/dns_me.sh @@ -140,7 +140,7 @@ _me_rest() { data="$3" _debug "$ep" - cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z") + cdate=$(LC_ALL=C date -u +"%a, %d %b %Y %T %Z") hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) export _H1="x-dnsme-apiKey: $ME_Key" diff --git a/dnsapi/dns_namecom.sh b/dnsapi/dns_namecom.sh index 1ba6a6e5..bd7da0c2 100755 --- a/dnsapi/dns_namecom.sh +++ b/dnsapi/dns_namecom.sh @@ -15,7 +15,7 @@ Namecom_API="https://api.name.com/v4" #Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_namecom_add() { - fulldomain=$1 + fulldomain=$(_idn "$1") txtvalue=$2 Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}" @@ -68,7 +68,7 @@ dns_namecom_add() { #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_namecom_rm() { - fulldomain=$1 + fulldomain=$(_idn "$1") txtvalue=$2 Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}" diff --git a/dnsapi/dns_njalla.sh b/dnsapi/dns_njalla.sh index c410447d..6ce51380 100644 --- a/dnsapi/dns_njalla.sh +++ b/dnsapi/dns_njalla.sh @@ -98,7 +98,7 @@ dns_njalla_rm() { echo "$records" | while read -r record; do record_name=$(echo "$record" | _egrep_o "\"name\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") record_content=$(echo "$record" | _egrep_o "\"content\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") - record_id=$(echo "$record" | _egrep_o "\"id\":\s?[0-9]+" | cut -d : -f 2 | tr -d " " | tr -d \") + record_id=$(echo "$record" | _egrep_o "\"id\":\s?\"?[^\",}]*" | cut -d : -f 2 | tr -d " " | tr -d \") if [ "$_sub_domain" = "$record_name" ]; then if [ "$txtvalue" = "$record_content" ]; then _debug "record_id" "$record_id" diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh index ec19ad25..847a1af1 100755 --- a/dnsapi/dns_pdns.sh +++ b/dnsapi/dns_pdns.sh @@ -50,6 +50,9 @@ dns_pdns_add() { PDNS_Ttl="$DEFAULT_PDNS_TTL" fi + # Ensure PDNS_Url has no trailing slash ('/') + PDNS_Url="${PDNS_Url%/}" + #save the api addr and key to the account conf file. _saveaccountconf_mutable PDNS_Url "$PDNS_Url" _saveaccountconf_mutable PDNS_ServerId "$PDNS_ServerId" diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 465bcc60..176f329d 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -151,8 +151,8 @@ dns_pleskxml_rm() { # Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value recid="$( _value "$reclist" | - grep -i "${fulldomain}." | - grep "${txtvalue}" | + grep -Fi "${fulldomain}." | + grep -F "${txtvalue}" | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/' )" @@ -419,7 +419,7 @@ _pleskxml_get_root_domain() { _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" + root_domain_id="$(_value "$output" | grep -F "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match diff --git a/dnsapi/dns_selfhost.sh b/dnsapi/dns_selfhost.sh index 40cc0210..782a5d5f 100644 --- a/dnsapi/dns_selfhost.sh +++ b/dnsapi/dns_selfhost.sh @@ -7,6 +7,7 @@ Options: SELFHOSTDNS_USERNAME Username SELFHOSTDNS_PASSWORD Password SELFHOSTDNS_MAP Subdomain name + SELFHOSTDNS_UPDATE_URL API url. Optional. Default "https://account.selfhost.de/cgi-bin/api.pl" Issues: github.com/acmesh-official/acme.sh/issues/4291 Author: Marvin Edeler ' @@ -18,9 +19,11 @@ dns_selfhost_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txt" - SELFHOSTDNS_UPDATE_URL="https://account.selfhost.de/cgi-bin/api.pl" + DEFAULT_SELFHOSTDNS_UPDATE_URL="https://account.selfhost.de/cgi-bin/api.pl" # Get values, but don't save until we successfully validated + SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$(_readaccountconf_mutable SELFHOSTDNS_UPDATE_URL)}" + SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$DEFAULT_SELFHOSTDNS_UPDATE_URL}" SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}" SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}" # These values are domain dependent, so read them from there @@ -84,6 +87,11 @@ dns_selfhost_add() { fi fi + # Save api url if different from default + if [ "$DEFAULT_SELFHOSTDNS_UPDATE_URL" != "$SELFHOSTDNS_UPDATE_URL" ]; then + _saveaccountconf_mutable SELFHOSTDNS_UPDATE_URL "$SELFHOSTDNS_UPDATE_URL" + fi + # Now that we know the values are good, save them _saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME" _saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD" diff --git a/notify/smtp.sh b/notify/smtp.sh index f5ebebca..a7318692 100644 --- a/notify/smtp.sh +++ b/notify/smtp.sh @@ -200,6 +200,7 @@ _smtp_send_curl() { set -- "$@" \ --upload-file - \ + --crlf \ --mail-from "$SMTP_FROM" \ --max-time "$SMTP_TIMEOUT"