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.

322 lines
9.6 KiB

4 years ago
4 years ago
4 years ago
4 years ago
  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. dns_cyon_info='cyon.ch
  4. Site: cyon.ch
  5. Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cyon
  6. Options:
  7. CY_Username Username
  8. CY_Password API Token
  9. CY_OTP_Secret OTP token. Only required if using 2FA
  10. Issues: github.com/noplanman/cyon-api/issues
  11. Author: Armando Lüscher <armando@noplanman.ch>
  12. '
  13. dns_cyon_add() {
  14. _cyon_load_credentials &&
  15. _cyon_load_parameters "$@" &&
  16. _cyon_print_header "add" &&
  17. _cyon_login &&
  18. _cyon_change_domain_env &&
  19. _cyon_add_txt &&
  20. _cyon_logout
  21. }
  22. dns_cyon_rm() {
  23. _cyon_load_credentials &&
  24. _cyon_load_parameters "$@" &&
  25. _cyon_print_header "delete" &&
  26. _cyon_login &&
  27. _cyon_change_domain_env &&
  28. _cyon_delete_txt &&
  29. _cyon_logout
  30. }
  31. #########################
  32. ### PRIVATE FUNCTIONS ###
  33. #########################
  34. _cyon_load_credentials() {
  35. # Convert loaded password to/from base64 as needed.
  36. if [ "${CY_Password_B64}" ]; then
  37. CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64)"
  38. elif [ "${CY_Password}" ]; then
  39. CY_Password_B64="$(printf "%s" "${CY_Password}" | _base64)"
  40. fi
  41. if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then
  42. # Dummy entries to satisfy script checker.
  43. CY_Username=""
  44. CY_Password=""
  45. CY_OTP_Secret=""
  46. _err ""
  47. _err "You haven't set your cyon.ch login credentials yet."
  48. _err "Please set the required cyon environment variables."
  49. _err ""
  50. return 1
  51. fi
  52. # Save the login credentials to the account.conf file.
  53. _debug "Save credentials to account.conf"
  54. _saveaccountconf CY_Username "${CY_Username}"
  55. _saveaccountconf CY_Password_B64 "$CY_Password_B64"
  56. if [ -n "${CY_OTP_Secret}" ]; then
  57. _saveaccountconf CY_OTP_Secret "$CY_OTP_Secret"
  58. else
  59. _clearaccountconf CY_OTP_Secret
  60. fi
  61. }
  62. _cyon_is_idn() {
  63. _idn_temp="$(printf "%s" "${1}" | tr -d "0-9a-zA-Z.,-_")"
  64. _idn_temp2="$(printf "%s" "${1}" | grep -o "xn--")"
  65. [ "$_idn_temp" ] || [ "$_idn_temp2" ]
  66. }
  67. _cyon_load_parameters() {
  68. # Read the required parameters to add the TXT entry.
  69. # shellcheck disable=SC2018,SC2019
  70. fulldomain="$(printf "%s" "${1}" | tr "A-Z" "a-z")"
  71. fulldomain_idn="${fulldomain}"
  72. # Special case for IDNs, as cyon needs a domain environment change,
  73. # which uses the "pretty" instead of the punycode version.
  74. if _cyon_is_idn "${fulldomain}"; then
  75. if ! _exists idn; then
  76. _err "Please install idn to process IDN names."
  77. _err ""
  78. return 1
  79. fi
  80. fulldomain="$(idn -u "${fulldomain}")"
  81. fulldomain_idn="$(idn -a "${fulldomain}")"
  82. fi
  83. _debug fulldomain "${fulldomain}"
  84. _debug fulldomain_idn "${fulldomain_idn}"
  85. txtvalue="${2}"
  86. _debug txtvalue "${txtvalue}"
  87. # This header is required for curl calls.
  88. _H1="X-Requested-With: XMLHttpRequest"
  89. export _H1
  90. }
  91. _cyon_print_header() {
  92. if [ "${1}" = "add" ]; then
  93. _info ""
  94. _info "+---------------------------------------------+"
  95. _info "| Adding DNS TXT entry to your cyon.ch domain |"
  96. _info "+---------------------------------------------+"
  97. _info ""
  98. _info " * Full Domain: ${fulldomain}"
  99. _info " * TXT Value: ${txtvalue}"
  100. _info ""
  101. elif [ "${1}" = "delete" ]; then
  102. _info ""
  103. _info "+-------------------------------------------------+"
  104. _info "| Deleting DNS TXT entry from your cyon.ch domain |"
  105. _info "+-------------------------------------------------+"
  106. _info ""
  107. _info " * Full Domain: ${fulldomain}"
  108. _info ""
  109. fi
  110. }
  111. _cyon_get_cookie_header() {
  112. printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')"
  113. }
  114. _cyon_login() {
  115. _info " - Logging in..."
  116. username_encoded="$(printf "%s" "${CY_Username}" | _url_encode)"
  117. password_encoded="$(printf "%s" "${CY_Password}" | _url_encode)"
  118. login_url="https://my.cyon.ch/auth/index/dologin-async"
  119. login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")"
  120. login_response="$(_post "$login_data" "$login_url")"
  121. _debug login_response "${login_response}"
  122. # Bail if login fails.
  123. if [ "$(printf "%s" "${login_response}" | _cyon_get_response_success)" != "success" ]; then
  124. _err " $(printf "%s" "${login_response}" | _cyon_get_response_message)"
  125. _err ""
  126. return 1
  127. fi
  128. _info " success"
  129. # NECESSARY!! Load the main page after login, to get the new cookie.
  130. _H2="$(_cyon_get_cookie_header)"
  131. export _H2
  132. _get "https://my.cyon.ch/" >/dev/null
  133. # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request.
  134. # 2FA authentication with OTP?
  135. if [ -n "${CY_OTP_Secret}" ]; then
  136. _info " - Authorising with OTP code..."
  137. if ! _exists oathtool; then
  138. _err "Please install oathtool to use 2 Factor Authentication."
  139. _err ""
  140. return 1
  141. fi
  142. # Get OTP code with the defined secret.
  143. otp_code="$(oathtool --base32 --totp "${CY_OTP_Secret}" 2>/dev/null)"
  144. login_otp_url="https://my.cyon.ch/auth/multi-factor/domultifactorauth-async"
  145. login_otp_data="totpcode=${otp_code}&pathname=%2F&rememberme=0"
  146. login_otp_response="$(_post "$login_otp_data" "$login_otp_url")"
  147. _debug login_otp_response "${login_otp_response}"
  148. # Bail if OTP authentication fails.
  149. if [ "$(printf "%s" "${login_otp_response}" | _cyon_get_response_success)" != "success" ]; then
  150. _err " $(printf "%s" "${login_otp_response}" | _cyon_get_response_message)"
  151. _err ""
  152. return 1
  153. fi
  154. _info " success"
  155. fi
  156. _info ""
  157. }
  158. _cyon_logout() {
  159. _info " - Logging out..."
  160. _get "https://my.cyon.ch/auth/index/dologout" >/dev/null
  161. _info " success"
  162. _info ""
  163. }
  164. _cyon_change_domain_env() {
  165. _info " - Changing domain environment..."
  166. # Get the "example.com" part of the full domain name.
  167. domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')"
  168. _debug "Changing domain environment to ${domain_env}"
  169. gloo_item_key="$(_get "https://my.cyon.ch/domain/" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")"
  170. _debug gloo_item_key "${gloo_item_key}"
  171. domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}"
  172. domain_env_response="$(_get "${domain_env_url}")"
  173. _debug domain_env_response "${domain_env_response}"
  174. if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi
  175. domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)"
  176. # Bail if domain environment change fails.
  177. if [ "${domain_env_success}" != "true" ]; then
  178. _err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)"
  179. _err ""
  180. return 1
  181. fi
  182. _info " success"
  183. _info ""
  184. }
  185. _cyon_add_txt() {
  186. _info " - Adding DNS TXT entry..."
  187. add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async"
  188. add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}"
  189. add_txt_response="$(_post "$add_txt_data" "$add_txt_url")"
  190. _debug add_txt_response "${add_txt_response}"
  191. if ! _cyon_check_if_2fa_missed "${add_txt_response}"; then return 1; fi
  192. add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)"
  193. add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)"
  194. # Bail if adding TXT entry fails.
  195. if [ "${add_txt_status}" != "true" ]; then
  196. _err " ${add_txt_message}"
  197. _err ""
  198. return 1
  199. fi
  200. _info " success (TXT|${fulldomain_idn}.|${txtvalue})"
  201. _info ""
  202. }
  203. _cyon_delete_txt() {
  204. _info " - Deleting DNS TXT entry..."
  205. list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async"
  206. list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')"
  207. _debug list_txt_response "${list_txt_response}"
  208. if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi
  209. # Find and delete all acme challenge entries for the $fulldomain.
  210. _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')"
  211. printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do
  212. dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)"
  213. dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)"
  214. if [ "${dns_type}" != "TXT" ] || [ "${dns_domain}" != "${fulldomain_idn}." ]; then
  215. continue
  216. fi
  217. hash_encoded="$(printf "%s" "${_hash}" | _url_encode)"
  218. identifier_encoded="$(printf "%s" "${_identifier}" | _url_encode)"
  219. delete_txt_url="https://my.cyon.ch/domain/dnseditor/delete-record-async"
  220. delete_txt_data="$(printf "%s" "hash=${hash_encoded}&identifier=${identifier_encoded}")"
  221. delete_txt_response="$(_post "$delete_txt_data" "$delete_txt_url")"
  222. _debug delete_txt_response "${delete_txt_response}"
  223. if ! _cyon_check_if_2fa_missed "${delete_txt_response}"; then return 1; fi
  224. delete_txt_message="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_message)"
  225. delete_txt_status="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_status)"
  226. # Skip if deleting TXT entry fails.
  227. if [ "${delete_txt_status}" != "true" ]; then
  228. _err " ${delete_txt_message} (${_identifier})"
  229. else
  230. _info " success (${_identifier})"
  231. fi
  232. done
  233. _info " done"
  234. _info ""
  235. }
  236. _cyon_get_response_message() {
  237. _egrep_o '"message":"[^"]*"' | cut -d : -f 2 | tr -d '"'
  238. }
  239. _cyon_get_response_status() {
  240. _egrep_o '"status":\w*' | cut -d : -f 2
  241. }
  242. _cyon_get_response_success() {
  243. _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"'
  244. }
  245. _cyon_check_if_2fa_missed() {
  246. # Did we miss the 2FA?
  247. if test "${1#*multi_factor_form}" != "${1}"; then
  248. _err " Missed OTP authentication!"
  249. _err ""
  250. return 1
  251. fi
  252. }