diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml index 89497580..b0332775 100644 --- a/.github/workflows/wiki-monitor.yml +++ b/.github/workflows/wiki-monitor.yml @@ -12,6 +12,7 @@ jobs: with: repository: ${{ github.repository }}.wiki path: wiki + fetch-depth: 0 - name: Generate wiki change message run: | @@ -58,3 +59,4 @@ jobs: + diff --git a/acme.sh b/acme.sh index 46c1124c..ead7602b 100755 --- a/acme.sh +++ b/acme.sh @@ -1811,6 +1811,10 @@ _time() { # 2022-04-01 08:10:33 to 1648800633 #or 2022-04-01T08:10:33Z to 1648800633 _date2time() { + #Mac/BSD + if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then + return + fi #Linux if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return @@ -1820,10 +1824,6 @@ _date2time() { if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return fi - #Mac/BSD - if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then - return - fi #Omnios if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then return @@ -2538,15 +2538,19 @@ _startserver() { _NC="socat" if [ "$Le_Listen_V6" ]; then _NC="$_NC -6" - else + SOCAT_OPTIONS=TCP6-LISTEN + elif [ "$Le_Listen_V4" ]; then _NC="$_NC -4" + SOCAT_OPTIONS=TCP4-LISTEN + else + SOCAT_OPTIONS=TCP-LISTEN fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then _NC="$_NC -d -d -v" fi - SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork + SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork #Adding bind to local-address if [ "$ncaddr" ]; then @@ -3513,7 +3517,7 @@ _on_before_issue() { _debug _chk_alt_domains "$_chk_alt_domains" #run pre hook if [ "$_chk_pre_hook" ]; then - _info "Runing pre hook:'$_chk_pre_hook'" + _info "Running pre hook:'$_chk_pre_hook'" if ! ( export Le_Domain="$_chk_main_domain" export Le_Alt="$_chk_alt_domains" @@ -4508,6 +4512,7 @@ issue() { if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then _err "_on_before_issue." + _on_issue_err "$_post_hook" return 1 fi @@ -4770,7 +4775,8 @@ $_authorizations_map" _debug keyauthorization "$keyauthorization" fi - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$keyauthorization" -a -z "$entry" ]; then @@ -6360,7 +6366,8 @@ _deactivate() { fi _debug "Trigger validation." vtype="$(_getIdType "$_d_domain")" - entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" + # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 + entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$entry" ]; then _err "$d: Cannot get domain token" diff --git a/deploy/panos.sh b/deploy/panos.sh index 0dc1b2f0..a9232e79 100644 --- a/deploy/panos.sh +++ b/deploy/panos.sh @@ -7,20 +7,26 @@ # # Firewall admin with superuser and IP address is required. # -# REQURED: +# REQUIRED: # export PANOS_HOST="" # export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role # export PANOS_PASS="" # # OPTIONAL -# export PANOS_TEMPLATE="" #Template Name of panorama managed devices +# export PANOS_TEMPLATE="" # Template Name of panorama managed devices +# export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically +# export PANOS_VSYS="Shared" # name of the vsys to import the certificate # # The script will automatically generate a new API key if # no key is found, or if a saved key has expired or is invalid. +_COMMIT_WAIT_INTERVAL=30 # query commit status every 30 seconds +_COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes) + # This function is to parse the XML response from the firewall parse_response() { type=$2 + _debug "API Response: $1" if [ "$type" = 'keygen' ]; then status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g') if [ "$status" = "success" ]; then @@ -30,6 +36,13 @@ parse_response() { message="PAN-OS Key could not be set." fi else + if [ "$type" = 'commit' ]; then + job_id=$(echo "$1" | sed 's/^.*\(\)\(.*\)<\/job>.*/\2/g') + _commit_job_id=$job_id + elif [ "$type" = 'job_status' ]; then + job_status=$(echo "$1" | tr -d '\n' | sed 's/^.*\([^<]*\)<\/result>.*/\1/g') + _commit_job_status=$job_status + fi status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g') message=$(echo "$1" | tr -d '\n' | sed 's/.*\(\|\|\)\([^<]*\).*/\2/g') _debug "Firewall message: $message" @@ -44,13 +57,13 @@ parse_response() { #This function is used to deploy to the firewall deployer() { content="" - type=$1 # Types are keytest, keygen, cert, key, commit + type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push panos_url="https://$_panos_host/api/" + export _H1="Content-Type: application/x-www-form-urlencoded" #Test API Key by performing a lookup if [ "$type" = 'keytest' ]; then _debug "**** Testing saved API Key ****" - _H1="Content-Type: application/x-www-form-urlencoded" # Get Version Info to test key content="type=version&key=$_panos_key" ## Exclude all scopes for the empty commit @@ -61,7 +74,6 @@ deployer() { # Generate API Key if [ "$type" = 'keygen' ]; then _debug "**** Generating new API Key ****" - _H1="Content-Type: application/x-www-form-urlencoded" content="type=keygen&user=$_panos_user&password=$_panos_pass" # content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}" fi @@ -84,6 +96,9 @@ deployer() { if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi + if [ "$_panos_vsys" ]; then + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" + fi fi if [ "$type" = 'key' ]; then panos_url="${panos_url}?type=import" @@ -96,6 +111,9 @@ deployer() { if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi + if [ "$_panos_vsys" ]; then + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" + fi fi #Close multipart content="$content${nl}--$delim--${nl}${nl}" @@ -106,7 +124,6 @@ deployer() { # Commit changes if [ "$type" = 'commit' ]; then _debug "**** Committing changes ****" - export _H1="Content-Type: application/x-www-form-urlencoded" #Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution! if [ "$FORCE" ]; then _debug "Force switch detected. Committing ALL changes to the firewall." @@ -118,6 +135,20 @@ deployer() { content="type=commit&action=partial&key=$_panos_key&cmd=$cmd" fi + # Query job status + if [ "$type" = 'job_status' ]; then + echo "**** Querying job $_commit_job_id status ****" + cmd=$(printf "%s" "$_commit_job_id" | _url_encode) + content="type=op&key=$_panos_key&cmd=$cmd" + fi + + # Push changes + if [ "$type" = 'push' ]; then + echo "**** Pushing changes ****" + cmd=$(printf "%s" "$_panos_template_stack$_panos_user" | _url_encode) + content="type=commit&action=all&key=$_panos_key&cmd=$cmd" + fi + response=$(_post "$content" "$panos_url" "" "POST") parse_response "$response" "$type" # Saving response to variables @@ -126,6 +157,8 @@ deployer() { if [ "$response_status" = "success" ]; then _debug "Successfully deployed $type" return 0 + elif [ "$_commit_job_status" ]; then + _debug "Commit Job Status = $_commit_job_status" else _err "Deploy of type $type failed. Try deploying with --debug to troubleshoot." _debug "$message" @@ -191,11 +224,31 @@ panos_deploy() { _getdeployconf PANOS_TEMPLATE fi + # PANOS_TEMPLATE_STACK + if [ "$PANOS_TEMPLATE_STACK" ]; then + _debug "Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file." + _savedeployconf PANOS_TEMPLATE_STACK "$PANOS_TEMPLATE_STACK" 1 + else + _debug "Attempting to load variable PANOS_TEMPLATE_STACK from file." + _getdeployconf PANOS_TEMPLATE_STACK + fi + + # PANOS_TEMPLATE_STACK + if [ "$PANOS_VSYS" ]; then + _debug "Detected ENV variable PANOS_VSYS. Saving to file." + _savedeployconf PANOS_VSYS "$PANOS_VSYS" 1 + else + _debug "Attempting to load variable PANOS_VSYS from file." + _getdeployconf PANOS_VSYS + fi + #Store variables _panos_host=$PANOS_HOST _panos_user=$PANOS_USER _panos_pass=$PANOS_PASS _panos_template=$PANOS_TEMPLATE + _panos_template_stack=$PANOS_TEMPLATE_STACK + _panos_vsys=$PANOS_VSYS #Test API Key if found. If the key is invalid, the variable _panos_key will be unset. if [ "$_panos_host" ] && [ "$_panos_key" ]; then @@ -229,6 +282,20 @@ panos_deploy() { deployer cert deployer key deployer commit + if [ "$_panos_template_stack" ]; then + # try to get job status for 20 times in 30 sec interval + i=0 + while [ "$i" -lt $_COMMIT_WAIT_ITERATIONS ]; do + deployer job_status + if [ "$_commit_job_status" = "OK" ]; then + echo "Commit finished!" + break + fi + sleep $_COMMIT_WAIT_INTERVAL + i=$((i + 1)) + done + deployer push + fi fi fi } diff --git a/deploy/proxmoxbs.sh b/deploy/proxmoxbs.sh index d1146454..e8528e8f 100644 --- a/deploy/proxmoxbs.sh +++ b/deploy/proxmoxbs.sh @@ -115,6 +115,16 @@ HEREDOC _info "Push certificates to server" export HTTPS_INSECURE=1 export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}" - _post "$_json_payload" "$_target_url" "" POST "application/json" + response=$(_post "$_json_payload" "$_target_url" "" POST "application/json") + _retval=$? + if [ "${_retval}" -eq 0 ]; then + _debug3 response "$response" + _info "Certificate successfully deployed" + return 0 + else + _err "Certificate deployment failed" + _debug "Response" "$response" + return 1 + fi } diff --git a/deploy/proxmoxve.sh b/deploy/proxmoxve.sh index f9de590c..8c67f7de 100644 --- a/deploy/proxmoxve.sh +++ b/deploy/proxmoxve.sh @@ -127,6 +127,16 @@ HEREDOC _info "Push certificates to server" export HTTPS_INSECURE=1 export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}" - _post "$_json_payload" "$_target_url" "" POST "application/json" + response=$(_post "$_json_payload" "$_target_url" "" POST "application/json") + _retval=$? + if [ "${_retval}" -eq 0 ]; then + _debug3 response "$response" + _info "Certificate successfully deployed" + return 0 + else + _err "Certificate deployment failed" + _debug "Response" "$response" + return 1 + fi } diff --git a/dnsapi/dns_constellix.sh b/dnsapi/dns_constellix.sh index 6a50e199..7251f8b2 100644 --- a/dnsapi/dns_constellix.sh +++ b/dnsapi/dns_constellix.sh @@ -117,7 +117,7 @@ dns_constellix_rm() { #################### Private functions below ################################## _get_root() { - domain=$1 + domain=$(echo "$1" | _lower_case) i=2 p=1 _debug "Detecting root zone" @@ -156,6 +156,9 @@ _constellix_rest() { data="$3" _debug "$ep" + # Prevent rate limit + _sleep 2 + rdate=$(date +"%s")"000" hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64) diff --git a/dnsapi/dns_fornex.sh b/dnsapi/dns_fornex.sh index 91e5491b..dcaa2297 100644 --- a/dnsapi/dns_fornex.sh +++ b/dnsapi/dns_fornex.sh @@ -95,7 +95,7 @@ _get_root() { return 1 fi - if ! _rest GET "dns/domain/"; then + if ! _rest GET "dns/domain/?q=$h"; then return 1 fi diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index f19333c4..9cb6327e 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -1,14 +1,17 @@ #!/usr/bin/env sh + +# LA_Id="123" +# LA_Sk="456" # shellcheck disable=SC2034 dns_la_info='dns.la Site: dns.la Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la Options: - LA_Id API ID - LA_Key API key + LA_Id APIID + LA_Sk APISecret + LA_Token 用冒号连接 APIID APISecret 再base64生成 Issues: github.com/acmesh-official/acme.sh/issues/4257 ' - LA_Api="https://api.dns.la/api" ######## Public functions ##################### @@ -19,18 +22,23 @@ dns_la_add() { txtvalue=$2 LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" - LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}" + LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}" + _log "LA_Id=$LA_Id" + _log "LA_Sk=$LA_Sk" - if [ -z "$LA_Id" ] || [ -z "$LA_Key" ]; then + if [ -z "$LA_Id" ] || [ -z "$LA_Sk" ]; then LA_Id="" - LA_Key="" + LA_Sk="" _err "You didn't specify a dnsla api id and key yet." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable LA_Id "$LA_Id" - _saveaccountconf_mutable LA_Key "$LA_Key" + _saveaccountconf_mutable LA_Sk "$LA_Sk" + + # generate dnsla token + _la_token _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -42,11 +50,13 @@ dns_la_add() { _debug _domain "$_domain" _info "Adding record" - if _la_rest "record.ashx?cmd=create&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue&recordline="; then - if _contains "$response" '"resultid":'; then + + # record type is enum in new api, 16 for TXT + if _la_post "{\"domainId\":\"$_domain_id\",\"type\":16,\"host\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"ttl\":600}" "record"; then + if _contains "$response" '"id":'; then _info "Added, OK" return 0 - elif _contains "$response" '"code":532'; then + elif _contains "$response" '"msg":"与已有记录冲突"'; then _info "Already exists, OK" return 0 else @@ -54,7 +64,7 @@ dns_la_add() { return 1 fi fi - _err "Add txt record error." + _err "Add txt record failed." return 1 } @@ -65,7 +75,9 @@ dns_la_rm() { txtvalue=$2 LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" - LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}" + LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}" + + _la_token _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -77,27 +89,29 @@ dns_la_rm() { _debug _domain "$_domain" _debug "Getting txt records" - if ! _la_rest "record.ashx?cmd=listn&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue"; then + # record type is enum in new api, 16 for TXT + if ! _la_get "recordList?pageIndex=1&pageSize=10&domainId=$_domain_id&host=$_sub_domain&type=16&data=$txtvalue"; then _err "Error" return 1 fi - if ! _contains "$response" '"recordid":'; then + if ! _contains "$response" '"id":'; then _info "Don't need to remove." return 0 fi - record_id=$(printf "%s" "$response" | grep '"recordid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n') + record_id=$(printf "%s" "$response" | grep '"id":' | _head_n 1 | sed 's/.*"id": *"\([^"]*\)".*/\1/') _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi - if ! _la_rest "record.ashx?cmd=remove&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&recordid=$record_id"; then + # remove record in new api is RESTful + if ! _la_post "" "record?id=$record_id" "DELETE"; then _err "Delete record error." return 1 fi - _contains "$response" '"code":300' + _contains "$response" '"code":200' } @@ -119,12 +133,13 @@ _get_root() { return 1 fi - if ! _la_rest "domain.ashx?cmd=get&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domain=$h"; then + if ! _la_get "domain?domain=$h"; then return 1 fi - if _contains "$response" '"domainid":'; then - _domain_id=$(printf "%s" "$response" | grep '"domainid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n') + if _contains "$response" '"domain":'; then + _domain_id=$(echo "$response" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p') + _log "_domain_id" "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" @@ -143,6 +158,21 @@ _la_rest() { url="$LA_Api/$1" _debug "$url" + if ! response="$(_get "$url" "Authorization: Basic $LA_Token" | tr -d ' ' | tr "}" ",")"; then + _err "Error: $url" + return 1 + fi + + _debug2 response "$response" + return 0 +} + +_la_get() { + url="$LA_Api/$1" + _debug "$url" + + export _H1="Authorization: Basic $LA_Token" + if ! response="$(_get "$url" | tr -d ' ' | tr "}" ",")"; then _err "Error: $url" return 1 @@ -151,3 +181,29 @@ _la_rest() { _debug2 response "$response" return 0 } + +# Usage: _la_post body url [POST|PUT|DELETE] +_la_post() { + body=$1 + url="$LA_Api/$2" + http_method=$3 + _debug "$body" + _debug "$url" + + export _H1="Authorization: Basic $LA_Token" + + if ! response="$(_post "$body" "$url" "" "$http_method")"; then + _err "Error: $url" + return 1 + fi + + _debug2 response "$response" + return 0 +} + +_la_token() { + LA_Token=$(printf "%s:%s" "$LA_Id" "$LA_Sk" | _base64) + _debug "$LA_Token" + + return 0 +} diff --git a/dnsapi/dns_openprovider_rest.sh b/dnsapi/dns_openprovider_rest.sh new file mode 100644 index 00000000..210dc6fc --- /dev/null +++ b/dnsapi/dns_openprovider_rest.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_openprovider_rest_info='OpenProvider (REST) +Domains: OpenProvider.com +Site: OpenProvider.eu +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openprovider_rest +Options: + OPENPROVIDER_REST_USERNAME Openprovider Account Username + OPENPROVIDER_REST_PASSWORD Openprovider Account Password +Issues: github.com/acmesh-official/acme.sh/issues/6122 +Author: Lambiek12 +' + +OPENPROVIDER_API_URL="https://api.openprovider.eu/v1beta" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_openprovider_rest_add() { + fulldomain=$1 + txtvalue=$2 + + _openprovider_prepare_credentials || return 1 + + _debug "Try fetch OpenProvider DNS zone details" + if ! _get_dns_zone "$fulldomain"; then + _err "DNS zone not found within configured OpenProvider account." + return 1 + fi + + if [ -n "$_domain_id" ]; then + addzonerecordrequestparameters="dns/zones/$_domain_name" + addzonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"add\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"$txtvalue\"}]}}" + + if _openprovider_rest PUT "$addzonerecordrequestparameters" "$addzonerecordrequestbody"; then + if _contains "$response" "\"success\":true"; then + return 0 + elif _contains "$response" "\"Duplicate record\""; then + _debug "Record already existed" + return 0 + else + _err "Adding TXT record failed due to errors." + return 1 + fi + fi + fi + + _err "Adding TXT record failed due to errors." + return 1 +} + +# Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to remove the txt record after validation +dns_openprovider_rest_rm() { + fulldomain=$1 + txtvalue=$2 + + _openprovider_prepare_credentials || return 1 + + _debug "Try fetch OpenProvider DNS zone details" + if ! _get_dns_zone "$fulldomain"; then + _err "DNS zone not found within configured OpenProvider account." + return 1 + fi + + if [ -n "$_domain_id" ]; then + removezonerecordrequestparameters="dns/zones/$_domain_name" + removezonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"remove\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"\\\"$txtvalue\\\"\"}]}}" + + if _openprovider_rest PUT "$removezonerecordrequestparameters" "$removezonerecordrequestbody"; then + if _contains "$response" "\"success\":true"; then + return 0 + else + _err "Removing TXT record failed due to errors." + return 1 + fi + fi + fi + + _err "Removing TXT record failed due to errors." + return 1 +} + +#################### OpenProvider API common functions #################### +_openprovider_prepare_credentials() { + OPENPROVIDER_REST_USERNAME="${OPENPROVIDER_REST_USERNAME:-$(_readaccountconf_mutable OPENPROVIDER_REST_USERNAME)}" + OPENPROVIDER_REST_PASSWORD="${OPENPROVIDER_REST_PASSWORD:-$(_readaccountconf_mutable OPENPROVIDER_REST_PASSWORD)}" + + if [ -z "$OPENPROVIDER_REST_USERNAME" ] || [ -z "$OPENPROVIDER_REST_PASSWORD" ]; then + OPENPROVIDER_REST_USERNAME="" + OPENPROVIDER_REST_PASSWORD="" + _err "You didn't specify the Openprovider username or password yet." + return 1 + fi + + #save the credentials to the account conf file. + _saveaccountconf_mutable OPENPROVIDER_REST_USERNAME "$OPENPROVIDER_REST_USERNAME" + _saveaccountconf_mutable OPENPROVIDER_REST_PASSWORD "$OPENPROVIDER_REST_PASSWORD" +} + +_openprovider_rest() { + httpmethod=$1 + queryparameters=$2 + requestbody=$3 + + _openprovider_rest_login + if [ -z "$openproviderauthtoken" ]; then + _err "Unable to fetch authentication token from Openprovider API." + return 1 + fi + + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + export _H3="Authorization: Bearer $openproviderauthtoken" + + if [ "$httpmethod" != "GET" ]; then + response="$(_post "$requestbody" "$OPENPROVIDER_API_URL/$queryparameters" "" "$httpmethod")" + else + response="$(_get "$OPENPROVIDER_API_URL/$queryparameters")" + fi + + if [ "$?" != "0" ]; then + _err "No valid parameters supplied for Openprovider API: Error $queryparameters" + return 1 + fi + + _debug2 response "$response" + + return 0 +} + +_openprovider_rest_login() { + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + + loginrequesturl="$OPENPROVIDER_API_URL/auth/login" + loginrequestbody="{\"ip\":\"0.0.0.0\",\"password\":\"$OPENPROVIDER_REST_PASSWORD\",\"username\":\"$OPENPROVIDER_REST_USERNAME\"}" + loginresponse="$(_post "$loginrequestbody" "$loginrequesturl" "" "POST")" + + openproviderauthtoken="$(printf "%s\n" "$loginresponse" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')" + + export openproviderauthtoken +} + +#################### Private functions ################################## + +# Usage: _get_dns_zone _acme-challenge.www.domain.com +# Returns: +# _domain_id=123456789 +# _domain_name=domain.com +# _sub_domain=_acme-challenge.www +_get_dns_zone() { + domain=$1 + i=1 + p=1 + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + if [ -z "$h" ]; then + # Empty value not allowed + return 1 + fi + + if ! _openprovider_rest GET "dns/zones/$h" ""; then + return 1 + fi + + if _contains "$response" "\"name\":\"$h\""; then + _domain_id="$(printf "%s\n" "$response" | _egrep_o '"id" *: *[^,]*' | _head_n 1 | sed 's#^"id" *: *##')" + _debug _domain_id "$_domain_id" + + _domain_name="$h" + _debug _domain_name "$_domain_name" + + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _debug _sub_domain "$_sub_domain" + return 0 + fi + + p=$i + i=$(_math "$i" + 1) + done + + return 1 +} diff --git a/dnsapi/dns_rage4.sh b/dnsapi/dns_rage4.sh index ad312759..c27fbc5f 100755 --- a/dnsapi/dns_rage4.sh +++ b/dnsapi/dns_rage4.sh @@ -42,6 +42,14 @@ dns_rage4_add() { _debug _domain_id "$_domain_id" _rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1" + + # Response after adding a TXT record should be something like this: + # {"status":true,"id":28160443,"error":null} + if ! _contains "$response" '"error":null' >/dev/null; then + _err "Error while adding TXT record: '$response'" + return 1 + fi + return 0 } @@ -63,7 +71,12 @@ dns_rage4_rm() { _debug "Getting txt records" _rage4_rest "getrecords/?id=${_domain_id}" - _record_id=$(echo "$response" | sed -rn 's/.*"id":([[:digit:]]+)[^\}]*'"$txtvalue"'.*/\1/p') + _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -rn 's/.*"id":([[:digit:]]+),.*/\1/p') + if [ -z "$_record_id" ]; then + _err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'." + return 1 + fi + _rage4_rest "deleterecord/?id=${_record_id}" return 0 } @@ -105,8 +118,7 @@ _rage4_rest() { token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"') auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64) - export _H1="Content-Type: application/json" - export _H2="Authorization: Basic $auth" + export _H1="Authorization: Basic $auth" response="$(_get "$RAGE4_Api$ep")" diff --git a/dnsapi/dns_variomedia.sh b/dnsapi/dns_variomedia.sh index fa38bbb6..4620b854 100644 --- a/dnsapi/dns_variomedia.sh +++ b/dnsapi/dns_variomedia.sh @@ -74,7 +74,7 @@ dns_variomedia_rm() { return 1 fi - _record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')" + _record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f3 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep -i "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')" _debug _record_id "$_record_id" if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." diff --git a/notify/telegram.sh b/notify/telegram.sh index ccbd1533..c0621ae7 100644 --- a/notify/telegram.sh +++ b/notify/telegram.sh @@ -34,8 +34,8 @@ telegram_send() { fi _saveaccountconf_mutable TELEGRAM_BOT_URLBASE "$TELEGRAM_BOT_URLBASE" - _subject="$(printf "%s" "$_subject" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([_*[()~`>#+--=|{}.!]\)/\\\\\1/g')" - _content="$(printf "%s" "$_content" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([_*[()~`>#+--=|{}.!]\)/\\\\\1/g')" + _subject="$(printf "%s" "$_subject" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" + _content="$(printf "%s" "$_content" | sed 's/\\/\\\\\\\\/g' | sed 's/\]/\\\\\]/g' | sed 's/\([_*[()~`>#+\-=|{}.!]\)/\\\\\1/g')" _content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)" _data="{\"text\": \"$_content\", " _data="$_data\"chat_id\": \"$TELEGRAM_BOT_CHATID\", "