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.

334 lines
10 KiB

  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. dns_yandex360_info='Yandex 360 for Business DNS API.
  4. Yandex 360 for Business is a digital environment for effective collaboration.
  5. Site: https://360.yandex.com/
  6. Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360
  7. Options:
  8. YANDEX360_CLIENT_ID OAuth 2.0 ClientID
  9. YANDEX360_CLIENT_SECRET OAuth 2.0 Client secret
  10. YANDEX360_ORG_ID Organization ID
  11. OptionsAlt:
  12. YANDEX360_ACCESS_TOKEN OAuth 2.0 Access token. Optional.
  13. Issues: https://github.com/acmesh-official/acme.sh/issues/5213
  14. Author: <Als@admin.ru.net>
  15. '
  16. YANDEX360_API_BASE='https://api360.yandex.net/directory/v1/org'
  17. YANDEX360_OAUTH_BASE='https://oauth.yandex.ru'
  18. ######## Public functions #####################
  19. dns_yandex360_add() {
  20. fulldomain=$1
  21. txtvalue=$2
  22. _info 'Using Yandex 360 DNS API'
  23. if ! _check_yandex360_variables; then
  24. return 1
  25. fi
  26. if ! _get_root "$fulldomain"; then
  27. return 1
  28. fi
  29. sub_domain=$(echo "$fulldomain" | sed "s/\.$root_domain$//")
  30. _debug 'Adding Yandex 360 DNS record for subdomain' "$sub_domain"
  31. dns_api_url="${YANDEX360_API_BASE}/${YANDEX360_ORG_ID}/domains/${root_domain}/dns"
  32. data='{"name":"'"$sub_domain"'","type":"TXT","ttl":60,"text":"'"$txtvalue"'"}'
  33. response="$(_post "$data" "$dns_api_url" '' 'POST' 'application/json')"
  34. response="$(echo "$response" | _normalizeJson)"
  35. if _contains "$response" 'recordId'; then
  36. return 0
  37. else
  38. _debug 'Response' "$response"
  39. return 1
  40. fi
  41. }
  42. dns_yandex360_rm() {
  43. fulldomain=$1
  44. txtvalue=$2
  45. _info 'Using Yandex 360 DNS API'
  46. if ! _check_yandex360_variables; then
  47. return 1
  48. fi
  49. if ! _get_root "$fulldomain"; then
  50. return 1
  51. fi
  52. _debug 'Retrieving 100 records from Yandex 360 DNS'
  53. dns_api_url="${YANDEX360_API_BASE}/${YANDEX360_ORG_ID}/domains/${root_domain}/dns?perPage=100"
  54. response="$(_get "$dns_api_url" '' '')"
  55. response="$(echo "$response" | _normalizeJson)"
  56. if ! _contains "$response" "$txtvalue"; then
  57. _info 'DNS record not found. Nothing to remove.'
  58. _debug 'Response' "$response"
  59. return 1
  60. fi
  61. record_id=$(
  62. echo "$response" |
  63. _egrep_o '\{[^}]*'"${txtvalue}"'[^}]*\}' |
  64. _egrep_o '"recordId":[0-9]*' |
  65. cut -d':' -f2
  66. )
  67. if [ -z "$record_id" ]; then
  68. _err 'Unable to get record ID to remove'
  69. return 1
  70. fi
  71. _debug 'Removing DNS record' "$record_id"
  72. delete_url="${YANDEX360_API_BASE}/${YANDEX360_ORG_ID}/domains/${root_domain}/dns/${record_id}"
  73. response="$(_post '' "$delete_url" '' 'DELETE')"
  74. response="$(echo "$response" | _normalizeJson)"
  75. if _contains "$response" '{}'; then
  76. return 0
  77. else
  78. _debug 'Response' "$response"
  79. return 1
  80. fi
  81. }
  82. #################### Private functions below ##################################
  83. _check_yandex360_variables() {
  84. YANDEX360_CLIENT_ID="${YANDEX360_CLIENT_ID:-$(_readaccountconf_mutable YANDEX360_CLIENT_ID)}"
  85. YANDEX360_CLIENT_SECRET="${YANDEX360_CLIENT_SECRET:-$(_readaccountconf_mutable YANDEX360_CLIENT_SECRET)}"
  86. YANDEX360_ORG_ID="${YANDEX360_ORG_ID:-$(_readaccountconf_mutable YANDEX360_ORG_ID)}"
  87. YANDEX360_ACCESS_TOKEN="${YANDEX360_ACCESS_TOKEN:-$(_readaccountconf_mutable YANDEX360_ACCESS_TOKEN)}"
  88. YANDEX360_REFRESH_TOKEN="${YANDEX360_REFRESH_TOKEN:-$(_readaccountconf_mutable YANDEX360_REFRESH_TOKEN)}"
  89. if [ -z "$YANDEX360_ORG_ID" ]; then
  90. _err '========================================='
  91. _err ' ERROR'
  92. _err '========================================='
  93. _err "A required environment variable YANDEX360_ORG_ID is not set"
  94. _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  95. _err '========================================='
  96. return 1
  97. fi
  98. _saveaccountconf_mutable YANDEX360_ORG_ID "$YANDEX360_ORG_ID"
  99. if [ -n "$YANDEX360_ACCESS_TOKEN" ]; then
  100. _info '========================================='
  101. _info ' ATTENTION'
  102. _info '========================================='
  103. _info 'A manually provided Yandex 360 access token has been detected, which is not recommended.'
  104. _info 'Please note that this token is valid for a limited time after issuance.'
  105. _info 'It is recommended to obtain the token interactively using acme.sh for one-time setup.'
  106. _info 'Subsequent token renewals will be handled automatically.'
  107. _info 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  108. _info '========================================='
  109. _saveaccountconf_mutable YANDEX360_ACCESS_TOKEN "$YANDEX360_ACCESS_TOKEN"
  110. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  111. return 0
  112. fi
  113. if [ -z "$YANDEX360_CLIENT_ID" ] || [ -z "$YANDEX360_CLIENT_SECRET" ]; then
  114. _err '========================================='
  115. _err ' ERROR'
  116. _err '========================================='
  117. _err 'The preferred environment variables YANDEX360_CLIENT_ID, YANDEX360_CLIENT_SECRET, and YANDEX360_ORG_ID, or alternatively YANDEX360_ACCESS_TOKEN, is not set.'
  118. _err 'It is recommended to export the first three variables over the latter before running acme.sh.'
  119. _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  120. _err '========================================='
  121. return 1
  122. fi
  123. _saveaccountconf_mutable YANDEX360_CLIENT_ID "$YANDEX360_CLIENT_ID"
  124. _saveaccountconf_mutable YANDEX360_CLIENT_SECRET "$YANDEX360_CLIENT_SECRET"
  125. if [ -n "$YANDEX360_REFRESH_TOKEN" ]; then
  126. _debug 'Refresh token found. Attempting to refresh access token.'
  127. if _refresh_token; then
  128. return 0
  129. fi
  130. fi
  131. if ! _get_token; then
  132. return 1
  133. fi
  134. return 0
  135. }
  136. _get_token() {
  137. _info "$(_red '=========================================')"
  138. _info "$(_red ' NOTICE')"
  139. _info "$(_red '=========================================')"
  140. _info "$(_red 'Before using the Yandex 360 API, you need to complete an authorization procedure.')"
  141. _info "$(_red 'The initial access token is obtained interactively and is a one-time operation.')"
  142. _info "$(_red 'Subsequent API requests will be handled automatically.')"
  143. _info "$(_red '=========================================')"
  144. _info 'Initiating device authorization flow'
  145. device_code_url="${YANDEX360_OAUTH_BASE}/device/code"
  146. hostname=$(uname -n)
  147. data="client_id=$YANDEX360_CLIENT_ID&device_id=acme.sh ${hostname}&device_name=acme.sh ${hostname}"
  148. response="$(_post "$data" "$device_code_url" '' 'POST')"
  149. response="$(echo "$response" | _normalizeJson)"
  150. if ! _contains "$response" 'device_code'; then
  151. _err 'Failed to get device code'
  152. _debug 'Response' "$response"
  153. return 1
  154. fi
  155. device_code=$(
  156. echo "$response" |
  157. _egrep_o '"device_code":"[^"]*"' |
  158. cut -d: -f2 |
  159. tr -d '"'
  160. )
  161. _debug 'Device code' "$device_code"
  162. user_code=$(
  163. echo "$response" |
  164. _egrep_o '"user_code":"[^"]*"' |
  165. cut -d: -f2 |
  166. tr -d '"'
  167. )
  168. _debug 'User code' "$user_code"
  169. verification_url=$(
  170. echo "$response" |
  171. _egrep_o '"verification_url":"[^"]*"' |
  172. cut -d: -f2- |
  173. tr -d '"'
  174. )
  175. _debug 'Verification URL' "$verification_url"
  176. interval=$(
  177. echo "$response" |
  178. _egrep_o '"interval":[[:space:]]*[0-9]+' |
  179. cut -d: -f2
  180. )
  181. _debug 'Polling interval' "$interval"
  182. _info "$(__red 'Please visit '"$verification_url"' and log in as an organization administrator')"
  183. _info "$(__red 'Once logged in, enter the code: '"$user_code"' on the page from the previous step')"
  184. _info "$(__red 'Waiting for authorization...')"
  185. _debug 'Polling for token'
  186. token_url="${YANDEX360_OAUTH_BASE}/token"
  187. while true; do
  188. data="grant_type=device_code&code=$device_code&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET"
  189. response="$(_post "$data" "$token_url" '' 'POST')"
  190. response="$(echo "$response" | _normalizeJson)"
  191. if _contains "$response" 'access_token'; then
  192. YANDEX360_ACCESS_TOKEN=$(
  193. echo "$response" |
  194. _egrep_o '"access_token":"[^"]*"' |
  195. cut -d: -f2- |
  196. tr -d '"'
  197. )
  198. YANDEX360_REFRESH_TOKEN=$(
  199. echo "$response" |
  200. _egrep_o '"refresh_token":"[^"]*"' |
  201. cut -d: -f2- |
  202. tr -d '"'
  203. )
  204. _secure_debug 'Obtained access token' "$YANDEX360_ACCESS_TOKEN"
  205. _secure_debug 'Obtained refresh token' "$YANDEX360_REFRESH_TOKEN"
  206. _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN "$YANDEX360_REFRESH_TOKEN"
  207. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  208. _info 'Access token obtained successfully'
  209. return 0
  210. elif _contains "$response" 'authorization_pending'; then
  211. _debug 'Response' "$response"
  212. _debug "Authorization pending. Waiting $interval seconds before next attempt."
  213. _sleep "$interval"
  214. else
  215. _debug 'Response' "$response"
  216. _err 'Failed to get access token'
  217. return 1
  218. fi
  219. done
  220. }
  221. _refresh_token() {
  222. token_url="${YANDEX360_OAUTH_BASE}/token"
  223. data="grant_type=refresh_token&refresh_token=$YANDEX360_REFRESH_TOKEN&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET"
  224. response="$(_post "$data" "$token_url" '' 'POST')"
  225. response="$(echo "$response" | _normalizeJson)"
  226. if _contains "$response" 'access_token'; then
  227. YANDEX360_ACCESS_TOKEN=$(
  228. echo "$response" |
  229. _egrep_o '"access_token":"[^"]*"' |
  230. cut -d: -f2 |
  231. tr -d '"'
  232. )
  233. YANDEX360_REFRESH_TOKEN=$(
  234. echo "$response" |
  235. _egrep_o '"refresh_token":"[^"]*"' |
  236. cut -d: -f2- |
  237. tr -d '"'
  238. )
  239. _secure_debug 'Received access token' "$YANDEX360_ACCESS_TOKEN"
  240. _secure_debug 'Received refresh token' "$YANDEX360_REFRESH_TOKEN"
  241. _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN "$YANDEX360_REFRESH_TOKEN"
  242. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  243. _info 'Access token refreshed successfully'
  244. return 0
  245. else
  246. _info 'Failed to refresh token. Will attempt to obtain a new one.'
  247. _debug 'Response' "$response"
  248. return 1
  249. fi
  250. }
  251. _get_root() {
  252. domain="$1"
  253. i=1
  254. while true; do
  255. h=$(echo "$domain" | cut -d . -f "$i"-)
  256. _debug "Checking domain: $h"
  257. if [ -z "$h" ]; then
  258. _err "Could not determine root domain"
  259. return 1
  260. fi
  261. dns_api_url="${YANDEX360_API_BASE}/${YANDEX360_ORG_ID}/domains/${h}/dns"
  262. response="$(_get "$dns_api_url" '' '')"
  263. response="$(echo "$response" | _normalizeJson)"
  264. _debug 'Response' "$response"
  265. if _contains "$response" '"total":'; then
  266. root_domain="$h"
  267. _debug 'Root domain found' "$root_domain"
  268. return 0
  269. fi
  270. i=$(_math "$i" + 1)
  271. done
  272. }