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.

184 lines
4.6 KiB

  1. #!/usr/bin/env sh
  2. # Supports IONOS DNS API Beta v1.0.0
  3. #
  4. # Usage:
  5. # Export IONOS_PREFIX and IONOS_SECRET before calling acme.sh:
  6. #
  7. # $ export IONOS_PREFIX="..."
  8. # $ export IONOS_SECRET="..."
  9. #
  10. # $ acme.sh --issue --dns dns_ionos ...
  11. IONOS_API="https://api.hosting.ionos.com/dns"
  12. IONOS_ROUTE_ZONES="/v1/zones"
  13. IONOS_TXT_TTL=60 # minimum accepted by API
  14. IONOS_TXT_PRIO=10
  15. dns_ionos_add() {
  16. fulldomain=$1
  17. txtvalue=$2
  18. if ! _ionos_init; then
  19. return 1
  20. fi
  21. _new_record="{\"name\":\"$_sub_domain.$_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":$IONOS_TXT_TTL,\"prio\":$IONOS_TXT_PRIO,\"disabled\":false}"
  22. # As no POST route is supported by the API, check for existing records and include them in the PATCH request in order not delete them.
  23. # This is required to support ACME v2 wildcard certificate creation, where two TXT records for the same domain name are created.
  24. _ionos_get_existing_records "$fulldomain" "$_zone_id"
  25. if [ "$_existing_records" ]; then
  26. _body="[$_new_record,$_existing_records]"
  27. else
  28. _body="[$_new_record]"
  29. fi
  30. if _ionos_rest PATCH "$IONOS_ROUTE_ZONES/$_zone_id" "$_body" && [ -z "$response" ]; then
  31. _info "TXT record has been created successfully."
  32. return 0
  33. fi
  34. return 1
  35. }
  36. dns_ionos_rm() {
  37. fulldomain=$1
  38. txtvalue=$2
  39. if ! _ionos_init; then
  40. return 1
  41. fi
  42. if ! _ionos_get_record "$fulldomain" "$_zone_id" "$txtvalue"; then
  43. _err "Could not find _acme-challenge TXT record."
  44. return 1
  45. fi
  46. if _ionos_rest DELETE "$IONOS_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ -z "$response" ]; then
  47. _info "TXT record has been deleted successfully."
  48. return 0
  49. fi
  50. return 1
  51. }
  52. _ionos_init() {
  53. IONOS_PREFIX="${IONOS_PREFIX:-$(_readaccountconf_mutable IONOS_PREFIX)}"
  54. IONOS_SECRET="${IONOS_SECRET:-$(_readaccountconf_mutable IONOS_SECRET)}"
  55. if [ -z "$IONOS_PREFIX" ] || [ -z "$IONOS_SECRET" ]; then
  56. _err "You didn't specify an IONOS api prefix and secret yet."
  57. _err "Read https://beta.developer.hosting.ionos.de/docs/getstarted to learn how to get a prefix and secret."
  58. _err ""
  59. _err "Then set them before calling acme.sh:"
  60. _err "\$ export IONOS_PREFIX=\"...\""
  61. _err "\$ export IONOS_SECRET=\"...\""
  62. _err "\$ acme.sh --issue -d ... --dns dns_ionos"
  63. return 1
  64. fi
  65. _saveaccountconf_mutable IONOS_PREFIX "$IONOS_PREFIX"
  66. _saveaccountconf_mutable IONOS_SECRET "$IONOS_SECRET"
  67. if ! _get_root "$fulldomain"; then
  68. _err "Cannot find this domain in your IONOS account."
  69. return 1
  70. fi
  71. }
  72. _get_root() {
  73. domain=$1
  74. i=2
  75. p=1
  76. if _ionos_rest GET "$IONOS_ROUTE_ZONES"; then
  77. response="$(echo "$response" | tr -d "\n")"
  78. while true; do
  79. h=$(printf "%s" "$domain" | cut -d . -f $i-100)
  80. if [ -z "$h" ]; then
  81. return 1
  82. fi
  83. _zone="$(echo "$response" | _egrep_o "\"name\":\"$h\".*?}")"
  84. if [ "$_zone" ]; then
  85. _zone_id=$(printf "%s\n" "$_zone" | _egrep_o "\"id\":\"[a-fA-F0-9-]+\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
  86. if [ "$_zone_id" ]; then
  87. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
  88. _domain=$h
  89. return 0
  90. fi
  91. return 1
  92. fi
  93. p=$i
  94. i=$(_math "$i" + 1)
  95. done
  96. fi
  97. return 1
  98. }
  99. _ionos_get_existing_records() {
  100. fulldomain=$1
  101. zone_id=$2
  102. if _ionos_rest GET "$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT"; then
  103. response="$(echo "$response" | tr -d "\n")"
  104. _existing_records="$(printf "%s\n" "$response" | _egrep_o "\"records\":\[.*?\]" | _head_n 1 | cut -d '[' -f 2 | sed 's/]//')"
  105. fi
  106. }
  107. _ionos_get_record() {
  108. fulldomain=$1
  109. zone_id=$2
  110. txtrecord=$3
  111. if _ionos_rest GET "$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT"; then
  112. response="$(echo "$response" | tr -d "\n")"
  113. _record="$(echo "$response" | _egrep_o "\{\"name\":\"$fulldomain\"[^\}]*?\"type\":\"TXT\"[^\}]*?\"content\":\"\\\\\"$txtrecord\\\\\"\".*?\}")"
  114. if [ "$_record" ]; then
  115. _record_id=$(printf "%s\n" "$_record" | _egrep_o "\"id\":\"[a-fA-F0-9-]+\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
  116. return 0
  117. fi
  118. fi
  119. return 1
  120. }
  121. _ionos_rest() {
  122. method="$1"
  123. route="$2"
  124. data="$3"
  125. IONOS_API_KEY="$(printf "%s.%s" "$IONOS_PREFIX" "$IONOS_SECRET")"
  126. export _H1="X-API-Key: $IONOS_API_KEY"
  127. if [ "$method" != "GET" ]; then
  128. export _H2="Accept: application/json"
  129. export _H3="Content-Type: application/json"
  130. response="$(_post "$data" "$IONOS_API$route" "" "$method")"
  131. else
  132. export _H2="Accept: */*"
  133. response="$(_get "$IONOS_API$route")"
  134. fi
  135. if [ "$?" != "0" ]; then
  136. _err "Error $route"
  137. return 1
  138. fi
  139. return 0
  140. }