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.

352 lines
11 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. OptionsAlt:
  11. YANDEX360_ORG_ID Organization ID. Optional.
  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'
  17. YANDEX360_OAUTH_BASE='https://oauth.yandex.ru'
  18. ######## Public functions #####################
  19. dns_yandex360_add() {
  20. fulldomain="$(_idn "$1")"
  21. txtvalue=$2
  22. _info 'Using Yandex 360 DNS API'
  23. if ! _check_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}/org/${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. if _contains "$response" 'recordId'; then
  35. return 0
  36. else
  37. _debug 'Response' "$response"
  38. return 1
  39. fi
  40. }
  41. dns_yandex360_rm() {
  42. fulldomain="$(_idn "$1")"
  43. txtvalue=$2
  44. _info 'Using Yandex 360 DNS API'
  45. if ! _check_variables; then
  46. return 1
  47. fi
  48. if ! _get_root "$fulldomain"; then
  49. return 1
  50. fi
  51. _debug 'Retrieving 100 records from Yandex 360 DNS'
  52. dns_api_url="${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns?perPage=100"
  53. response="$(_get "$dns_api_url" '' '')"
  54. if ! _contains "$response" "$txtvalue"; then
  55. _info 'DNS record not found. Nothing to remove.'
  56. _debug 'Response' "$response"
  57. return 1
  58. fi
  59. response="$(echo "$response" | _normalizeJson)"
  60. record_id=$(
  61. echo "$response" |
  62. _egrep_o '\{[^}]*'"${txtvalue}"'[^}]*\}' |
  63. _egrep_o '"recordId":[0-9]*' |
  64. cut -d':' -f2
  65. )
  66. if [ -z "$record_id" ]; then
  67. _err 'Unable to get record ID to remove'
  68. return 1
  69. fi
  70. _debug 'Removing DNS record' "$record_id"
  71. delete_url="${YANDEX360_API_BASE}/org/${YANDEX360_ORG_ID}/domains/${root_domain}/dns/${record_id}"
  72. response="$(_post '' "$delete_url" '' 'DELETE')"
  73. if _contains "$response" '{}'; then
  74. return 0
  75. else
  76. _debug 'Response' "$response"
  77. return 1
  78. fi
  79. }
  80. #################### Private functions below ##################################
  81. _check_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 [ -n "$YANDEX360_ACCESS_TOKEN" ]; then
  88. _info '========================================='
  89. _info ' ATTENTION'
  90. _info '========================================='
  91. _info 'A manually provided Yandex 360 access token has been detected, which is not recommended.'
  92. _info 'Please note that this token is valid for a limited time after issuance.'
  93. _info 'It is recommended to obtain the token interactively using acme.sh for one-time setup.'
  94. _info 'Subsequent token renewals will be handled automatically.'
  95. _info 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  96. _info '========================================='
  97. _saveaccountconf_mutable YANDEX360_ACCESS_TOKEN "$YANDEX360_ACCESS_TOKEN"
  98. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  99. elif [ -z "$YANDEX360_CLIENT_ID" ] || [ -z "$YANDEX360_CLIENT_SECRET" ]; then
  100. _err '========================================='
  101. _err ' ERROR'
  102. _err '========================================='
  103. _err 'The required environment variables YANDEX360_CLIENT_ID and YANDEX360_CLIENT_SECRET are not set.'
  104. _err 'Alternatively, you can set YANDEX360_ACCESS_TOKEN environment variable.'
  105. _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  106. _err '========================================='
  107. return 1
  108. else
  109. _saveaccountconf_mutable YANDEX360_CLIENT_ID "$YANDEX360_CLIENT_ID"
  110. _saveaccountconf_mutable YANDEX360_CLIENT_SECRET "$YANDEX360_CLIENT_SECRET"
  111. if [ -n "$YANDEX360_REFRESH_TOKEN" ]; then
  112. _debug 'Refresh token found. Attempting to refresh access token.'
  113. fi
  114. _refresh_token || _get_token || return 1
  115. fi
  116. if [ -z "$YANDEX360_ORG_ID" ]; then
  117. org_response="$(_get "${YANDEX360_API_BASE}/org" '' '')"
  118. if _contains "$org_response" '"organizations"'; then
  119. org_response="$(echo "$org_response" | _normalizeJson)"
  120. YANDEX360_ORG_ID=$(
  121. echo "$org_response" |
  122. _egrep_o '"id":[[:space:]]*[0-9]+' |
  123. cut -d':' -f2
  124. )
  125. _debug 'Automatically retrieved YANDEX360_ORG_ID' "$YANDEX360_ORG_ID"
  126. else
  127. _err '========================================='
  128. _err ' ERROR'
  129. _err '========================================='
  130. _err "Failed to retrieve YANDEX360_ORG_ID automatically."
  131. _err 'For more details, please visit: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yandex360'
  132. _err '========================================='
  133. _debug 'Response' "$org_response"
  134. return 1
  135. fi
  136. fi
  137. return 0
  138. }
  139. _get_token() {
  140. _info "$(__red '=========================================')"
  141. _info "$(__red ' NOTICE')"
  142. _info "$(__red '=========================================')"
  143. _info "$(__red 'Before using the Yandex 360 API, you need to complete an authorization procedure.')"
  144. _info "$(__red 'The initial access token is obtained interactively and is a one-time operation.')"
  145. _info "$(__red 'Subsequent API requests will be handled automatically.')"
  146. _info "$(__red '=========================================')"
  147. _info 'Initiating device authorization flow'
  148. device_code_url="${YANDEX360_OAUTH_BASE}/device/code"
  149. hostname=$(uname -n)
  150. data="client_id=$YANDEX360_CLIENT_ID&device_id=acme.sh ${hostname}&device_name=acme.sh ${hostname}"
  151. response="$(_post "$data" "$device_code_url" '' 'POST')"
  152. if ! _contains "$response" 'device_code'; then
  153. _err 'Failed to get device code'
  154. _debug 'Response' "$response"
  155. return 1
  156. fi
  157. response="$(echo "$response" | _normalizeJson)"
  158. device_code=$(
  159. echo "$response" |
  160. _egrep_o '"device_code":"[^"]*"' |
  161. cut -d'"' -f4
  162. )
  163. _debug 'Device code' "$device_code"
  164. user_code=$(
  165. echo "$response" |
  166. _egrep_o '"user_code":"[^"]*"' |
  167. cut -d'"' -f4
  168. )
  169. _debug 'User code' "$user_code"
  170. verification_url=$(
  171. echo "$response" |
  172. _egrep_o '"verification_url":"[^"]*"' |
  173. cut -d'"' -f4
  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. if _contains "$response" 'access_token'; then
  191. response="$(echo "$response" | _normalizeJson)"
  192. YANDEX360_ACCESS_TOKEN=$(
  193. echo "$response" |
  194. _egrep_o '"access_token":"[^"]*"' |
  195. cut -d'"' -f4
  196. )
  197. YANDEX360_REFRESH_TOKEN=$(
  198. echo "$response" |
  199. _egrep_o '"refresh_token":"[^"]*"' |
  200. cut -d'"' -f4
  201. )
  202. _secure_debug 'Obtained access token' "$YANDEX360_ACCESS_TOKEN"
  203. _secure_debug 'Obtained refresh token' "$YANDEX360_REFRESH_TOKEN"
  204. _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN "$YANDEX360_REFRESH_TOKEN"
  205. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  206. _info 'Access token obtained successfully'
  207. return 0
  208. elif _contains "$response" 'authorization_pending'; then
  209. _debug 'Response' "$response"
  210. _debug "Authorization pending. Waiting $interval seconds before next attempt."
  211. _sleep "$interval"
  212. else
  213. _debug 'Response' "$response"
  214. _err 'Failed to get access token'
  215. return 1
  216. fi
  217. done
  218. }
  219. _refresh_token() {
  220. token_url="${YANDEX360_OAUTH_BASE}/token"
  221. data="grant_type=refresh_token&refresh_token=$YANDEX360_REFRESH_TOKEN&client_id=$YANDEX360_CLIENT_ID&client_secret=$YANDEX360_CLIENT_SECRET"
  222. response="$(_post "$data" "$token_url" '' 'POST')"
  223. if _contains "$response" 'access_token'; then
  224. response="$(echo "$response" | _normalizeJson)"
  225. YANDEX360_ACCESS_TOKEN=$(
  226. echo "$response" |
  227. _egrep_o '"access_token":"[^"]*"' |
  228. cut -d'"' -f4
  229. )
  230. YANDEX360_REFRESH_TOKEN=$(
  231. echo "$response" |
  232. _egrep_o '"refresh_token":"[^"]*"' |
  233. cut -d'"' -f4
  234. )
  235. _secure_debug 'Received access token' "$YANDEX360_ACCESS_TOKEN"
  236. _secure_debug 'Received refresh token' "$YANDEX360_REFRESH_TOKEN"
  237. _saveaccountconf_mutable YANDEX360_REFRESH_TOKEN "$YANDEX360_REFRESH_TOKEN"
  238. export _H1="Authorization: OAuth $YANDEX360_ACCESS_TOKEN"
  239. _info 'Access token refreshed successfully'
  240. return 0
  241. else
  242. _info 'Failed to refresh token. Will attempt to obtain a new one.'
  243. _debug 'Response' "$response"
  244. return 1
  245. fi
  246. }
  247. _get_root() {
  248. domain="$1"
  249. for org_id in $YANDEX360_ORG_ID; do
  250. _debug 'Checking organization ID' "$org_id"
  251. domains_api_url="${YANDEX360_API_BASE}/org/${org_id}/domains"
  252. domains_response="$(_get "$domains_api_url" '' '')"
  253. if ! _contains "$domains_response" '"domains"'; then
  254. _debug 'No domains found for organization' "$org_id"
  255. _debug 'Response' "$domains_response"
  256. continue
  257. fi
  258. domains_response="$(echo "$domains_response" | _normalizeJson)"
  259. domain_names=$(
  260. echo "$domains_response" |
  261. _egrep_o '"name":"[^"]*"' |
  262. cut -d'"' -f4
  263. )
  264. for d in $domain_names; do
  265. d="$(_idn "$d")"
  266. _debug 'Checking domain' "$d"
  267. if _endswith "$domain" "$d"; then
  268. root_domain="$d"
  269. break
  270. fi
  271. done
  272. if [ -n "$root_domain" ]; then
  273. _debug "Root domain found: $root_domain in organization $org_id"
  274. YANDEX360_ORG_ID="$org_id"
  275. _saveaccountconf_mutable YANDEX360_ORG_ID "$YANDEX360_ORG_ID"
  276. return 0
  277. fi
  278. done
  279. if [ -z "$root_domain" ]; then
  280. _err "Could not find a matching root domain for $domain in any organization"
  281. return 1
  282. fi
  283. }