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

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