dns_qc.sh 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. dns_qc_info='QUIC.cloud
  4. Site: quic.cloud
  5. Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_qc
  6. Options:
  7. QC_API_KEY QC API Key
  8. QC_API_EMAIL Your account email
  9. '
  10. QC_Api="https://api.quic.cloud/v2"
  11. ######## Public functions #####################
  12. #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  13. dns_qc_add() {
  14. fulldomain=$1
  15. txtvalue=$2
  16. _debug "Enter dns_qc_add fulldomain: $fulldomain, txtvalue: $txtvalue"
  17. QC_API_KEY="${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}"
  18. QC_API_EMAIL="${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}"
  19. if [ "$QC_API_KEY" ]; then
  20. _saveaccountconf_mutable QC_API_KEY "$QC_API_KEY"
  21. else
  22. _err "You didn't specify a QUIC.cloud api key as QC_API_KEY."
  23. _err "You can get yours from here https://my.quic.cloud/up/api."
  24. return 1
  25. fi
  26. if ! _contains "$QC_API_EMAIL" "@"; then
  27. _err "It seems that the QC_API_EMAIL=$QC_API_EMAIL is not a valid email address."
  28. _err "Please check and retry."
  29. return 1
  30. fi
  31. #save the api key and email to the account conf file.
  32. _saveaccountconf_mutable QC_API_EMAIL "$QC_API_EMAIL"
  33. _debug "First detect the root zone"
  34. if ! _get_root "$fulldomain"; then
  35. _err "invalid domain during add"
  36. return 1
  37. fi
  38. _debug _domain_id "$_domain_id"
  39. _debug _sub_domain "$_sub_domain"
  40. _debug _domain "$_domain"
  41. _debug "Getting txt records"
  42. _qc_rest GET "zones/${_domain_id}/records"
  43. if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
  44. _err "Error failed response from QC GET: $response"
  45. return 1
  46. fi
  47. # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
  48. # we can not use updating anymore.
  49. # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
  50. # _debug count "$count"
  51. # if [ "$count" = "0" ]; then
  52. _info "Adding txt record"
  53. if _qc_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":1800}"; then
  54. if _contains "$response" "$txtvalue"; then
  55. _info "Added txt record, OK"
  56. return 0
  57. elif _contains "$response" "Same record already exists"; then
  58. _info "txt record already exists, OK"
  59. return 0
  60. else
  61. _err "Add txt record error: $response"
  62. return 1
  63. fi
  64. fi
  65. _err "Add txt record error: POST failed: $response"
  66. return 1
  67. }
  68. #fulldomain txtvalue
  69. dns_qc_rm() {
  70. fulldomain=$1
  71. txtvalue=$2
  72. _debug "Enter dns_qc_rm fulldomain: $fulldomain, txtvalue: $txtvalue"
  73. QC_API_KEY="${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}"
  74. QC_API_EMAIL="${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}"
  75. _debug "First detect the root zone"
  76. if ! _get_root "$fulldomain"; then
  77. _err "invalid domain during rm"
  78. return 1
  79. fi
  80. _debug _domain_id "$_domain_id"
  81. _debug _sub_domain "$_sub_domain"
  82. _debug _domain "$_domain"
  83. _debug "Getting txt records"
  84. _qc_rest GET "zones/${_domain_id}/records"
  85. if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
  86. _err "Error rm GET response: $response"
  87. return 1
  88. fi
  89. _debug "Pre-jq response:" "$response"
  90. # Do not use jq or subsequent code
  91. #response=$(echo "$response" | jq ".result[] | select(.id) | select(.content == \"$txtvalue\") | select(.type == \"TXT\")")
  92. #_debug "get txt response" "$response"
  93. #if [ "${response}" = "" ]; then
  94. # _info "Don't need to remove txt records."
  95. # return 0
  96. #fi
  97. #record_id=$(echo "$response" | grep \"id\" | awk -F ' ' '{print $2}' | sed 's/,$//')
  98. #_debug "txt record_id" "$record_id"
  99. #Instead of jq
  100. array=$(echo "$response" | grep -o '\[[^]]*\]' | sed 's/^\[\(.*\)\]$/\1/')
  101. if [ -z "$array" ]; then
  102. _err "Expected array in QC response: $response"
  103. return 1
  104. fi
  105. # Temporary file to hold matched content (one per line)
  106. tmpfile=$(_mktemp)
  107. echo "$array" | grep -o '{[^}]*}' | sed 's/^{//;s/}$//' >"$tmpfile"
  108. record_id=""
  109. while IFS= read -r obj || [ -n "$obj" ]; do
  110. if echo "$obj" | grep -q '"TXT"' && echo "$obj" | grep -q '"id"' && echo "$obj" | grep -q "$txtvalue"; then
  111. _debug "response includes" "$obj"
  112. record_id=$(echo "$obj" | sed 's/^\"id\":\([0-9]\+\).*/\1/')
  113. break
  114. fi
  115. done <"$tmpfile"
  116. rm "$tmpfile"
  117. if [ -z "$record_id" ]; then
  118. _info "TXT record, or $txtvalue not found, nothing to remove"
  119. return 0
  120. fi
  121. #End of jq replacement
  122. if ! _qc_rest DELETE "zones/$_domain_id/records/$record_id"; then
  123. _info "Delete txt record error."
  124. return 1
  125. fi
  126. _info "TXT Record ID: $record_id successfully deleted"
  127. return 0
  128. }
  129. #################### Private functions below ##################################
  130. #_acme-challenge.www.domain.com
  131. #returns
  132. # _sub_domain=_acme-challenge.www
  133. # _domain=domain.com
  134. # _domain_id=sdjkglgdfewsdfg
  135. _get_root() {
  136. domain=$1
  137. i=1
  138. p=1
  139. h=$(printf "%s" "$domain" | cut -d . -f2-)
  140. _debug h "$h"
  141. if [ -z "$h" ]; then
  142. _err "$h ($domain) is an invalid domain"
  143. return 1
  144. fi
  145. if ! _qc_rest GET "zones"; then
  146. _err "qc_rest failed"
  147. return 1
  148. fi
  149. if _contains "$response" "\"name\":\"$h\"" || _contains "$response" "\"name\":\"$h.\""; then
  150. _domain_id=$h
  151. if [ "$_domain_id" ]; then
  152. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  153. _domain=$h
  154. return 0
  155. fi
  156. _err "Empty domain_id $h"
  157. return 1
  158. fi
  159. _err "Missing domain_id $h"
  160. return 1
  161. }
  162. _qc_rest() {
  163. m=$1
  164. ep="$2"
  165. data="$3"
  166. _debug "$ep"
  167. email_trimmed=$(echo "$QC_API_EMAIL" | tr -d '"')
  168. token_trimmed=$(echo "$QC_API_KEY" | tr -d '"')
  169. export _H1="Content-Type: application/json"
  170. export _H2="X-Auth-Email: $email_trimmed"
  171. export _H3="X-Auth-Key: $token_trimmed"
  172. if [ "$m" != "GET" ]; then
  173. _debug data "$data"
  174. response="$(_post "$data" "$QC_Api/$ep" "" "$m")"
  175. else
  176. response="$(_get "$QC_Api/$ep")"
  177. fi
  178. if [ "$?" != "0" ]; then
  179. _err "error $ep"
  180. return 1
  181. fi
  182. _debug2 response "$response"
  183. return 0
  184. }