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.

484 lines
16 KiB

7 years ago
7 years ago
  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. # dns_selectel_info='Selectel.com
  4. # Domains: Selectel.ru
  5. # Site: Selectel.com
  6. # Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel
  7. # Options:
  8. # Variables that must be defined before running
  9. # SL_Ver can take one of the values 'v1' or 'v2', default is 'v1'
  10. # SL_Ver='v1', when using version API legacy (v1)
  11. # SL_Ver='v2', when using version API actual (v2)
  12. # when using API version v1, i.e. SL_Ver is 'v1' or not defined:
  13. # SL_Key - API Key, required
  14. # when using API version v2:
  15. # SL_Ver - required as 'v2'
  16. # SL_Login_ID - account ID, required
  17. # SL_Project_Name - name project, required
  18. # SL_Login_Name - service user name, required
  19. # SL_Pswd - service user password, required
  20. # SL_Expire - token lifetime in minutes (0-1440), default 1400 minutes
  21. #
  22. # Issues: github.com/acmesh-official/acme.sh/issues/5126
  23. #
  24. SL_Api="https://api.selectel.ru/domains"
  25. auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens"
  26. _sl_sep='#'
  27. ######## Public functions #####################
  28. #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  29. dns_selectel_add() {
  30. fulldomain=$1
  31. txtvalue=$2
  32. if ! _sl_init_vars; then
  33. return 1
  34. fi
  35. _debug2 SL_Ver "$SL_Ver"
  36. _debug2 SL_Expire "$SL_Expire"
  37. _debug2 SL_Login_Name "$SL_Login_Name"
  38. _debug2 SL_Login_ID "$SL_Login_ID"
  39. _debug2 SL_Project_Name "$SL_Project_Name"
  40. _debug "First detect the root zone"
  41. if ! _get_root "$fulldomain"; then
  42. _err "invalid domain"
  43. return 1
  44. fi
  45. _debug _domain_id "$_domain_id"
  46. _debug _sub_domain "$_sub_domain"
  47. _debug _domain "$_domain"
  48. _info "Adding record"
  49. if [ "$SL_Ver" = "v2" ]; then
  50. _ext_srv1="/zones/"
  51. _ext_srv2="/rrset/"
  52. _text_tmp=$(echo "$txtvalue" | sed -En "s/[\"]*([^\"]*)/\1/p")
  53. _text_tmp='\"'$_text_tmp'\"'
  54. _data="{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"${fulldomain}.\", \"records\": [{\"content\":\"$_text_tmp\"}]}"
  55. elif [ "$SL_Ver" = "v1" ]; then
  56. _ext_srv1="/"
  57. _ext_srv2="/records/"
  58. _data="{\"type\":\"TXT\",\"ttl\":60,\"name\":\"$fulldomain\",\"content\":\"$txtvalue\"}"
  59. else
  60. _err "Error. Unsupported version API $SL_Ver"
  61. return 1
  62. fi
  63. _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}"
  64. _debug _ext_uri "$_ext_uri"
  65. _debug _data "$_data"
  66. if _sl_rest POST "$_ext_uri" "$_data"; then
  67. if _contains "$response" "$txtvalue"; then
  68. _info "Added, OK"
  69. return 0
  70. fi
  71. if _contains "$response" "already_exists"; then
  72. # record TXT with $fulldomain already exists
  73. if [ "$SL_Ver" = "v2" ]; then
  74. # It is necessary to add one more content to the comments
  75. # read all records rrset
  76. _debug "Getting txt records"
  77. _sl_rest GET "${_ext_uri}"
  78. # There is already a $txtvalue value, no need to add it
  79. if _contains "$response" "$txtvalue"; then
  80. _info "Added, OK"
  81. _info "Txt record ${fulldomain} with value ${txtvalue} already exists"
  82. return 0
  83. fi
  84. # group \1 - full record rrset; group \2 - records attribute value, exactly {"content":"\"value1\""},{"content":"\"value2\""}",...
  85. _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\1/p")"
  86. _record_array="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\2/p")"
  87. # record id
  88. _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")"
  89. # preparing _data
  90. _tmp_str="${_record_array},{\"content\":\"${_text_tmp}\"}"
  91. _data="{\"ttl\": 60, \"records\": [${_tmp_str}]}"
  92. _debug2 _record_seg "$_record_seg"
  93. _debug2 _record_array "$_record_array"
  94. _debug2 _record_array "$_record_id"
  95. _debug "New data for record" "$_data"
  96. if _sl_rest PATCH "${_ext_uri}${_record_id}" "$_data"; then
  97. _info "Added, OK"
  98. return 0
  99. fi
  100. elif [ "$SL_Ver" = "v1" ]; then
  101. _info "Added, OK"
  102. return 0
  103. fi
  104. fi
  105. fi
  106. _err "Add txt record error."
  107. return 1
  108. }
  109. #fulldomain txtvalue
  110. dns_selectel_rm() {
  111. fulldomain=$1
  112. txtvalue=$2
  113. if ! _sl_init_vars "nosave"; then
  114. return 1
  115. fi
  116. _debug2 SL_Ver "$SL_Ver"
  117. _debug2 SL_Expire "$SL_Expire"
  118. _debug2 SL_Login_Name "$SL_Login_Name"
  119. _debug2 SL_Login_ID "$SL_Login_ID"
  120. _debug2 SL_Project_Name "$SL_Project_Name"
  121. #
  122. _debug "First detect the root zone"
  123. if ! _get_root "$fulldomain"; then
  124. _err "invalid domain"
  125. return 1
  126. fi
  127. _debug _domain_id "$_domain_id"
  128. _debug _sub_domain "$_sub_domain"
  129. _debug _domain "$_domain"
  130. #
  131. if [ "$SL_Ver" = "v2" ]; then
  132. _ext_srv1="/zones/"
  133. _ext_srv2="/rrset/"
  134. elif [ "$SL_Ver" = "v1" ]; then
  135. _ext_srv1="/"
  136. _ext_srv2="/records/"
  137. else
  138. _err "Error. Unsupported version API $SL_Ver"
  139. return 1
  140. fi
  141. #
  142. _debug "Getting txt records"
  143. _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}"
  144. _debug _ext_uri "$_ext_uri"
  145. _sl_rest GET "${_ext_uri}"
  146. #
  147. if ! _contains "$response" "$txtvalue"; then
  148. _err "Txt record not found"
  149. return 1
  150. fi
  151. #
  152. if [ "$SL_Ver" = "v2" ]; then
  153. _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\1/gp")"
  154. _record_arr="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/p")"
  155. elif [ "$SL_Ver" = "v1" ]; then
  156. _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")"
  157. else
  158. _err "Error. Unsupported version API $SL_Ver"
  159. return 1
  160. fi
  161. _debug2 "_record_seg" "$_record_seg"
  162. if [ -z "$_record_seg" ]; then
  163. _err "can not find _record_seg"
  164. return 1
  165. fi
  166. # record id
  167. # the following lines change the algorithm for deleting records with the value $txtvalue
  168. # if you use the 1st line, then all such records are deleted at once
  169. # if you use the 2nd line, then only the first entry from them is deleted
  170. #_record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")"
  171. _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"" | sed '1!d')"
  172. if [ -z "$_record_id" ]; then
  173. _err "can not find _record_id"
  174. return 1
  175. fi
  176. _debug2 "_record_id" "$_record_id"
  177. # delete all record type TXT with text $txtvalue
  178. if [ "$SL_Ver" = "v2" ]; then
  179. # actual
  180. _new_arr="$(echo "$_record_seg" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/gp" | sed -En "s/(\},\{)/}\n{/gp" | sed "/${txtvalue}/d" | sed ":a;N;s/\n/,/;ta")"
  181. # uri record for DEL or PATCH
  182. _del_uri="${_ext_uri}${_record_id}"
  183. _debug _del_uri "$_del_uri"
  184. if [ -z "$_new_arr" ]; then
  185. # remove record
  186. if ! _sl_rest DELETE "${_del_uri}"; then
  187. _err "Delete record error: ${_del_uri}."
  188. else
  189. info "Delete record success: ${_del_uri}."
  190. fi
  191. else
  192. # update a record by removing one element in content
  193. _data="{\"ttl\": 60, \"records\": [${_new_arr}]}"
  194. _debug2 _data "$_data"
  195. # REST API PATCH call
  196. if _sl_rest PATCH "${_del_uri}" "$_data"; then
  197. _info "Patched, OK: ${_del_uri}"
  198. else
  199. _err "Patched record error: ${_del_uri}."
  200. fi
  201. fi
  202. else
  203. # legacy
  204. for _one_id in $_record_id; do
  205. _del_uri="${_ext_uri}${_one_id}"
  206. _debug _del_uri "$_del_uri"
  207. if ! _sl_rest DELETE "${_del_uri}"; then
  208. _err "Delete record error: ${_del_uri}."
  209. else
  210. info "Delete record success: ${_del_uri}."
  211. fi
  212. done
  213. fi
  214. return 0
  215. }
  216. #################### Private functions below ##################################
  217. _get_root() {
  218. domain=$1
  219. if [ "$SL_Ver" = 'v1' ]; then
  220. # version API 1
  221. if ! _sl_rest GET "/"; then
  222. return 1
  223. fi
  224. i=2
  225. p=1
  226. while true; do
  227. h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
  228. _debug h "$h"
  229. if [ -z "$h" ]; then
  230. return 1
  231. fi
  232. if _contains "$response" "\"name\" *: *\"$h\","; then
  233. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  234. _domain=$h
  235. _debug "Getting domain id for $h"
  236. if ! _sl_rest GET "/$h"; then
  237. _err "Error read records of all domains $SL_Ver"
  238. return 1
  239. fi
  240. _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)"
  241. return 0
  242. fi
  243. p=$i
  244. i=$(_math "$i" + 1)
  245. done
  246. _err "Error read records of all domains $SL_Ver"
  247. return 1
  248. elif [ "$SL_Ver" = "v2" ]; then
  249. # version API 2
  250. _ext_uri='/zones/'
  251. domain="${domain}."
  252. _debug "domain:: " "$domain"
  253. # read records of all domains
  254. if ! _sl_rest GET "$_ext_uri"; then
  255. _err "Error read records of all domains $SL_Ver"
  256. return 1
  257. fi
  258. i=1
  259. p=1
  260. while true; do
  261. h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
  262. _debug h "$h"
  263. if [ -z "$h" ]; then
  264. _err "The domain was not found among the registered ones"
  265. return 1
  266. fi
  267. _domain_record=$(echo "$response" | sed -En "s/.*(\{[^}]*id[^}]*\"name\" *: *\"$h\"[^}]*}).*/\1/p")
  268. _debug "_domain_record:: " "$_domain_record"
  269. if [ -n "$_domain_record" ]; then
  270. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  271. _domain=$h
  272. _debug "Getting domain id for $h"
  273. _domain_id=$(echo "$_domain_record" | sed -En "s/\{[^}]*\"id\" *: *\"([^\"]*)\"[^}]*\}/\1/p")
  274. return 0
  275. fi
  276. p=$i
  277. i=$(_math "$i" + 1)
  278. done
  279. _err "Error read records of all domains $SL_Ver"
  280. return 1
  281. else
  282. _err "Error. Unsupported version API $SL_Ver"
  283. return 1
  284. fi
  285. }
  286. #################################################################
  287. # use: method add_url body
  288. _sl_rest() {
  289. m=$1
  290. ep="$2"
  291. data="$3"
  292. _token=$(_get_auth_token)
  293. if [ -z "$_token" ]; then
  294. _err "BAD key or token $ep"
  295. return 1
  296. fi
  297. if [ "$SL_Ver" = v2 ]; then
  298. _h1_name="X-Auth-Token"
  299. else
  300. _h1_name='X-Token'
  301. fi
  302. export _H1="${_h1_name}: ${_token}"
  303. export _H2="Content-Type: application/json"
  304. _debug2 "Full URI: " "$SL_Api/${SL_Ver}${ep}"
  305. _debug2 "_H1:" "$_H1"
  306. _debug2 "_H2:" "$_H2"
  307. if [ "$m" != "GET" ]; then
  308. _debug data "$data"
  309. response="$(_post "$data" "$SL_Api/${SL_Ver}${ep}" "" "$m")"
  310. else
  311. response="$(_get "$SL_Api/${SL_Ver}${ep}")"
  312. fi
  313. # shellcheck disable=SC2181
  314. if [ "$?" != "0" ]; then
  315. _err "error $ep"
  316. return 1
  317. fi
  318. _debug2 response "$response"
  319. return 0
  320. }
  321. _get_auth_token() {
  322. if [ "$SL_Ver" = 'v1' ]; then
  323. # token for v1
  324. _debug "Token v1"
  325. _token_keystone=$SL_Key
  326. elif [ "$SL_Ver" = 'v2' ]; then
  327. # token for v2. Get a token for calling the API
  328. _debug "Keystone Token v2"
  329. token_v2=$(_readaccountconf_mutable SL_Token_V2)
  330. if [ -n "$token_v2" ]; then
  331. # The structure with the token was considered. Let's check its validity
  332. # field 1 - SL_Login_Name
  333. # field 2 - token keystone
  334. # field 3 - SL_Login_ID
  335. # field 4 - SL_Project_Name
  336. # field 5 - Receipt time
  337. # separator - '$_sl_sep'
  338. _login_name=$(_getfield "$token_v2" 1 "$_sl_sep")
  339. _token_keystone=$(_getfield "$token_v2" 2 "$_sl_sep")
  340. _project_name=$(_getfield "$token_v2" 4 "$_sl_sep")
  341. _receipt_time=$(_getfield "$token_v2" 5 "$_sl_sep")
  342. _login_id=$(_getfield "$token_v2" 3 "$_sl_sep")
  343. _debug2 _login_name "$_login_name"
  344. _debug2 _login_id "$_login_id"
  345. _debug2 _project_name "$_project_name"
  346. # check the validity of the token for the user and the project and its lifetime
  347. _dt_diff_minute=$((($(date +%s) - _receipt_time) / 60))
  348. _debug2 _dt_diff_minute "$_dt_diff_minute"
  349. [ "$_dt_diff_minute" -gt "$SL_Expire" ] && unset _token_keystone
  350. if [ "$_project_name" != "$SL_Project_Name" ] || [ "$_login_name" != "$SL_Login_Name" ] || [ "$_login_id" != "$SL_Login_ID" ]; then
  351. unset _token_keystone
  352. fi
  353. _debug "Get exists token"
  354. fi
  355. if [ -z "$_token_keystone" ]; then
  356. # the previous token is incorrect or was not received, get a new one
  357. _debug "Update (get new) token"
  358. _data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}"
  359. export _H1="Content-Type: application/json"
  360. _result=$(_post "$_data_auth" "$auth_uri")
  361. _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | sed -nE "s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\r*)/\1/p")
  362. _dt_curr=$(date +%s)
  363. SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}"
  364. _saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2"
  365. fi
  366. else
  367. # token set empty for unsupported version API
  368. _token_keystone=""
  369. fi
  370. printf -- "%s" "$_token_keystone"
  371. }
  372. #################################################################
  373. # use: [non_save]
  374. _sl_init_vars() {
  375. _non_save="${1}"
  376. _debug2 _non_save "$_non_save"
  377. _debug "First init variables"
  378. # version API
  379. SL_Ver="${SL_Ver:-$(_readaccountconf_mutable SL_Ver)}"
  380. if [ -z "$SL_Ver" ]; then
  381. SL_Ver="v1"
  382. fi
  383. if ! [ "$SL_Ver" = "v1" ] && ! [ "$SL_Ver" = "v2" ]; then
  384. _err "You don't specify selectel.ru API version."
  385. _err "Please define specify API version."
  386. fi
  387. _debug2 SL_Ver "$SL_Ver"
  388. if [ "$SL_Ver" = "v1" ]; then
  389. # token
  390. SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}"
  391. if [ -z "$SL_Key" ]; then
  392. SL_Key=""
  393. _err "You don't specify selectel.ru api key yet."
  394. _err "Please create you key and try again."
  395. return 1
  396. fi
  397. #save the api key to the account conf file.
  398. if [ -z "$_non_save" ]; then
  399. _saveaccountconf_mutable SL_Key "$SL_Key"
  400. fi
  401. elif [ "$SL_Ver" = "v2" ]; then
  402. # time expire token
  403. SL_Expire="${SL_Expire:-$(_readaccountconf_mutable SL_Expire)}"
  404. if [ -z "$SL_Expire" ]; then
  405. SL_Expire=1400 # 23h 20 min
  406. fi
  407. if [ -z "$_non_save" ]; then
  408. _saveaccountconf_mutable SL_Expire "$SL_Expire"
  409. fi
  410. # login service user
  411. SL_Login_Name="${SL_Login_Name:-$(_readaccountconf_mutable SL_Login_Name)}"
  412. if [ -z "$SL_Login_Name" ]; then
  413. SL_Login_Name=''
  414. _err "You did not specify the selectel.ru API service user name."
  415. _err "Please provide a service user name and try again."
  416. return 1
  417. fi
  418. if [ -z "$_non_save" ]; then
  419. _saveaccountconf_mutable SL_Login_Name "$SL_Login_Name"
  420. fi
  421. # user ID
  422. SL_Login_ID="${SL_Login_ID:-$(_readaccountconf_mutable SL_Login_ID)}"
  423. if [ -z "$SL_Login_ID" ]; then
  424. SL_Login_ID=''
  425. _err "You did not specify the selectel.ru API user ID."
  426. _err "Please provide a user ID and try again."
  427. return 1
  428. fi
  429. if [ -z "$_non_save" ]; then
  430. _saveaccountconf_mutable SL_Login_ID "$SL_Login_ID"
  431. fi
  432. # project name
  433. SL_Project_Name="${SL_Project_Name:-$(_readaccountconf_mutable SL_Project_Name)}"
  434. if [ -z "$SL_Project_Name" ]; then
  435. SL_Project_Name=''
  436. _err "You did not specify the project name."
  437. _err "Please provide a project name and try again."
  438. return 1
  439. fi
  440. if [ -z "$_non_save" ]; then
  441. _saveaccountconf_mutable SL_Project_Name "$SL_Project_Name"
  442. fi
  443. # service user password
  444. SL_Pswd="${SL_Pswd:-$(_readaccountconf_mutable SL_Pswd)}"
  445. if [ -z "$SL_Pswd" ]; then
  446. SL_Pswd=''
  447. _err "You did not specify the service user password."
  448. _err "Please provide a service user password and try again."
  449. return 1
  450. fi
  451. if [ -z "$_non_save" ]; then
  452. _saveaccountconf_mutable SL_Pswd "$SL_Pswd" "12345678"
  453. fi
  454. else
  455. SL_Ver=""
  456. _err "You also specified the wrong version of the selectel.ru API."
  457. _err "Please provide the correct API version and try again."
  458. return 1
  459. fi
  460. if [ -z "$_non_save" ]; then
  461. _saveaccountconf_mutable SL_Ver "$SL_Ver"
  462. fi
  463. return 0
  464. }