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.

231 lines
7.9 KiB

  1. #!/usr/bin/env sh
  2. #This file name is "dns_dnsservices.sh"
  3. #Script for Danish DNS registra and DNS hosting provider https://dns.services
  4. #
  5. #Author: Bjarke Bruun <bbruun@gmail.com>
  6. #Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/4152
  7. # Global variable to connect to the DNS.Services API
  8. DNSServices_API=https://dns.services/api
  9. ######## Public functions #####################
  10. #Usage: dns_dnsservices_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  11. dns_dnsservices_add() {
  12. fulldomain=$1
  13. txtvalue=$2
  14. _info "Using dns.services to create ACME DNS challenge"
  15. _debug2 add_fulldomain "$fulldomain"
  16. _debug2 add_txtvalue "$txtvalue"
  17. # Read username/password from environment or .acme.sh/accounts.conf
  18. DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}"
  19. DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}"
  20. if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
  21. DnsServices_Username=""
  22. DnsServices_Password=""
  23. _err "You didn't specify dns.services api username and password yet."
  24. _err "Set environment variables DnsServices_Username and DnsServices_Password"
  25. return 1
  26. fi
  27. # Setup GET/POST/DELETE headers
  28. _setup_headers
  29. #save the credentials to the account conf file.
  30. _saveaccountconf_mutable DnsServices_Username "$DnsServices_Username"
  31. _saveaccountconf_mutable DnsServices_Password "$DnsServices_Password"
  32. if ! _contains "$DnsServices_Username" "@"; then
  33. _err "It seems that the username variable DnsServices_Username has not been set/left blank"
  34. _err "or is not a valid email. Please correct and try again."
  35. return 1
  36. fi
  37. if ! _get_root "${fulldomain}"; then
  38. _err "Invalid domain ${fulldomain}"
  39. return 1
  40. fi
  41. if ! createRecord "$fulldomain" "${txtvalue}"; then
  42. _err "Error creating TXT record in domain $fulldomain in $rootZoneName"
  43. return 1
  44. fi
  45. _debug2 challenge-created "Created $fulldomain"
  46. return 0
  47. }
  48. #Usage: fulldomain txtvalue
  49. #Description: Remove the txt record after validation.
  50. dns_dnsservices_rm() {
  51. fulldomain=$1
  52. txtvalue=$2
  53. _info "Using dns.services to delete challenge $fulldomain TXT $txtvalue"
  54. _debug rm_fulldomain "$fulldomain"
  55. _debug rm_txtvalue "$txtvalue"
  56. # Read username/password from environment or .acme.sh/accounts.conf
  57. DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}"
  58. DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}"
  59. if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
  60. DnsServices_Username=""
  61. DnsServices_Password=""
  62. _err "You didn't specify dns.services api username and password yet."
  63. _err "Set environment variables DnsServices_Username and DnsServices_Password"
  64. return 1
  65. fi
  66. # Setup GET/POST/DELETE headers
  67. _setup_headers
  68. if ! _get_root "${fulldomain}"; then
  69. _err "Invalid domain ${fulldomain}"
  70. return 1
  71. fi
  72. _debug2 rm_rootDomainInfo "found root domain $rootZoneName for $fulldomain"
  73. if ! deleteRecord "${fulldomain}" "${txtvalue}"; then
  74. _err "Error removing record: $fulldomain TXT ${txtvalue}"
  75. return 1
  76. fi
  77. return 0
  78. }
  79. #################### Private functions below ##################################
  80. _setup_headers() {
  81. # Set up API Headers for _get() and _post()
  82. # The <function>_add or <function>_rm must have been called before to work
  83. if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
  84. _err "Could not setup BASIC authentication headers, they are missing"
  85. return 1
  86. fi
  87. DnsServiceCredentials="$(printf "%s" "$DnsServices_Username:$DnsServices_Password" | _base64)"
  88. export _H1="Authorization: Basic $DnsServiceCredentials"
  89. export _H2="Content-Type: application/json"
  90. # Just return if headers are set
  91. return 0
  92. }
  93. _get_root() {
  94. domain=$1
  95. _debug2 _get_root "Get the root domain of ${domain} for DNS API"
  96. # Setup _get() and _post() headers
  97. #_setup_headers
  98. result=$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/dns")
  99. _debug2 _get_root "Got the following root domain(s) $result"
  100. _debug2 _get_root "- JSON: $result"
  101. if [ "$(echo "$result" | grep -c '"name"')" -gt "1" ]; then
  102. checkMultiZones="true"
  103. _debug2 _get_root "- multiple zones found"
  104. else
  105. checkMultiZones="false"
  106. fi
  107. # Find/isolate the root zone to work with in createRecord() and deleteRecord()
  108. rootZone=""
  109. if [ "$checkMultiZones" = "true" ]; then
  110. rootZone=$(for zone in $(echo "$result" | tr -d '\n' ' '); do
  111. if [ "$(echo "$domain" | grep "$zone")" != "" ]; then
  112. _debug2 _get_root "- trying to figure out if $zone is in $domain"
  113. echo "$zone"
  114. break
  115. fi
  116. done)
  117. else
  118. rootZone=$(echo "$result" | grep -o '"name":"[^"]*' | cut -d'"' -f4)
  119. _debug2 _get_root "- only found 1 domain in API: $rootZone"
  120. fi
  121. if [ -z "$rootZone" ]; then
  122. _err "Could not find root domain for $domain - is it correctly typed?"
  123. return 1
  124. fi
  125. # Setup variables used by other functions to communicate with DNS.Services API
  126. zoneInfo=$(echo "$result" | sed "s,\"zones,\n&,g" | grep zones | cut -d'[' -f2 | cut -d']' -f1 | tr '}' '\n' | grep "\"$rootZone\"")
  127. rootZoneName="$rootZone"
  128. subDomainName="$(echo "$domain" | sed "s,\.$rootZone,,g")"
  129. subDomainNameClean="$(echo "$domain" | sed "s,_acme-challenge.,,g")"
  130. rootZoneDomainID=$(echo "$zoneInfo" | tr ',' '\n' | grep domain_id | cut -d'"' -f4)
  131. rootZoneServiceID=$(echo "$zoneInfo" | tr ',' '\n' | grep service_id | cut -d'"' -f4)
  132. _debug2 _get_root "Root zone name : $rootZoneName"
  133. _debug2 _get_root "Root zone domain ID : $rootZoneDomainID"
  134. _debug2 _get_root "Root zone service ID: $rootZoneServiceID"
  135. _debug2 _get_root "Sub domain : $subDomainName"
  136. _debug _get_root "Found valid root domain $rootZone for $subDomainNameClean"
  137. return 0
  138. }
  139. createRecord() {
  140. fulldomain=$1
  141. txtvalue="$2"
  142. # Get root domain information - needed for DNS.Services API communication
  143. if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
  144. _get_root "$fulldomain"
  145. fi
  146. _debug2 createRecord "CNAME TXT value is: $txtvalue"
  147. # Prepare data to send to API
  148. data="{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\", \"ttl\":\"10\"}"
  149. _debug2 createRecord "data to API: $data"
  150. result=$(_post "$data" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records" "" "POST")
  151. _debug2 createRecord "result from API: $result"
  152. if [ "$(echo "$result" | grep '"success":true')" = "" ]; then
  153. _err "Failed to create TXT record $fulldomain with content $txtvalue in zone $rootZoneName"
  154. _err "$result"
  155. return 1
  156. fi
  157. _info "Record \"$fulldomain TXT $txtvalue\" has been created"
  158. return 0
  159. }
  160. deleteRecord() {
  161. fulldomain=$1
  162. txtvalue=$2
  163. _debug2 deleteRecord "Deleting $fulldomain TXT $txtvalue record"
  164. if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
  165. _get_root "$fulldomain"
  166. fi
  167. result="$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID")"
  168. recordInfo="$(echo "$result" | tr '}' '\n' | grep "\"name\":\"${fulldomain}" | grep "\"content\":\"" | grep "${txtvalue}")"
  169. _debug2 deleteRecord "recordInfo=$recordInfo"
  170. recordID="$(echo "$recordInfo" | tr ',' '\n' | grep -E "\"id\":\"[0-9]+\"" | cut -d'"' -f4)"
  171. if [ -z "$recordID" ]; then
  172. _info "Record $fulldomain TXT $txtvalue not found or already deleted"
  173. return 0
  174. else
  175. _debug2 deleteRecord "Found recordID=$recordID"
  176. fi
  177. _debug2 deleteRecord "DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID"
  178. result="$(_H1="$_H1" _H2="$_H2" _post "" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" "" "DELETE")"
  179. _debug2 deleteRecord "API Delete result \"$result\""
  180. # Return OK regardless
  181. return 0
  182. }