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. _debug 'Response' "$response"
  36. if _contains "$response" 'recordId'; then
  37. return 0
  38. else
  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. _debug 'Response' "$response"
  57. if ! _contains "$response" "$txtvalue"; then
  58. _info 'DNS record not found. Nothing to remove.'
  59. return 1
  60. fi
  61. record_id=$(
  62. echo "$response" |
  63. sed -En 's/.*"recordId":([0-9]+).*"text":"'"${txtvalue}"'".*$/\1/p'
  64. )
  65. if [ -z "$record_id" ]; then
  66. _err 'Unable to get record ID to remove'
  67. return 1
  68. fi
  69. _debug 'Removing DNS record' "$record_id"
  70. delete_url="${YANDEX360_API_BASE}/${YANDEX360_ORG_ID}/domains/${root_domain}/dns/${record_id}"
  71. response="$(_post '' "$delete_url" '' 'DELETE')"
  72. response="$(echo "$response" | _normalizeJson)"
  73. _debug 'Response' "$response"
  74. if _contains "$response" '{}'; then
  75. return 0
  76. else
  77. return 1
  78. fi
  79. }
  80. #################### Private functions below ##################################
  81. _check_yandex360_variables() {
  82. YANDEX360_CLIENT_ID="${YANDEX360_CLIENT_ID:-$(_readaccountconf_mutable YANDEX360_CLIENT_ID)}"
  83. YANDEX360_CLIENT_SECRET="${YANDEX360_CLIENT_SECRET:-$(_readaccountconf_mutable YANDEX360_CLIENT_SECRET)}"
  84. YANDEX360_ORG_ID="${YANDEX360_ORG_ID:-$(_readaccountconf_mutable YANDEX360_ORG_ID)}"
  85. YANDEX360_ACCESS_TOKEN="${YANDEX360_ACCESS_TOKEN:-$(_readaccountconf_mutable YANDEX360_ACCESS_TOKEN)}"
  86. YANDEX360_REFRESH_TOKEN="${YANDEX360_REFRESH_TOKEN:-$(_readaccountconf_mutable YANDEX360_REFRESH_TOKEN)}"
  87. if [ -z "$YANDEX360_ORG_ID" ]; then
  88. _err '========================================='
  89. _err ' ERROR'
  90. _err '========================================='
  91. _err "A required environment variable YANDEX360_ORG_ID is not set"
  92. _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  93. _err '========================================='
  94. return 1
  95. fi
  96. _saveaccountconf_mutable YANDEX360_ORG_ID "$YANDEX360_ORG_ID"
  97. if [ -n "$YANDEX360_ACCESS_TOKEN" ]; then
  98. _info '========================================='
  99. _info ' ATTENTION'
  100. _info '========================================='
  101. _info 'A manually provided Yandex 360 access token has been detected, which is not recommended.'
  102. _info 'Please note that this token is valid for a limited time after issuance.'
  103. _info 'It is recommended to obtain the token interactively using acme.sh for one-time setup.'
  104. _info 'Subsequent token renewals will be handled automatically.'
  105. _info 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  106. _info '========================================='
  107. _saveaccountconf_mutable YANDEX360_ACCESS_TOKEN "$YANDEX360_ACCESS_TOKEN"
  108. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  109. return 0
  110. fi
  111. if [ -z "$YANDEX360_CLIENT_ID" ] || [ -z "$YANDEX360_CLIENT_SECRET" ]; then
  112. _err '========================================='
  113. _err ' ERROR'
  114. _err '========================================='
  115. _err 'The preferred environment variables YANDEX360_CLIENT_ID, YANDEX360_CLIENT_SECRET, and YANDEX360_ORG_ID, or alternatively YANDEX360_ACCESS_TOKEN, is not set.'
  116. _err 'It is recommended to export the first three variables over the latter before running acme.sh.'
  117. _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  118. _err '========================================='
  119. return 1
  120. fi
  121. _saveaccountconf_mutable YANDEX360_CLIENT_ID "$YANDEX360_CLIENT_ID"
  122. _saveaccountconf_mutable YANDEX360_CLIENT_SECRET "$YANDEX360_CLIENT_SECRET"
  123. if [ -n "$YANDEX360_REFRESH_TOKEN" ]; then
  124. _debug 'Refresh token found. Attempting to refresh access token.'
  125. if _refresh_token; then
  126. return 0
  127. fi
  128. fi
  129. if ! _get_token; then
  130. return 1
  131. fi
  132. return 0
  133. }
  134. _get_token() {
  135. _info "$(_red '=========================================')"
  136. _info "$(_red ' NOTICE')"
  137. _info "$(_red '=========================================')"
  138. _info "$(_red 'Before using the Yandex 360 API, you need to complete an authorization procedure.')"
  139. _info "$(_red 'The initial access token is obtained interactively and is a one-time operation.')"
  140. _info "$(_red 'Subsequent API requests will be handled automatically.')"
  141. _info "$(_red '=========================================')"
  142. _info 'Initiating device authorization flow'
  143. device_code_url="${YANDEX360_OAUTH_BASE}/device/code"
  144. hostname=$(uname -n)
  145. data="client_id=$YANDEX360_CLIENT_ID&device_id=acme.sh ${hostname}&device_name=acme.sh ${hostname}"
  146. response="$(_post "$data" "$device_code_url" '' 'POST')"
  147. response="$(echo "$response" | _normalizeJson)"
  148. _debug 'Response' "$response"
  149. if ! _contains "$response" 'device_code'; then
  150. _err 'Failed to get device code'
  151. return 1
  152. fi
  153. device_code=$(
  154. echo "$response" |
  155. _egrep_o '"device_code":"[^"]*"' |
  156. cut -d: -f2 |
  157. tr -d '"'
  158. )
  159. _debug 'Device code' "$device_code"
  160. user_code=$(
  161. echo "$response" |
  162. _egrep_o '"user_code":"[^"]*"' |
  163. cut -d: -f2 |
  164. tr -d '"'
  165. )
  166. _debug 'User code' "$user_code"
  167. verification_url=$(
  168. echo "$response" |
  169. _egrep_o '"verification_url":"[^"]*"' |
  170. cut -d: -f2- |
  171. tr -d '"'
  172. )
  173. _debug 'Verification URL' "$verification_url"
  174. interval=$(
  175. echo "$response" |
  176. _egrep_o '"interval":[[:space:]]*[0-9]+' |
  177. cut -d: -f2
  178. )
  179. _debug 'Polling interval' "$interval"
  180. _info "$(__red 'Please visit '"$verification_url"' and log in as an organization administrator')"
  181. _info "$(__red 'Once logged in, enter the code: '"$user_code"' on the page from the previous step')"
  182. _info "$(__red 'Waiting for authorization...')"
  183. _debug 'Polling for token'
  184. token_url="${YANDEX360_OAUTH_BASE}/token"
  185. while true; do
  186. data="grant_type=device_code&code=$device_code&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET"
  187. response="$(_post "$data" "$token_url" '' 'POST')"
  188. response="$(echo "$response" | _normalizeJson)"
  189. if _contains "$response" 'access_token'; then
  190. YANDEX360_ACCESS_TOKEN=$(
  191. echo "$response" |
  192. _egrep_o '"access_token":"[^"]*"' |
  193. cut -d: -f2- |
  194. tr -d '"'
  195. )
  196. YANDEX360_REFRESH_TOKEN=$(
  197. echo "$response" |
  198. _egrep_o '"refresh_token":"[^"]*"' |
  199. cut -d: -f2- |
  200. tr -d '"'
  201. )
  202. _secure_debug 'Response' "$response"
  203. _secure_debug 'Received access token' "$YANDEX360_ACCESS_TOKEN"
  204. _secure_debug 'Received refresh token' "$YANDEX360_REFRESH_TOKEN"
  205. _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN "$YANDEX360_REFRESH_TOKEN"
  206. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  207. _info 'Access token obtained successfully'
  208. return 0
  209. elif _contains "$response" 'authorization_pending'; then
  210. _debug 'Response' "$response"
  211. _debug "Authorization pending. Waiting $interval seconds before next attempt."
  212. _sleep "$interval"
  213. else
  214. _debug 'Response' "$response"
  215. _err 'Failed to get access token'
  216. return 1
  217. fi
  218. done
  219. }
  220. _refresh_token() {
  221. token_url="${YANDEX360_OAUTH_BASE}/token"
  222. data="grant_type=refresh_token&refresh_token=$YANDEX360_REFRESH_TOKEN&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET"
  223. response="$(_post "$data" "$token_url" '' 'POST')"
  224. response="$(echo "$response" | _normalizeJson)"
  225. if _contains "$response" 'access_token'; then
  226. YANDEX360_ACCESS_TOKEN=$(
  227. echo "$response" |
  228. _egrep_o '"access_token":"[^"]*"' |
  229. cut -d: -f2 |
  230. tr -d '"'
  231. )
  232. YANDEX360_REFRESH_TOKEN=$(
  233. echo "$response" |
  234. _egrep_o '"refresh_token":"[^"]*"' |
  235. cut -d: -f2- |
  236. tr -d '"'
  237. )
  238. _secure_debug 'Response' "$response"
  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. _debug 'Response' "$response"
  247. _info 'Failed to refresh token. Will attempt to obtain a new one.'
  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. }