diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 5f6cdac9..720cfaab 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -188,7 +188,7 @@ jobs: - uses: actions/checkout@v2 - name: Clone acmetest run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/freebsd-vm@v0.1.4 + - uses: vmactions/freebsd-vm@v0.1.7 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: pkg install -y socat curl diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml index 4d310f05..6f8797db 100644 --- a/.github/workflows/FreeBSD.yml +++ b/.github/workflows/FreeBSD.yml @@ -49,7 +49,7 @@ jobs: run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/freebsd-vm@v0.1.5 + - uses: vmactions/freebsd-vm@v0.1.7 with: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN' nat: | diff --git a/dnsapi/dns_dnsservices.sh b/dnsapi/dns_dnsservices.sh new file mode 100755 index 00000000..9f2220fe --- /dev/null +++ b/dnsapi/dns_dnsservices.sh @@ -0,0 +1,234 @@ +#!/usr/bin/env sh + +#This file name is "dns_dnsservices.sh" +#Script for Danish DNS registra and DNS hosting provider https://dns.services + +#Author: Bjarke Bruun +#Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/4152 + +# Global variable to connect to the DNS.Services API +DNSServices_API=https://dns.services/api + +######## Public functions ##################### + +#Usage: dns_dnsservices_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_dnsservices_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Using dns.services to create ACME DNS challenge" + _debug2 add_fulldomain "$fulldomain" + _debug2 add_txtvalue "$txtvalue" + + # Read username/password from environment or .acme.sh/accounts.conf + DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}" + DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}" + if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then + DnsServices_Username="" + DnsServices_Password="" + _err "You didn't specify dns.services api username and password yet." + _err "Set environment variables DnsServices_Username and DnsServices_Password" + return 1 + fi + + # Setup GET/POST/DELETE headers + _setup_headers + + #save the credentials to the account conf file. + _saveaccountconf_mutable DnsServices_Username "$DnsServices_Username" + _saveaccountconf_mutable DnsServices_Password "$DnsServices_Password" + + if ! _contains "$DnsServices_Username" "@"; then + _err "It seems that the username variable DnsServices_Username has not been set/left blank" + _err "or is not a valid email. Please correct and try again." + return 1 + fi + + if ! _get_root "${fulldomain}"; then + _err "Invalid domain ${fulldomain}" + return 1 + fi + + if ! createRecord "$fulldomain" "${txtvalue}"; then + _err "Error creating TXT record in domain $fulldomain in $rootZoneName" + return 1 + fi + + _debug2 challenge-created "Created $fulldomain" + return 0 +} + +#Usage: fulldomain txtvalue +#Description: Remove the txt record after validation. +dns_dnsservices_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Using dns.services to remove DNS record $fulldomain TXT $txtvalue" + _debug rm_fulldomain "$fulldomain" + _debug rm_txtvalue "$txtvalue" + + # Read username/password from environment or .acme.sh/accounts.conf + DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}" + DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}" + if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then + DnsServices_Username="" + DnsServices_Password="" + _err "You didn't specify dns.services api username and password yet." + _err "Set environment variables DnsServices_Username and DnsServices_Password" + return 1 + fi + + # Setup GET/POST/DELETE headers + _setup_headers + + if ! _get_root "${fulldomain}"; then + _err "Invalid domain ${fulldomain}" + return 1 + fi + + _debug2 rm_rootDomainInfo "found root domain $rootZoneName for $fulldomain" + + if ! deleteRecord "${fulldomain}" "${txtvalue}"; then + _err "Error removing record: $fulldomain TXT ${txtvalue}" + return 1 + fi + + return 0 +} + +#################### Private functions below ################################## + +_setup_headers() { + # Set up API Headers for _get() and _post() + # The _add or _rm must have been called before to work + + if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then + _err "Could not setup BASIC authentication headers, they are missing" + return 1 + fi + + DnsServiceCredentials="$(printf "%s" "$DnsServices_Username:$DnsServices_Password" | _base64)" + export _H1="Authorization: Basic $DnsServiceCredentials" + export _H2="Content-Type: application/json" + + # Just return if headers are set + return 0 +} + +_get_root() { + domain=$1 + _debug2 _get_root "Get the root domain of ${domain} for DNS API" + + # Setup _get() and _post() headers + #_setup_headers + + result=$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/dns") + _debug2 _get_root "Got the following root domain(s) $result" + _debug2 _get_root "- JSON: $result" + + if [ "$(echo "$result" | grep -c '"name"')" -gt "1" ]; then + checkMultiZones="true" + _debug2 _get_root "- multiple zones found" + else + checkMultiZones="false" + + fi + + # Find/isolate the root zone to work with in createRecord() and deleteRecord() + rootZone="" + if [ "$checkMultiZones" = "true" ]; then + rootZone=$(for zone in $(echo "$result" | tr -d '\n' ' '); do + if [ "$(echo "$domain" | grep "$zone")" != "" ]; then + _debug2 _get_root "- trying to figure out if $zone is in $domain" + echo "$zone" + break + fi + done) + else + rootZone=$(echo "$result" | _egrep_o '"name":"[^"]*' | cut -d'"' -f4) + _debug2 _get_root "- only found 1 domain in API: $rootZone" + fi + + if [ -z "$rootZone" ]; then + _err "Could not find root domain for $domain - is it correctly typed?" + return 1 + fi + + # Setup variables used by other functions to communicate with DNS.Services API + #zoneInfo=$(echo "$result" | sed "s,\"zones,\n&,g" | grep zones | cut -d'[' -f2 | cut -d']' -f1 | tr '}' '\n' | grep "\"$rootZone\"") + zoneInfo=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"name":")([^"]*)"(.*)$,\2,g' | grep "\"$rootZone\"") + rootZoneName="$rootZone" + subDomainName="$(echo "$domain" | sed "s,\.$rootZone,,g")" + subDomainNameClean="$(echo "$domain" | sed "s,_acme-challenge.,,g")" + rootZoneDomainID=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"domain_id":")([^"]*)"(.*)$,\2,g') + rootZoneServiceID=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"service_id":")([^"]*)"(.*)$,\2,g') + + _debug2 _zoneInfo "Zone info from API : $zoneInfo" + _debug2 _get_root "Root zone name : $rootZoneName" + _debug2 _get_root "Root zone domain ID : $rootZoneDomainID" + _debug2 _get_root "Root zone service ID: $rootZoneServiceID" + _debug2 _get_root "Sub domain : $subDomainName" + + _debug _get_root "Found valid root domain $rootZone for $subDomainNameClean" + return 0 +} + +createRecord() { + fulldomain=$1 + txtvalue="$2" + + # Get root domain information - needed for DNS.Services API communication + if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then + _get_root "$fulldomain" + fi + + _debug2 createRecord "CNAME TXT value is: $txtvalue" + + # Prepare data to send to API + data="{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\", \"ttl\":\"10\"}" + + _debug2 createRecord "data to API: $data" + result=$(_post "$data" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records" "" "POST") + _debug2 createRecord "result from API: $result" + + if [ "$(echo "$result" | _egrep_o "\"success\":true")" = "" ]; then + _err "Failed to create TXT record $fulldomain with content $txtvalue in zone $rootZoneName" + _err "$result" + return 1 + fi + + _info "Record \"$fulldomain TXT $txtvalue\" has been created" + return 0 +} + +deleteRecord() { + fulldomain=$1 + txtvalue=$2 + + _log deleteRecord "Deleting $fulldomain TXT $txtvalue record" + + if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then + _get_root "$fulldomain" + fi + + result="$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID")" + recordInfo="$(echo "$result" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}")" + recordID="$(echo "$recordInfo" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"id":")([^"]*)"(.*)$,\2,g')" + + if [ -z "$recordID" ]; then + _info "Record $fulldomain TXT $txtvalue not found or already deleted" + return 0 + else + _debug2 deleteRecord "Found recordID=$recordID" + fi + + _debug2 deleteRecord "DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" + _log "curl DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" + result="$(_H1="$_H1" _H2="$_H2" _post "" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" "" "DELETE")" + _debug2 deleteRecord "API Delete result \"$result\"" + _log "curl API Delete result \"$result\"" + + # Return OK regardless + return 0 +} diff --git a/dnsapi/dns_huaweicloud.sh b/dnsapi/dns_huaweicloud.sh index ac3ede65..ceda9258 100644 --- a/dnsapi/dns_huaweicloud.sh +++ b/dnsapi/dns_huaweicloud.sh @@ -2,7 +2,7 @@ # HUAWEICLOUD_Username # HUAWEICLOUD_Password -# HUAWEICLOUD_ProjectID +# HUAWEICLOUD_DomainName iam_api="https://iam.myhuaweicloud.com" dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work @@ -14,6 +14,8 @@ dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work # # Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/zh-cn_topic_0132421999.html # +# About "DomainName" parameters see: https://support.huaweicloud.com/api-iam/iam_01_0006.html +# dns_huaweicloud_add() { fulldomain=$1 @@ -21,16 +23,16 @@ dns_huaweicloud_add() { HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}" - HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}" + HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" # Check information - if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then + if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then _err "Not enough information provided to dns_huaweicloud!" return 1 fi unset token # Clear token - token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")" + token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")" if [ -z "${token}" ]; then # Check token _err "dns_api(dns_huaweicloud): Error getting token." return 1 @@ -56,7 +58,7 @@ dns_huaweicloud_add() { # Do saving work if all succeeded _saveaccountconf_mutable HUAWEICLOUD_Username "${HUAWEICLOUD_Username}" _saveaccountconf_mutable HUAWEICLOUD_Password "${HUAWEICLOUD_Password}" - _saveaccountconf_mutable HUAWEICLOUD_ProjectID "${HUAWEICLOUD_ProjectID}" + _saveaccountconf_mutable HUAWEICLOUD_DomainName "${HUAWEICLOUD_DomainName}" return 0 } @@ -72,16 +74,16 @@ dns_huaweicloud_rm() { HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}" - HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}" + HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" # Check information - if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then + if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then _err "Not enough information provided to dns_huaweicloud!" return 1 fi unset token # Clear token - token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")" + token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")" if [ -z "${token}" ]; then # Check token _err "dns_api(dns_huaweicloud): Error getting token." return 1 @@ -253,7 +255,7 @@ _rm_record() { _get_token() { _username=$1 _password=$2 - _project=$3 + _domain_name=$3 _debug "Getting Token" body="{ @@ -267,14 +269,14 @@ _get_token() { \"name\": \"${_username}\", \"password\": \"${_password}\", \"domain\": { - \"name\": \"${_username}\" + \"name\": \"${_domain_name}\" } } } }, \"scope\": { \"project\": { - \"id\": \"${_project}\" + \"name\": \"ap-southeast-1\" } } } diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh index b382e52f..2252f03a 100755 --- a/dnsapi/dns_ovh.sh +++ b/dnsapi/dns_ovh.sh @@ -118,6 +118,7 @@ _initAuth() { #return and wait for retry. return 1 fi + _saveaccountconf OVH_CK "$OVH_CK" _info "Checking authentication" @@ -235,7 +236,6 @@ _ovh_authentication() { _secure_debug consumerKey "$consumerKey" OVH_CK="$consumerKey" - _saveaccountconf OVH_CK "$OVH_CK" _info "Please open this link to do authentication: $(__green "$validationUrl")" diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh index bcf256ff..a0a83c37 100644 --- a/dnsapi/dns_world4you.sh +++ b/dnsapi/dns_world4you.sh @@ -12,7 +12,7 @@ RECORD='' # Usage: dns_world4you_add dns_world4you_add() { - fqdn="$1" + fqdn=$(echo "$1" | _lower_case) value="$2" _info "Using world4you to add record" _debug fulldomain "$fqdn" @@ -49,12 +49,12 @@ dns_world4you_add() { ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded') _resethttp - if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then + if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then res=$(_get "$WORLD4YOU_API/$paketnr/dns") if _contains "$res" "successfully"; then return 0 else - msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>\|^\s*//g') + msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to add record: Unknown error" echo "$ret" >'error-01.html' @@ -66,15 +66,15 @@ dns_world4you_add() { return 1 fi else - _err "$(_head_n 3 <"$HTTP_HEADER")" - _err "View $HTTP_HEADER for debugging" + msg=$(echo "$ret" | grep '"form-error-message"' | sed 's/^.*
\([^<]*\)<\/div>.*$/\1/') + _err "Unable to add record: my.world4you.com: $msg" return 1 fi } # Usage: dns_world4you_rm dns_world4you_rm() { - fqdn="$1" + fqdn=$(echo "$1" | _lower_case) value="$2" _info "Using world4you to remove record" _debug fulldomain "$fqdn" @@ -113,12 +113,12 @@ dns_world4you_rm() { ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns/record/delete" '' POST 'application/x-www-form-urlencoded') _resethttp - if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then + if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then res=$(_get "$WORLD4YOU_API/$paketnr/dns") if _contains "$res" "successfully"; then return 0 else - msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>\|^\s*//g') + msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to remove record: Unknown error" echo "$ret" >'error-01.html' @@ -130,8 +130,8 @@ dns_world4you_rm() { return 1 fi else - _err "$(_head_n 3 <"$HTTP_HEADER")" - _err "View $HTTP_HEADER for debugging" + msg=$(echo "$ret" | grep "form-error-message" | sed 's/^.*
\([^<]*\)<\/div>.*$/\1/') + _err "Unable to remove record: my.world4you.com: $msg" return 1 fi } @@ -155,29 +155,42 @@ _login() { _saveaccountconf_mutable WORLD4YOU_USERNAME "$WORLD4YOU_USERNAME" _saveaccountconf_mutable WORLD4YOU_PASSWORD "$WORLD4YOU_PASSWORD" + _resethttp + export ACME_HTTP_NO_REDIRECTS=1 + page=$(_get "$WORLD4YOU_API/login") + _resethttp + + if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then + _info "Already logged in" + _parse_sessid + return 0 + fi + _info "Logging in..." username="$WORLD4YOU_USERNAME" password="$WORLD4YOU_PASSWORD" - csrf_token=$(_get "$WORLD4YOU_API/login" | grep '_csrf_token' | sed 's/^.*]*value=\"\([^"]*\)\".*$/\1/') - sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/') + csrf_token=$(echo "$page" | grep '_csrf_token' | sed 's/^.*]*value=\"\([^"]*\)\".*$/\1/') + _parse_sessid export _H1="Cookie: W4YSESSID=$sessid" export _H2="X-Requested-With: XMLHttpRequest" body="_username=$username&_password=$password&_csrf_token=$csrf_token" ret=$(_post "$body" "$WORLD4YOU_API/login" '' POST 'application/x-www-form-urlencoded') unset _H2 + _debug ret "$ret" if _contains "$ret" "\"success\":true"; then _info "Successfully logged in" - sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/') + _parse_sessid else - _err "Unable to log in: $(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/')" + msg=$(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/') + _err "Unable to log in: my.world4you.com: $msg" return 1 fi } -# Usage _get_paketnr
+# Usage: _get_paketnr _get_paketnr() { fqdn="$1" form="$2" @@ -200,3 +213,8 @@ _get_paketnr() { PAKETNR=$(echo "$form" | grep "data-textfilter=\".* $domain " | _tail_n 1 | sed "s|.*$WORLD4YOU_API/\\([0-9]*\\)/.*|\\1|") return 0 } + +# Usage: _parse_sessid +_parse_sessid() { + sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | _tail_n 1 | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/') +}