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