You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							251 lines
						
					
					
						
							9.3 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							251 lines
						
					
					
						
							9.3 KiB
						
					
					
				| #!/usr/bin/env sh | |
| # shellcheck disable=SC2034 | |
| dns_dnsservices_info='DNS.Services | |
| Site: DNS.Services | |
| Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsservices | |
| Options: | |
|  DnsServices_Username Username | |
|  DnsServices_Password Password | |
| Issues: github.com/acmesh-official/acme.sh/issues/4152 | |
| Author: Bjarke Bruun <bbruun@gmail.com> | |
| ' | |
| 
 | |
| 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 <function>_add or <function>_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") | |
|   result2="$(printf "%s\n" "$result" | tr '[' '\n' | grep '"name"')" | |
|   result3="$(printf "%s\n" "$result2" | tr '}' '\n' | grep '"name"' | sed "s,^\,,,g" | sed "s,$,},g")" | |
|   useResult="" | |
|   _debug2 _get_root "Got the following root domain(s) $result" | |
|   _debug2 _get_root "- JSON: $result" | |
| 
 | |
|   if [ "$(printf "%s\n" "$result" | tr '}' '\n' | grep -c '"name"')" -gt "1" ]; then | |
|     checkMultiZones="true" | |
|     _debug2 _get_root "- multiple zones found" | |
|   else | |
|     checkMultiZones="false" | |
|     _debug2 _get_root "- single zone found" | |
|   fi | |
| 
 | |
|   # Find/isolate the root zone to work with in createRecord() and deleteRecord() | |
|   rootZone="" | |
|   if [ "$checkMultiZones" = "true" ]; then | |
|     #rootZone=$(for x in $(printf "%s" "${result3}" | tr ',' '\n' | sed -n 's/.*"name":"\(.*\)",.*/\1/p'); do if [ "$(echo "$domain" | grep "$x")" != "" ]; then echo "$x"; fi; done) | |
|     rootZone=$(for x in $(printf "%s\n" "${result3}" | tr ',' '\n' | grep name | cut -d'"' -f4); do if [ "$(echo "$domain" | grep "$x")" != "" ]; then echo "$x"; fi; done) | |
|     if [ "$rootZone" != "" ]; then | |
|       _debug2 _rootZone "- root zone for $domain is $rootZone" | |
|     else | |
|       _err "Could not find root zone for $domain, is it correctly typed?" | |
|       return 1 | |
|     fi | |
|   else | |
|     rootZone=$(echo "$result" | tr '}' '\n' | _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 | |
| 
 | |
|   # Make sure we use the correct API zone data | |
|   useResult="$(printf "%s\n" "${result3}" tr ',' '\n' | grep "$rootZone")" | |
|   _debug2 _useResult "useResult=$useResult" | |
| 
 | |
|   # Setup variables used by other functions to communicate with DNS.Services API | |
|   #zoneInfo=$(printf "%s\n" "$useResult" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"name":")([^"]*)"(.*)$,\2,g') | |
|   zoneInfo=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep '"name"' | cut -d'"' -f4) | |
|   rootZoneName="$rootZone" | |
|   subDomainName="$(printf "%s\n" "$domain" | sed "s,\.$rootZone,,g")" | |
|   subDomainNameClean="$(printf "%s\n" "$domain" | sed "s,_acme-challenge.,,g")" | |
|   rootZoneDomainID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep domain_id | cut -d'"' -f4) | |
|   rootZoneServiceID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep service_id | cut -d'"' -f4) | |
| 
 | |
|   _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 | |
|   if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then | |
|     _err "Something happend - could not get the API zone information" | |
|     return 1 | |
|   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')" | |
|   recordID="$(printf "%s\n" "$result" | tr '}' '\n' | grep -- "$txtvalue" | tr ',' '\n' | grep '"id"' | cut -d'"' -f4)" | |
|   _debug2 _recordID "recordID used for deletion of record: $recordID" | |
| 
 | |
|   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 | |
| }
 |