diff --git a/acme.sh b/acme.sh index 9842e3f17a..3b763c066d 100755 --- a/acme.sh +++ b/acme.sh @@ -6061,7 +6061,7 @@ installcronjob() { _script="$(_readlink "$_SCRIPT_")" _debug _script "$_script" if [ -f "$_script" ]; then - _info "Usinging the current script from: $_script" + _info "Using the current script from: $_script" lesh="$_script" else _err "Cannot install cronjob, $PROJECT_ENTRY not found." @@ -6813,7 +6813,7 @@ _send_notify() { _nsource="$NOTIFY_SOURCE" if [ -z "$_nsource" ]; then - _nsource="$(hostname)" + _nsource="$(uname -n)" fi _nsubject="$_nsubject by $_nsource" diff --git a/deploy/proxmoxbs.sh b/deploy/proxmoxbs.sh new file mode 100644 index 0000000000..d114645477 --- /dev/null +++ b/deploy/proxmoxbs.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env sh + +# Deploy certificates to a proxmox backup server using the API. +# +# Environment variables that can be set are: +# `DEPLOY_PROXMOXBS_SERVER`: The hostname of the proxmox backup server. Defaults to +# _cdomain. +# `DEPLOY_PROXMOXBS_SERVER_PORT`: The port number the management interface is on. +# Defaults to 8007. +# `DEPLOY_PROXMOXBS_USER`: The user we'll connect as. Defaults to root. +# `DEPLOY_PROXMOXBS_USER_REALM`: The authentication realm the user authenticates +# with. Defaults to pam. +# `DEPLOY_PROXMOXBS_API_TOKEN_NAME`: The name of the API token created for the +# user account. Defaults to acme. +# `DEPLOY_PROXMOXBS_API_TOKEN_KEY`: The API token. Required. + +proxmoxbs_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug2 _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + # "Sane" defaults. + _getdeployconf DEPLOY_PROXMOXBS_SERVER + if [ -z "$DEPLOY_PROXMOXBS_SERVER" ]; then + _target_hostname="$_cdomain" + else + _target_hostname="$DEPLOY_PROXMOXBS_SERVER" + _savedeployconf DEPLOY_PROXMOXBS_SERVER "$DEPLOY_PROXMOXBS_SERVER" + fi + _debug2 DEPLOY_PROXMOXBS_SERVER "$_target_hostname" + + _getdeployconf DEPLOY_PROXMOXBS_SERVER_PORT + if [ -z "$DEPLOY_PROXMOXBS_SERVER_PORT" ]; then + _target_port="8007" + else + _target_port="$DEPLOY_PROXMOXBS_SERVER_PORT" + _savedeployconf DEPLOY_PROXMOXBS_SERVER_PORT "$DEPLOY_PROXMOXBS_SERVER_PORT" + fi + _debug2 DEPLOY_PROXMOXBS_SERVER_PORT "$_target_port" + + # Complete URL. + _target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/localhost/certificates/custom" + _debug TARGET_URL "$_target_url" + + # More "sane" defaults. + _getdeployconf DEPLOY_PROXMOXBS_USER + if [ -z "$DEPLOY_PROXMOXBS_USER" ]; then + _proxmoxbs_user="root" + else + _proxmoxbs_user="$DEPLOY_PROXMOXBS_USER" + _savedeployconf DEPLOY_PROXMOXBS_USER "$DEPLOY_PROXMOXBS_USER" + fi + _debug2 DEPLOY_PROXMOXBS_USER "$_proxmoxbs_user" + + _getdeployconf DEPLOY_PROXMOXBS_USER_REALM + if [ -z "$DEPLOY_PROXMOXBS_USER_REALM" ]; then + _proxmoxbs_user_realm="pam" + else + _proxmoxbs_user_realm="$DEPLOY_PROXMOXBS_USER_REALM" + _savedeployconf DEPLOY_PROXMOXBS_USER_REALM "$DEPLOY_PROXMOXBS_USER_REALM" + fi + _debug2 DEPLOY_PROXMOXBS_USER_REALM "$_proxmoxbs_user_realm" + + _getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME + if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" ]; then + _proxmoxbs_api_token_name="acme" + else + _proxmoxbs_api_token_name="$DEPLOY_PROXMOXBS_API_TOKEN_NAME" + _savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" + fi + _debug2 DEPLOY_PROXMOXBS_API_TOKEN_NAME "$_proxmoxbs_api_token_name" + + # This is required. + _getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY + if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" ]; then + _err "API key not provided." + return 1 + else + _proxmoxbs_api_token_key="$DEPLOY_PROXMOXBS_API_TOKEN_KEY" + _savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" + fi + _debug2 DEPLOY_PROXMOXBS_API_TOKEN_KEY "$_proxmoxbs_api_token_key" + + # PBS API Token header value. Used in "Authorization: PBSAPIToken". + _proxmoxbs_header_api_token="${_proxmoxbs_user}@${_proxmoxbs_user_realm}!${_proxmoxbs_api_token_name}:${_proxmoxbs_api_token_key}" + _debug2 "Auth Header" "$_proxmoxbs_header_api_token" + + # Ugly. I hate putting heredocs inside functions because heredocs don't + # account for whitespace correctly but it _does_ work and is several times + # cleaner than anything else I had here. + # + # This dumps the json payload to a variable that should be passable to the + # _psot function. + _json_payload=$( + cat </ui/apikeys +# export DEPLOY_TRUENAS_APIKEY="/dev/null 2>&1 # fail quietly if we're not running as root + fi + # Update unifi service for certificate cipher compatibility + _unifi_system_properties="${DEPLOY_UNIFI_SYSTEM_PROPERTIES:-/usr/lib/unifi/data/system.properties}" if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \ -in "$_import_pkcs12" \ -password pass:aircontrolenterprise \ -nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \ -noout | grep -i "signature" | grep -iq ecdsa >/dev/null 2>&1; then - cp -f /usr/lib/unifi/data/system.properties /usr/lib/unifi/data/system.properties_original - _info "Updating system configuration for cipher compatibility." - _info "Saved original system config to /usr/lib/unifi/data/system.properties_original" - sed -i '/unifi\.https\.ciphers/d' /usr/lib/unifi/data/system.properties - echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>/usr/lib/unifi/data/system.properties - sed -i '/unifi\.https\.sslEnabledProtocols/d' /usr/lib/unifi/data/system.properties - echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>/usr/lib/unifi/data/system.properties - _info "System configuration updated." + if [ -f "$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties" ]; then + _unifi_system_properties="$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties" + else + _unifi_system_properties="/usr/lib/unifi/data/system.properties" + fi + if [ -f "${_unifi_system_properties}" ]; then + cp -f "${_unifi_system_properties}" "${_unifi_system_properties}"_original + _info "Updating system configuration for cipher compatibility." + _info "Saved original system config to ${_unifi_system_properties}_original" + sed -i '/unifi\.https\.ciphers/d' "${_unifi_system_properties}" + echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>"${_unifi_system_properties}" + sed -i '/unifi\.https\.sslEnabledProtocols/d' "${_unifi_system_properties}" + echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>"${_unifi_system_properties}" + _info "System configuration updated." + fi fi rm "$_import_pkcs12" # Restarting unifi-core will bring up unifi, doing it out of order results in # a certificate error, and breaks wifiman. - # Restart if we aren't doing unifi-core, otherwise stop for later restart. - if systemctl -q is-active unifi; then - if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then - _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi" - else - _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl stop unifi" - fi + # Restart if we aren't doing Unifi OS (e.g. unifi-core service), otherwise stop for later restart. + _unifi_reload="${DEPLOY_UNIFI_RELOAD:-systemctl restart unifi}" + if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then + _reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload" + else + _info "Stopping Unifi Controller for later restart." + _unifi_stop=$(echo "${_unifi_reload}" | sed -e 's/restart/stop/') + $_unifi_stop + _reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload" + _info "Unifi Controller stopped." fi _services_updated="${_services_updated} unifi" _info "Install Unifi Controller certificate success!" @@ -181,13 +207,24 @@ unifi_deploy() { return 1 fi # Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks. - # Normally /usr/lib/unifi/data/keystore is a symlink there (so the keystore was - # updated above), but if not, we don't know how to handle this installation: - if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then - _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'" - return 1 + # It appears that unifi won't start if this is a symlink, so we'll copy it instead. + + # if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then + # _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'" + # return 1 + # fi + + _info "Updating ${_cloudkey_certdir}/unifi.keystore.jks" + if [ -e "${_cloudkey_certdir}/unifi.keystore.jks" ]; then + if [ -L "${_cloudkey_certdir}/unifi.keystore.jks" ]; then + rm -f "${_cloudkey_certdir}/unifi.keystore.jks" + else + mv "${_cloudkey_certdir}/unifi.keystore.jks" "${_cloudkey_certdir}/unifi.keystore.jks_original" + fi fi + cp "${_unifi_keystore}" "${_cloudkey_certdir}/unifi.keystore.jks" + cat "$_cfullchain" >"${_cloudkey_certdir}/cloudkey.crt" cat "$_ckey" >"${_cloudkey_certdir}/cloudkey.key" (cd "$_cloudkey_certdir" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks) @@ -215,14 +252,14 @@ unifi_deploy() { # Save the existing certs in case something goes wrong. cp -f "${_unifi_core_config}"/unifi-core.crt "${_unifi_core_config}"/unifi-core_original.crt cp -f "${_unifi_core_config}"/unifi-core.key "${_unifi_core_config}"/unifi-core_original.key - _info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt/key." + _info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt.key." cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt" cat "$_ckey" >"${_unifi_core_config}/unifi-core.key" - if systemctl -q is-active unifi-core; then - _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi-core" - fi + _unifi_os_reload="${DEPLOY_UNIFI_OS_RELOAD:-systemctl restart unifi-core}" + _reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_os_reload" + _info "Install UnifiOS certificate success!" _services_updated="${_services_updated} unifi-core" elif [ "$DEPLOY_UNIFI_CORE_CONFIG" ]; then @@ -261,6 +298,8 @@ unifi_deploy() { _savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR" _savedeployconf DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG" _savedeployconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD" + _savedeployconf DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD" + _savedeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES" return 0 } diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh index 3f0dfa3dfc..03feaf6384 100644 --- a/dnsapi/dns_azure.sh +++ b/dnsapi/dns_azure.sh @@ -9,7 +9,7 @@ Options: AZUREDNS_APPID App ID. App ID of the service principal AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. "true"/"false" - AZUREDNS_BEARERTOKEN Optional Bearer Token. Used instead of service principal credentials or managed identity + AZUREDNS_BEARERTOKEN Bearer Token. Used instead of service principal credentials or managed identity. Optional. ' wiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh new file mode 100755 index 0000000000..aa43caedbc --- /dev/null +++ b/dnsapi/dns_beget.sh @@ -0,0 +1,281 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_beget_info='Beget.com +Site: Beget.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_beget +Options: + BEGET_User API user + BEGET_Password API password +Issues: github.com/acmesh-official/acme.sh/issues/6200 +Author: ARNik arnik@arnik.ru +' + +Beget_Api="https://api.beget.com/api" + +#################### Public functions #################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_beget_add() { + fulldomain=$1 + txtvalue=$2 + _debug "dns_beget_add() $fulldomain $txtvalue" + fulldomain=$(echo "$fulldomain" | _lower_case) + + Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" + Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" + + if [ -z "$Beget_Username" ] || [ -z "$Beget_Password" ]; then + Beget_Username="" + Beget_Password="" + _err "You must export variables: Beget_Username, and Beget_Password" + return 1 + fi + + #save the credentials to the account conf file. + _saveaccountconf_mutable Beget_Username "$Beget_Username" + _saveaccountconf_mutable Beget_Password "$Beget_Password" + + _info "Prepare subdomain." + if ! _prepare_subdomain "$fulldomain"; then + _err "Can't prepare subdomain." + return 1 + fi + + _info "Get domain records" + data="{\"fqdn\":\"$fulldomain\"}" + res=$(_api_call "$Beget_Api/dns/getData" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't get domain records." + return 1 + fi + + _info "Add new TXT record" + data="{\"fqdn\":\"$fulldomain\",\"records\":{" + data=${data}$(_parce_records "$res" "A") + data=${data}$(_parce_records "$res" "AAAA") + data=${data}$(_parce_records "$res" "CAA") + data=${data}$(_parce_records "$res" "MX") + data=${data}$(_parce_records "$res" "SRV") + data=${data}$(_parce_records "$res" "TXT") + data=$(echo "$data" | sed 's/,$//') + data=${data}'}}' + + str=$(_txt_to_dns_json "$txtvalue") + data=$(_add_record "$data" "TXT" "$str") + + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't change domain records." + return 1 + fi + + return 0 +} + +# Usage: fulldomain txtvalue +# Used to remove the txt record after validation +dns_beget_rm() { + fulldomain=$1 + txtvalue=$2 + _debug "dns_beget_rm() $fulldomain $txtvalue" + fulldomain=$(echo "$fulldomain" | _lower_case) + + Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" + Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" + + _info "Get current domain records" + data="{\"fqdn\":\"$fulldomain\"}" + res=$(_api_call "$Beget_Api/dns/getData" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't get domain records." + return 1 + fi + + _info "Remove TXT record" + data="{\"fqdn\":\"$fulldomain\",\"records\":{" + data=${data}$(_parce_records "$res" "A") + data=${data}$(_parce_records "$res" "AAAA") + data=${data}$(_parce_records "$res" "CAA") + data=${data}$(_parce_records "$res" "MX") + data=${data}$(_parce_records "$res" "SRV") + data=${data}$(_parce_records "$res" "TXT") + data=$(echo "$data" | sed 's/,$//') + data=${data}'}}' + + str=$(_txt_to_dns_json "$txtvalue") + data=$(_rm_record "$data" "$str") + + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't change domain records." + return 1 + fi + + return 0 +} + +#################### Private functions below #################### + +# Create subdomain if needed +# Usage: _prepare_subdomain [fulldomain] +_prepare_subdomain() { + fulldomain=$1 + + _info "Detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if [ -z "$_sub_domain" ]; then + _debug "$fulldomain is a root domain." + return 0 + fi + + _info "Get subdomain list" + res=$(_api_call "$Beget_Api/domain/getSubdomainList") + if ! _is_api_reply_ok "$res"; then + _err "Can't get subdomain list." + return 1 + fi + + if _contains "$res" "\"fqdn\":\"$fulldomain\""; then + _debug "Subdomain $fulldomain already exist." + return 0 + fi + + _info "Subdomain $fulldomain does not exist. Let's create one." + data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" + res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't create subdomain." + return 1 + fi + + _debug "Cleanup subdomen records" + data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _debug "Can't cleanup $fulldomain records." + fi + + data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _debug "Can't cleanup www.$fulldomain records." + fi + + return 0 +} + +# Usage: _get_root _acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=32436365 +_get_root() { + fulldomain=$1 + i=1 + p=1 + + _debug "Get domain list" + res=$(_api_call "$Beget_Api/domain/getList") + if ! _is_api_reply_ok "$res"; then + _err "Can't get domain list." + return 1 + fi + + while true; do + h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) + _debug h "$h" + + if [ -z "$h" ]; then + return 1 + fi + + if _contains "$res" "$h"; then + _domain_id=$(echo "$res" | _egrep_o "\"id\":[0-9]*,\"fqdn\":\"$h\"" | cut -d , -f1 | cut -d : -f2) + if [ "$_domain_id" ]; then + if [ "$h" != "$fulldomain" ]; then + _sub_domain=$(echo "$fulldomain" | cut -d . -f 1-"$p") + else + _sub_domain="" + fi + _domain=$h + return 0 + fi + return 1 + fi + p="$i" + i=$(_math "$i" + 1) + done + return 1 +} + +# Parce DNS records from json string +# Usage: _parce_records [j_str] [record_name] +_parce_records() { + j_str=$1 + record_name=$2 + res="\"$record_name\":[" + res=${res}$(echo "$j_str" | _egrep_o "\"$record_name\":\[.*" | cut -d '[' -f2 | cut -d ']' -f1) + res=${res}"]," + echo "$res" +} + +# Usage: _add_record [data] [record_name] [record_data] +_add_record() { + data=$1 + record_name=$2 + record_data=$3 + echo "$data" | sed "s/\"$record_name\":\[/\"$record_name\":\[$record_data,/" | sed "s/,\]/\]/" +} + +# Usage: _rm_record [data] [record_data] +_rm_record() { + data=$1 + record_data=$2 + echo "$data" | sed "s/$record_data//g" | sed "s/,\+/,/g" | + sed "s/{,/{/g" | sed "s/,}/}/g" | + sed "s/\[,/\[/g" | sed "s/,\]/\]/g" +} + +_txt_to_dns_json() { + echo "{\"ttl\":600,\"txtdata\":\"$1\"}" +} + +# Usage: _api_call [api_url] [input_data] +_api_call() { + api_url="$1" + input_data="$2" + + _debug "_api_call $api_url" + _debug "Request: $input_data" + + # res=$(curl -s -L -D ./http.header \ + # "$api_url" \ + # --data-urlencode login=$Beget_Username \ + # --data-urlencode passwd=$Beget_Password \ + # --data-urlencode input_format=json \ + # --data-urlencode output_format=json \ + # --data-urlencode "input_data=$input_data") + + url="$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json" + if [ -n "$input_data" ]; then + url=${url}"&input_data=" + url=${url}$(echo "$input_data" | _url_encode) + fi + res=$(_get "$url") + + _debug "Reply: $res" + echo "$res" +} + +# Usage: _is_api_reply_ok [api_reply] +_is_api_reply_ok() { + _contains "$1" '^{"status":"success","answer":{"status":"success","result":.*}}$' +} diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 04a515aab4..a585e77202 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -215,10 +215,8 @@ _cyon_change_domain_env() { if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi - domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)" - # Bail if domain environment change fails. - if [ "${domain_env_success}" != "true" ]; then + if [ "$(printf "%s" "${domain_env_response}" | _cyon_get_environment_change_status)" != "true" ]; then _err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)" _err "" return 1 @@ -232,7 +230,7 @@ _cyon_add_txt() { _info " - Adding DNS TXT entry..." add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async" - add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}" + add_txt_data="name=${fulldomain_idn}.&ttl=900&type=TXT&dnscontent=${txtvalue}" add_txt_response="$(_post "$add_txt_data" "$add_txt_url")" _debug add_txt_response "${add_txt_response}" @@ -241,9 +239,10 @@ _cyon_add_txt() { add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)" add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)" + add_txt_validation="$(printf "%s" "${add_txt_response}" | _cyon_get_validation_status)" # Bail if adding TXT entry fails. - if [ "${add_txt_status}" != "true" ]; then + if [ "${add_txt_status}" != "true" ] || [ "${add_txt_validation}" != "true" ]; then _err " ${add_txt_message}" _err "" return 1 @@ -305,13 +304,21 @@ _cyon_get_response_message() { } _cyon_get_response_status() { - _egrep_o '"status":\w*' | cut -d : -f 2 + _egrep_o '"status":[a-zA-z0-9]*' | cut -d : -f 2 +} + +_cyon_get_validation_status() { + _egrep_o '"valid":[a-zA-z0-9]*' | cut -d : -f 2 } _cyon_get_response_success() { _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' } +_cyon_get_environment_change_status() { + _egrep_o '"authenticated":[a-zA-z0-9]*' | cut -d : -f 2 +} + _cyon_check_if_2fa_missed() { # Did we miss the 2FA? if test "${1#*multi_factor_form}" != "${1}"; then diff --git a/dnsapi/dns_he_ddns.sh b/dnsapi/dns_he_ddns.sh new file mode 100644 index 0000000000..7d56104c9e --- /dev/null +++ b/dnsapi/dns_he_ddns.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_he_ddns_info='Hurricane Electric HE.net DDNS +Site: dns.he.net +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns +Options: + HE_DDNS_KEY The DDNS key +Author: Markku Leiniƶ +' + +HE_DDNS_URL="https://dyn.dns.he.net/nic/update" + +######## Public functions ##################### + +#Usage: dns_he_ddns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_he_ddns_add() { + fulldomain=$1 + txtvalue=$2 + HE_DDNS_KEY="${HE_DDNS_KEY:-$(_readaccountconf_mutable HE_DDNS_KEY)}" + if [ -z "$HE_DDNS_KEY" ]; then + HE_DDNS_KEY="" + _err "You didn't specify a DDNS key for accessing the TXT record in HE API." + return 1 + fi + #Save the DDNS key to the account conf file. + _saveaccountconf_mutable HE_DDNS_KEY "$HE_DDNS_KEY" + + _info "Using Hurricane Electric DDNS API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + response="$(_post "hostname=$fulldomain&password=$HE_DDNS_KEY&txt=$txtvalue" "$HE_DDNS_URL")" + _info "Response: $response" + _contains "$response" "good" && return 0 || return 1 +} + +# dns_he_ddns_rm() is not implemented because the API call always updates the +# contents of the existing record (that the API key gives access to). diff --git a/dnsapi/dns_limacity.sh b/dnsapi/dns_limacity.sh index fb12f8c6ed..5734be9e3d 100644 --- a/dnsapi/dns_limacity.sh +++ b/dnsapi/dns_limacity.sh @@ -1,13 +1,13 @@ #!/usr/bin/env sh - -# Created by Laraveluser -# -# Pass credentials before "acme.sh --issue --dns dns_limacity ..." -# -- -# export LIMACITY_APIKEY="" -# -- -# -# Pleas note: APIKEY must have following roles: dns.admin, domains.reader +# shellcheck disable=SC2034 +dns_limacity_info='lima-city.de +Site: www.lima-city.de +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_limacity +Options: + LIMACITY_APIKEY API Key. Note: The API Key must have following roles: dns.admin, domains.reader +Issues: github.com/acmesh-official/acme.sh/issues/4758 +Author: @Laraveluser +' ######## Public functions ##################### diff --git a/dnsapi/dns_mijnhost.sh b/dnsapi/dns_mijnhost.sh new file mode 100644 index 0000000000..9dafc702fc --- /dev/null +++ b/dnsapi/dns_mijnhost.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_mijnhost_info='mijn.host +Domains: mijn.host +Site: mijn.host +Docs: https://mijn.host/api/doc/ +Issues: https://github.com/acmesh-official/acme.sh/issues/6177 +Author: peterv99 +Options: + MIJNHOST_API_KEY API Key +' + +######## Public functions ###################### Constants for your mijn-host API +MIJNHOST_API="https://mijn.host/api/v2" + +# Add TXT record for domain verification +dns_mijnhost_add() { + fulldomain=$1 + txtvalue=$2 + + MIJNHOST_API_KEY="${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}" + if [ -z "$MIJNHOST_API_KEY" ]; then + MIJNHOST_API_KEY="" + _err "You haven't specified your mijn-host API key yet." + _err "Please add MIJNHOST_API_KEY to the env." + return 1 + fi + + # Save the API key for future use + _saveaccountconf_mutable MIJNHOST_API_KEY "$MIJNHOST_API_KEY" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Invalid domain" + return 1 + fi + + _debug2 _sub_domain "$_sub_domain" + _debug2 _domain "$_domain" + _debug "Adding DNS record" "${fulldomain}." + + # Construct the API URL + api_url="$MIJNHOST_API/domains/$_domain/dns" + + # Getting previous records + _mijnhost_rest GET "$api_url" "" + + if [ "$_code" != "200" ]; then + _err "Error getting current DNS enties ($_code)" + return 1 + fi + + records=$(echo "$response" | _egrep_o '"records":\[.*\]' | sed 's/"records"://') + + _debug2 "Current records" "$records" + + # Build the payload for the API + data="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"value\":\"$txtvalue\",\"ttl\":300}" + + _debug2 "Record to add" "$data" + + # Updating the records + updated_records=$(echo "$records" | sed -E "s/\]( *$)/,$data\]/") + + _debug2 "Updated records" "$updated_records" + + # data + data="{\"records\": $updated_records}" + + _mijnhost_rest PUT "$api_url" "$data" + + if [ "$_code" = "200" ]; then + _info "DNS record succesfully added." + return 0 + else + _err "Error adding DNS record ($_code)." + return 1 + fi +} + +# Remove TXT record after verification +dns_mijnhost_rm() { + fulldomain=$1 + txtvalue=$2 + + MIJNHOST_API_KEY="${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}" + if [ -z "$MIJNHOST_API_KEY" ]; then + MIJNHOST_API_KEY="" + _err "You haven't specified your mijn-host API key yet." + _err "Please add MIJNHOST_API_KEY to the env." + return 1 + fi + + _debug "Detecting root zone for" "${fulldomain}." + if ! _get_root "$fulldomain"; then + _err "Invalid domain" + return 1 + fi + + _debug "Removing DNS record for TXT value" "${txtvalue}." + + # Construct the API URL + api_url="$MIJNHOST_API/domains/$_domain/dns" + + # Get current records + _mijnhost_rest GET "$api_url" "" + + if [ "$_code" != "200" ]; then + _err "Error getting current DNS enties ($_code)" + return 1 + fi + + _debug2 "Get current records response:" "$response" + + records=$(echo "$response" | _egrep_o '"records":\[.*\]' | sed 's/"records"://') + + _debug2 "Current records:" "$records" + + updated_records=$(echo "$records" | sed -E "s/\{[^}]*\"value\":\"$txtvalue\"[^}]*\},?//g" | sed 's/,]/]/g') + + _debug2 "Updated records:" "$updated_records" + + # Build the new payload + data="{\"records\": $updated_records}" + + # Use the _put method to update the records + _mijnhost_rest PUT "$api_url" "$data" + + if [ "$_code" = "200" ]; then + _info "DNS record removed successfully." + return 0 + else + _err "Error removing DNS record ($_code)." + return 1 + fi +} + +# Helper function to detect the root zone +_get_root() { + domain=$1 + + # Get current records + _debug "Getting current domains" + _mijnhost_rest GET "$MIJNHOST_API/domains" "" + + if [ "$_code" != "200" ]; then + _err "error getting current domains ($_code)" + return 1 + fi + + # Extract root domains from response + rootDomains=$(echo "$response" | _egrep_o '"domain":"[^"]*"' | sed -E 's/"domain":"([^"]*)"/\1/') + _debug "Root domains:" "$rootDomains" + + for rootDomain in $rootDomains; do + if _contains "$domain" "$rootDomain"; then + _domain="$rootDomain" + _sub_domain=$(echo "$domain" | sed "s/.$rootDomain//g") + _debug "Found root domain" "$_domain" "and subdomain" "$_sub_domain" "for" "$domain" + return 0 + fi + done + return 1 +} + +# Helper function for rest calls +_mijnhost_rest() { + m=$1 + ep="$2" + data="$3" + + MAX_REQUEST_RETRY_TIMES=15 + _request_retry_times=0 + _retry_sleep=5 #Initial sleep time in seconds. + + while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do + _debug2 _request_retry_times "$_request_retry_times" + export _H1="API-Key: $MIJNHOST_API_KEY" + export _H2="Content-Type: application/json" + # clear headers from previous request to avoid getting wrong http code on timeouts + : >"$HTTP_HEADER" + _debug "$ep" + if [ "$m" != "GET" ]; then + _debug2 "data $data" + response="$(_post "$data" "$ep" "" "$m")" + else + response="$(_get "$ep")" + fi + _ret="$?" + _debug2 "response $response" + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" + _debug "http response code $_code" + if [ "$_code" = "401" ]; then + # we have an invalid API token, maybe it is expired? + _err "Access denied. Invalid API token." + return 1 + fi + + if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "400" ] || _contains "$response" "DNS records not managed by mijn.host"; then #Sometimes API errors out + _request_retry_times="$(_math "$_request_retry_times" + 1)" + _info "REST call error $_code retrying $ep in ${_retry_sleep}s" + _sleep "$_retry_sleep" + _retry_sleep="$(_math "$_retry_sleep" \* 2)" + continue + fi + break + done + if [ "$_request_retry_times" = "$MAX_REQUEST_RETRY_TIMES" ]; then + _err "Error mijn.host API call was retried $MAX_REQUEST_RETRY_TIMES times." + _err "Calling $ep failed." + return 1 + fi + response="$(echo "$response" | _normalizeJson)" + return 0 +} diff --git a/dnsapi/dns_myapi.sh b/dnsapi/dns_myapi.sh index c9f5eb9f04..101854d541 100755 --- a/dnsapi/dns_myapi.sh +++ b/dnsapi/dns_myapi.sh @@ -1,12 +1,14 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 dns_myapi_info='Custom API Example - A sample custom DNS API script. -Domains: example.com + A sample custom DNS API script description. +Domains: example.com example.net Site: github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_myapi Options: - MYAPI_Token API Token. Get API Token from https://example.com/api/. Optional. + MYAPI_Token API Token. Get API Token from https://example.com/api/ + MYAPI_Variable2 Option 2. Default "default value". + MYAPI_Variable2 Option 3. Optional. Issues: github.com/acmesh-official/acme.sh Author: Neil Pang ' diff --git a/dnsapi/dns_netcup.sh b/dnsapi/dns_netcup.sh index 687b99bc52..8609adf6db 100644 --- a/dnsapi/dns_netcup.sh +++ b/dnsapi/dns_netcup.sh @@ -19,7 +19,7 @@ client="" dns_netcup_add() { _debug NC_Apikey "$NC_Apikey" - login + _login if [ "$NC_Apikey" = "" ] || [ "$NC_Apipw" = "" ] || [ "$NC_CID" = "" ]; then _err "No Credentials given" return 1 @@ -61,7 +61,7 @@ dns_netcup_add() { } dns_netcup_rm() { - login + _login fulldomain=$1 txtvalue=$2 @@ -125,7 +125,7 @@ dns_netcup_rm() { logout } -login() { +_login() { tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST") sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4) _debug "$tmp" diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh index 5c137c3f1a..df080bcf04 100644 --- a/dnsapi/dns_omglol.sh +++ b/dnsapi/dns_omglol.sh @@ -4,8 +4,8 @@ dns_omglol_info='omg.lol Site: omg.lol Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_omglol Options: - OMG_ApiKey API Key from omg.lol. This is accessible from the bottom of the account page at https://home.omg.lol/account - OMG_Address This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard + OMG_ApiKey API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account + OMG_Address Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard Issues: github.com/acmesh-official/acme.sh/issues/5299 Author: @Kholin ' diff --git a/dnsapi/dns_openprovider.sh b/dnsapi/dns_openprovider.sh index b584fad2b6..2dec9934c2 100755 --- a/dnsapi/dns_openprovider.sh +++ b/dnsapi/dns_openprovider.sh @@ -2,6 +2,7 @@ # shellcheck disable=SC2034 dns_openprovider_info='OpenProvider.eu Site: OpenProvider.eu +Domains: OpenProvider.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_openprovider Options: OPENPROVIDER_USER Username diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh index 2478e19fed..ec19ad2571 100755 --- a/dnsapi/dns_pdns.sh +++ b/dnsapi/dns_pdns.sh @@ -7,7 +7,7 @@ Options: PDNS_Url API URL. E.g. "http://ns.example.com:8081" PDNS_ServerId Server ID. E.g. "localhost" PDNS_Token API Token - PDNS_Ttl=60 Domain TTL. Default: "60". + PDNS_Ttl Domain TTL. Default: "60". ' DEFAULT_PDNS_TTL=60 diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh index 8b52b24e17..434bc48304 100644 --- a/dnsapi/dns_selectel.sh +++ b/dnsapi/dns_selectel.sh @@ -1,14 +1,31 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 -dns_selectel_info='Selectel.com -Domains: Selectel.ru -Site: Selectel.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel -Options: - SL_Key API Key -' -SL_Api="https://api.selectel.ru/domains/v1" +# dns_selectel_info='Selectel.com +# Domains: Selectel.ru +# Site: Selectel.com +# Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel +# Options: +# Variables that must be defined before running +# SL_Ver can take one of the values 'v1' or 'v2', default is 'v1' +# SL_Ver='v1', when using version API legacy (v1) +# SL_Ver='v2', when using version API actual (v2) +# when using API version v1, i.e. SL_Ver is 'v1' or not defined: +# SL_Key - API Key, required +# when using API version v2: +# SL_Ver - required as 'v2' +# SL_Login_ID - account ID, required +# SL_Project_Name - name project, required +# SL_Login_Name - service user name, required +# SL_Pswd - service user password, required +# SL_Expire - token lifetime in minutes (0-1440), default 1400 minutes +# +# Issues: github.com/acmesh-official/acme.sh/issues/5126 +# + +SL_Api="https://api.selectel.ru/domains" +auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens" +_sl_sep='#' ######## Public functions ##################### @@ -17,17 +34,14 @@ dns_selectel_add() { fulldomain=$1 txtvalue=$2 - SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" - - if [ -z "$SL_Key" ]; then - SL_Key="" - _err "You don't specify selectel.ru api key yet." - _err "Please create you key and try again." + if ! _sl_init_vars; then return 1 fi - - #save the api key to the account conf file. - _saveaccountconf_mutable SL_Key "$SL_Key" + _debug2 SL_Ver "$SL_Ver" + _debug2 SL_Expire "$SL_Expire" + _debug2 SL_Login_Name "$SL_Login_Name" + _debug2 SL_Login_ID "$SL_Login_ID" + _debug2 SL_Project_Name "$SL_Project_Name" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -39,11 +53,63 @@ dns_selectel_add() { _debug _domain "$_domain" _info "Adding record" - if _sl_rest POST "/$_domain_id/records/" "{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"$fulldomain\", \"content\": \"$txtvalue\"}"; then - if _contains "$response" "$txtvalue" || _contains "$response" "record_already_exists"; then + if [ "$SL_Ver" = "v2" ]; then + _ext_srv1="/zones/" + _ext_srv2="/rrset/" + _text_tmp=$(echo "$txtvalue" | sed -En "s/[\"]*([^\"]*)/\1/p") + _text_tmp='\"'$_text_tmp'\"' + _data="{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"${fulldomain}.\", \"records\": [{\"content\":\"$_text_tmp\"}]}" + elif [ "$SL_Ver" = "v1" ]; then + _ext_srv1="/" + _ext_srv2="/records/" + _data="{\"type\":\"TXT\",\"ttl\":60,\"name\":\"$fulldomain\",\"content\":\"$txtvalue\"}" + else + _err "Error. Unsupported version API $SL_Ver" + return 1 + fi + _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}" + _debug _ext_uri "$_ext_uri" + _debug _data "$_data" + + if _sl_rest POST "$_ext_uri" "$_data"; then + if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 fi + if _contains "$response" "already_exists"; then + # record TXT with $fulldomain already exists + if [ "$SL_Ver" = "v2" ]; then + # It is necessary to add one more content to the comments + # read all records rrset + _debug "Getting txt records" + _sl_rest GET "${_ext_uri}" + # There is already a $txtvalue value, no need to add it + if _contains "$response" "$txtvalue"; then + _info "Added, OK" + _info "Txt record ${fulldomain} with value ${txtvalue} already exists" + return 0 + fi + # group \1 - full record rrset; group \2 - records attribute value, exactly {"content":"\"value1\""},{"content":"\"value2\""}",... + _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\1/p")" + _record_array="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\2/p")" + # record id + _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")" + # preparing _data + _tmp_str="${_record_array},{\"content\":\"${_text_tmp}\"}" + _data="{\"ttl\": 60, \"records\": [${_tmp_str}]}" + _debug2 _record_seg "$_record_seg" + _debug2 _record_array "$_record_array" + _debug2 _record_array "$_record_id" + _debug "New data for record" "$_data" + if _sl_rest PATCH "${_ext_uri}${_record_id}" "$_data"; then + _info "Added, OK" + return 0 + fi + elif [ "$SL_Ver" = "v1" ]; then + _info "Added, OK" + return 0 + fi + fi fi _err "Add txt record error." return 1 @@ -54,15 +120,15 @@ dns_selectel_rm() { fulldomain=$1 txtvalue=$2 - SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" - - if [ -z "$SL_Key" ]; then - SL_Key="" - _err "You don't specify slectel api key yet." - _err "Please create you key and try again." + if ! _sl_init_vars "nosave"; then return 1 fi - + _debug2 SL_Ver "$SL_Ver" + _debug2 SL_Expire "$SL_Expire" + _debug2 SL_Login_Name "$SL_Login_Name" + _debug2 SL_Login_ID "$SL_Login_ID" + _debug2 SL_Project_Name "$SL_Project_Name" + # _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" @@ -71,91 +137,195 @@ dns_selectel_rm() { _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - + # + if [ "$SL_Ver" = "v2" ]; then + _ext_srv1="/zones/" + _ext_srv2="/rrset/" + elif [ "$SL_Ver" = "v1" ]; then + _ext_srv1="/" + _ext_srv2="/records/" + else + _err "Error. Unsupported version API $SL_Ver" + return 1 + fi + # _debug "Getting txt records" - _sl_rest GET "/${_domain_id}/records/" - + _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}" + _debug _ext_uri "$_ext_uri" + _sl_rest GET "${_ext_uri}" + # if ! _contains "$response" "$txtvalue"; then _err "Txt record not found" return 1 fi - - _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")" + # + if [ "$SL_Ver" = "v2" ]; then + _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\1/gp")" + _record_arr="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/p")" + elif [ "$SL_Ver" = "v1" ]; then + _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")" + else + _err "Error. Unsupported version API $SL_Ver" + return 1 + fi _debug2 "_record_seg" "$_record_seg" if [ -z "$_record_seg" ]; then _err "can not find _record_seg" return 1 fi - - _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2)" - _debug2 "_record_id" "$_record_id" + # record id + # the following lines change the algorithm for deleting records with the value $txtvalue + # if you use the 1st line, then all such records are deleted at once + # if you use the 2nd line, then only the first entry from them is deleted + #_record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")" + _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"" | sed '1!d')" if [ -z "$_record_id" ]; then _err "can not find _record_id" return 1 fi - - if ! _sl_rest DELETE "/$_domain_id/records/$_record_id"; then - _err "Delete record error." - return 1 + _debug2 "_record_id" "$_record_id" + # delete all record type TXT with text $txtvalue + if [ "$SL_Ver" = "v2" ]; then + # actual + _new_arr="$(echo "$_record_seg" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/gp" | sed -En "s/(\},\{)/}\n{/gp" | sed "/${txtvalue}/d" | sed ":a;N;s/\n/,/;ta")" + # uri record for DEL or PATCH + _del_uri="${_ext_uri}${_record_id}" + _debug _del_uri "$_del_uri" + if [ -z "$_new_arr" ]; then + # remove record + if ! _sl_rest DELETE "${_del_uri}"; then + _err "Delete record error: ${_del_uri}." + else + info "Delete record success: ${_del_uri}." + fi + else + # update a record by removing one element in content + _data="{\"ttl\": 60, \"records\": [${_new_arr}]}" + _debug2 _data "$_data" + # REST API PATCH call + if _sl_rest PATCH "${_del_uri}" "$_data"; then + _info "Patched, OK: ${_del_uri}" + else + _err "Patched record error: ${_del_uri}." + fi + fi + else + # legacy + for _one_id in $_record_id; do + _del_uri="${_ext_uri}${_one_id}" + _debug _del_uri "$_del_uri" + if ! _sl_rest DELETE "${_del_uri}"; then + _err "Delete record error: ${_del_uri}." + else + info "Delete record success: ${_del_uri}." + fi + done fi return 0 } #################### Private functions below ################################## -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -# _domain_id=sdjkglgdfewsdfg + _get_root() { domain=$1 - if ! _sl_rest GET "/"; then - return 1 - fi - - i=2 - p=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - #not valid + if [ "$SL_Ver" = 'v1' ]; then + # version API 1 + if ! _sl_rest GET "/"; then return 1 fi - - if _contains "$response" "\"name\" *: *\"$h\","; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - _debug "Getting domain id for $h" - if ! _sl_rest GET "/$h"; then + i=2 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _debug h "$h" + if [ -z "$h" ]; then return 1 fi - _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)" - return 0 + if _contains "$response" "\"name\" *: *\"$h\","; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain=$h + _debug "Getting domain id for $h" + if ! _sl_rest GET "/$h"; then + _err "Error read records of all domains $SL_Ver" + return 1 + fi + _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)" + return 0 + fi + p=$i + i=$(_math "$i" + 1) + done + _err "Error read records of all domains $SL_Ver" + return 1 + elif [ "$SL_Ver" = "v2" ]; then + # version API 2 + _ext_uri='/zones/' + domain="${domain}." + _debug "domain:: " "$domain" + # read records of all domains + if ! _sl_rest GET "$_ext_uri"; then + _err "Error read records of all domains $SL_Ver" + return 1 fi - p=$i - i=$(_math "$i" + 1) - done - return 1 + i=1 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _debug h "$h" + if [ -z "$h" ]; then + _err "The domain was not found among the registered ones" + return 1 + fi + _domain_record=$(echo "$response" | sed -En "s/.*(\{[^}]*id[^}]*\"name\" *: *\"$h\"[^}]*}).*/\1/p") + _debug "_domain_record:: " "$_domain_record" + if [ -n "$_domain_record" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain=$h + _debug "Getting domain id for $h" + _domain_id=$(echo "$_domain_record" | sed -En "s/\{[^}]*\"id\" *: *\"([^\"]*)\"[^}]*\}/\1/p") + return 0 + fi + p=$i + i=$(_math "$i" + 1) + done + _err "Error read records of all domains $SL_Ver" + return 1 + else + _err "Error. Unsupported version API $SL_Ver" + return 1 + fi } +################################################################# +# use: method add_url body _sl_rest() { m=$1 ep="$2" data="$3" - _debug "$ep" - export _H1="X-Token: $SL_Key" + _token=$(_get_auth_token) + if [ -z "$_token" ]; then + _err "BAD key or token $ep" + return 1 + fi + if [ "$SL_Ver" = v2 ]; then + _h1_name="X-Auth-Token" + else + _h1_name='X-Token' + fi + export _H1="${_h1_name}: ${_token}" export _H2="Content-Type: application/json" - + _debug2 "Full URI: " "$SL_Api/${SL_Ver}${ep}" + _debug2 "_H1:" "$_H1" + _debug2 "_H2:" "$_H2" if [ "$m" != "GET" ]; then _debug data "$data" - response="$(_post "$data" "$SL_Api/$ep" "" "$m")" + response="$(_post "$data" "$SL_Api/${SL_Ver}${ep}" "" "$m")" else - response="$(_get "$SL_Api/$ep")" + response="$(_get "$SL_Api/${SL_Ver}${ep}")" fi - + # shellcheck disable=SC2181 if [ "$?" != "0" ]; then _err "error $ep" return 1 @@ -163,3 +333,152 @@ _sl_rest() { _debug2 response "$response" return 0 } + +_get_auth_token() { + if [ "$SL_Ver" = 'v1' ]; then + # token for v1 + _debug "Token v1" + _token_keystone=$SL_Key + elif [ "$SL_Ver" = 'v2' ]; then + # token for v2. Get a token for calling the API + _debug "Keystone Token v2" + token_v2=$(_readaccountconf_mutable SL_Token_V2) + if [ -n "$token_v2" ]; then + # The structure with the token was considered. Let's check its validity + # field 1 - SL_Login_Name + # field 2 - token keystone + # field 3 - SL_Login_ID + # field 4 - SL_Project_Name + # field 5 - Receipt time + # separator - '$_sl_sep' + _login_name=$(_getfield "$token_v2" 1 "$_sl_sep") + _token_keystone=$(_getfield "$token_v2" 2 "$_sl_sep") + _project_name=$(_getfield "$token_v2" 4 "$_sl_sep") + _receipt_time=$(_getfield "$token_v2" 5 "$_sl_sep") + _login_id=$(_getfield "$token_v2" 3 "$_sl_sep") + _debug2 _login_name "$_login_name" + _debug2 _login_id "$_login_id" + _debug2 _project_name "$_project_name" + # check the validity of the token for the user and the project and its lifetime + _dt_diff_minute=$((($(date +%s) - _receipt_time) / 60)) + _debug2 _dt_diff_minute "$_dt_diff_minute" + [ "$_dt_diff_minute" -gt "$SL_Expire" ] && unset _token_keystone + if [ "$_project_name" != "$SL_Project_Name" ] || [ "$_login_name" != "$SL_Login_Name" ] || [ "$_login_id" != "$SL_Login_ID" ]; then + unset _token_keystone + fi + _debug "Get exists token" + fi + if [ -z "$_token_keystone" ]; then + # the previous token is incorrect or was not received, get a new one + _debug "Update (get new) token" + _data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}" + export _H1="Content-Type: application/json" + _result=$(_post "$_data_auth" "$auth_uri") + _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | sed -nE "s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\r*)/\1/p") + _dt_curr=$(date +%s) + SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}" + _saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2" + fi + else + # token set empty for unsupported version API + _token_keystone="" + fi + printf -- "%s" "$_token_keystone" +} + +################################################################# +# use: [non_save] +_sl_init_vars() { + _non_save="${1}" + _debug2 _non_save "$_non_save" + + _debug "First init variables" + # version API + SL_Ver="${SL_Ver:-$(_readaccountconf_mutable SL_Ver)}" + if [ -z "$SL_Ver" ]; then + SL_Ver="v1" + fi + if ! [ "$SL_Ver" = "v1" ] && ! [ "$SL_Ver" = "v2" ]; then + _err "You don't specify selectel.ru API version." + _err "Please define specify API version." + fi + _debug2 SL_Ver "$SL_Ver" + if [ "$SL_Ver" = "v1" ]; then + # token + SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" + + if [ -z "$SL_Key" ]; then + SL_Key="" + _err "You don't specify selectel.ru api key yet." + _err "Please create you key and try again." + return 1 + fi + #save the api key to the account conf file. + if [ -z "$_non_save" ]; then + _saveaccountconf_mutable SL_Key "$SL_Key" + fi + elif [ "$SL_Ver" = "v2" ]; then + # time expire token + SL_Expire="${SL_Expire:-$(_readaccountconf_mutable SL_Expire)}" + if [ -z "$SL_Expire" ]; then + SL_Expire=1400 # 23h 20 min + fi + if [ -z "$_non_save" ]; then + _saveaccountconf_mutable SL_Expire "$SL_Expire" + fi + # login service user + SL_Login_Name="${SL_Login_Name:-$(_readaccountconf_mutable SL_Login_Name)}" + if [ -z "$SL_Login_Name" ]; then + SL_Login_Name='' + _err "You did not specify the selectel.ru API service user name." + _err "Please provide a service user name and try again." + return 1 + fi + if [ -z "$_non_save" ]; then + _saveaccountconf_mutable SL_Login_Name "$SL_Login_Name" + fi + # user ID + SL_Login_ID="${SL_Login_ID:-$(_readaccountconf_mutable SL_Login_ID)}" + if [ -z "$SL_Login_ID" ]; then + SL_Login_ID='' + _err "You did not specify the selectel.ru API user ID." + _err "Please provide a user ID and try again." + return 1 + fi + if [ -z "$_non_save" ]; then + _saveaccountconf_mutable SL_Login_ID "$SL_Login_ID" + fi + # project name + SL_Project_Name="${SL_Project_Name:-$(_readaccountconf_mutable SL_Project_Name)}" + if [ -z "$SL_Project_Name" ]; then + SL_Project_Name='' + _err "You did not specify the project name." + _err "Please provide a project name and try again." + return 1 + fi + if [ -z "$_non_save" ]; then + _saveaccountconf_mutable SL_Project_Name "$SL_Project_Name" + fi + # service user password + SL_Pswd="${SL_Pswd:-$(_readaccountconf_mutable SL_Pswd)}" + if [ -z "$SL_Pswd" ]; then + SL_Pswd='' + _err "You did not specify the service user password." + _err "Please provide a service user password and try again." + return 1 + fi + if [ -z "$_non_save" ]; then + _saveaccountconf_mutable SL_Pswd "$SL_Pswd" "12345678" + fi + else + SL_Ver="" + _err "You also specified the wrong version of the selectel.ru API." + _err "Please provide the correct API version and try again." + return 1 + fi + if [ -z "$_non_save" ]; then + _saveaccountconf_mutable SL_Ver "$SL_Ver" + fi + + return 0 +} diff --git a/dnsapi/dns_technitium.sh b/dnsapi/dns_technitium.sh index a50db97c4d..7bc0dd482e 100755 --- a/dnsapi/dns_technitium.sh +++ b/dnsapi/dns_technitium.sh @@ -1,13 +1,12 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 -dns_Technitium_info='Technitium DNS Server - -Site: https://technitium.com/dns/ +dns_technitium_info='Technitium DNS Server +Site: Technitium.com/dns/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_technitium Options: Technitium_Server Server Address Technitium_Token API Token -Issues:https://github.com/acmesh-official/acme.sh/issues/6116 +Issues: github.com/acmesh-official/acme.sh/issues/6116 Author: Henning Reich ' diff --git a/dnsapi/dns_west_cn.sh b/dnsapi/dns_west_cn.sh index d0bb7d490f..b873bfc038 100644 --- a/dnsapi/dns_west_cn.sh +++ b/dnsapi/dns_west_cn.sh @@ -1,9 +1,13 @@ #!/usr/bin/env sh - -# West.cn Domain api -#WEST_Username="username" -#WEST_Key="sADDsdasdgdsf" -#Set key at https://www.west.cn/manager/API/APIconfig.asp +# shellcheck disable=SC2034 +dns_west_cn_info='West.cn +Site: West.cn +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_west_cn +Options: + WEST_Username API username + WEST_Key API Key. Set at https://www.west.cn/manager/API/APIconfig.asp +Issues: github.com/acmesh-official/acme.sh/issues/4894 +' REST_API="https://api.west.cn/API/v2" diff --git a/dnsapi/dns_yandex360.sh b/dnsapi/dns_yandex360.sh index c6b6053df5..18d013613e 100644 --- a/dnsapi/dns_yandex360.sh +++ b/dnsapi/dns_yandex360.sh @@ -1,7 +1,7 @@ #!/usr/bin/env sh # shellcheck disable=SC2034 dns_yandex360_info='Yandex 360 for Business DNS API. -Yandex 360 for Business is a digital environment for effective collaboration. + Yandex 360 for Business is a digital environment for effective collaboration. Site: https://360.yandex.com/ Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360 Options: diff --git a/dnsapi/dns_zoneedit.sh b/dnsapi/dns_zoneedit.sh index df01d8cf6f..77553f3cf3 100644 --- a/dnsapi/dns_zoneedit.sh +++ b/dnsapi/dns_zoneedit.sh @@ -1,19 +1,23 @@ #!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_zoneedit_info='ZoneEdit.com +Site: ZoneEdit.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_zoneedit +Options: + ZONEEDIT_ID ID + ZONEEDIT_Token API Token +Issues: github.com/acmesh-official/acme.sh/issues/6135 +' # https://github.com/blueslow/sslcertzoneedit -# Only need to export the credentials once, acme.sh will save for automatic renewal. -# export ZONEEDIT_ID="Your id" -# export ZONEEDIT_Token="Your token" -# acme.sh --issue --dns dns_zoneedit -d example.com -d www.example.com - ######## Public functions ##################### # Usage: dns_zoneedit_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_zoneedit_add() { fulldomain=$1 txtvalue=$2 - _info "Using Zoneedit" + _info "Using ZoneEdit" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" @@ -45,7 +49,7 @@ dns_zoneedit_add() { dns_zoneedit_rm() { fulldomain=$1 txtvalue=$2 - _info "Using Zoneedit" + _info "Using ZoneEdit" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" @@ -114,7 +118,7 @@ _zoneedit_api() { if [ "$ze_sleep" ]; then _sleep "$ze_sleep"; fi return 0 elif _contains "$response" "ERROR.*Minimum.*seconds"; then - _info "Zoneedit responded with a rate limit of..." + _info "ZoneEdit responded with a rate limit of..." ze_ratelimit=$(echo "$response" | sed -n 's/.*Minimum \([0-9]\+\) seconds.*/\1/p') if [ "$ze_ratelimit" ] && [ ! "$(echo "$ze_ratelimit" | tr -d '0-9')" ]; then _info "$ze_ratelimit seconds." diff --git a/notify/aws_ses.sh b/notify/aws_ses.sh index 30db45adbe..07e0c48ccd 100644 --- a/notify/aws_ses.sh +++ b/notify/aws_ses.sh @@ -89,7 +89,7 @@ _use_metadata() { _normalizeJson | tr '{,}' '\n' | while read -r _line; do - _key="$(echo "${_line%%:*}" | tr -d '"')" + _key="$(echo "${_line%%:*}" | tr -d \")" _value="${_line#*:}" _debug3 "_key" "$_key" _secure_debug3 "_value" "$_value"