]> git.proxmox.com Git - mirror_acme.sh.git/blame - dnsapi/dns_cyon.sh
Add DNS API for cyon.ch
[mirror_acme.sh.git] / dnsapi / dns_cyon.sh
CommitLineData
0ec9b982
AL
1#!/usr/bin/env sh
2
3########
4# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/Neilpang/acme.sh)
5#
6# Usage: acme.sh --issue --dns dns_cyon -d www.domain.com
7#
8# Dependencies:
9# -------------
10# - jq (get it here: https://stedolan.github.io/jq/download)
11# - oathtool (When using 2 Factor Authentication)
12#
13# Author: Armando Lüscher <armando@noplanman.ch>
14########
15
16########
17# Define cyon.ch login credentials:
18#
19# Either set them here: (uncomment these lines)
20#
21# cyon_username='your_cyon_username'
22# cyon_password='your_cyon_password'
23# cyon_otp_secret='your_otp_secret' # Only required if using 2FA
24#
25# ...or export them as environment variables in your shell:
26#
27# $ export cyon_username='your_cyon_username'
28# $ export cyon_password='your_cyon_password'
29# $ export cyon_otp_secret='your_otp_secret' # Only required if using 2FA
30#
31# *Note:*
32# After the first run, the credentials are saved in the "account.conf"
33# file, so any hard-coded or environment variables can then be removed.
34########
35
36dns_cyon_add() {
37 if ! _exists jq; then
38 _fail "Please install jq to use cyon.ch DNS API."
39 fi
40
41 _load_credentials
42 _load_parameters "$@"
43
44 _info_header "add"
45 _login
46 _domain_env
47 _add_txt
48 _cleanup
49
50 return 0
51}
52
53dns_cyon_rm() {
54 _load_credentials
55 _load_parameters "$@"
56
57 _info_header "delete"
58 _login
59 _domain_env
60 _delete_txt
61 _cleanup
62
63 return 0
64}
65
66#########################
67### PRIVATE FUNCTIONS ###
68#########################
69
70_load_credentials() {
71 # Convert loaded password to/from base64 as needed.
72 if [ "${cyon_password_b64}" ] ; then
73 cyon_password="$(echo "${cyon_password_b64}" | _dbase64)"
74 elif [ "${cyon_password}" ] ; then
75 cyon_password_b64="$(echo "${cyon_password}" | _base64)"
76 fi
77
78 if [ -z "${cyon_username}" ] || [ -z "${cyon_password}" ] ; then
79 _err ""
80 _err "You haven't set your cyon.ch login credentials yet."
81 _err "Please set the required cyon environment variables."
82 _err ""
83 exit 1
84 fi
85
86 # Save the login credentials to the account.conf file.
87 _debug "Save credentials to account.conf"
88 _saveaccountconf cyon_username "${cyon_username}"
89 _saveaccountconf cyon_password_b64 "$cyon_password_b64"
90 if [ ! -z "${cyon_otp_secret}" ] ; then
91 _saveaccountconf cyon_otp_secret "$cyon_otp_secret"
92 fi
93}
94
95_is_idn() {
96 _idn_temp=$(printf "%s" "$1" | tr -d "[0-9a-zA-Z.,-]")
97 _idn_temp2="$(printf "%s" "$1" | grep -o "xn--")"
98 [ "$_idn_temp" ] || [ "$_idn_temp2" ]
99}
100
101_load_parameters() {
102 # Read the required parameters to add the TXT entry.
103 fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
104 fulldomain_idn="${fulldomain}"
105
106 # Special case for IDNs, as cyon needs a domain environment change,
107 # which uses the "pretty" instead of the punycode version.
108 if _is_idn "$1" ; then
109 if ! _exists idn; then
110 _fail "Please install idn to process IDN names."
111 fi
112
113 fulldomain="$(idn -u "${fulldomain}")"
114 fulldomain_idn="$(idn -a "${fulldomain}")"
115 fi
116
117 _debug fulldomain "$fulldomain"
118 _debug fulldomain_idn "$fulldomain_idn"
119
120 txtvalue="$2"
121 _debug txtvalue "$txtvalue"
122
123 # Cookiejar required for login session, as cyon.ch has no official API (yet).
124 cookiejar=$(tempfile)
125 _debug cookiejar "$cookiejar"
126}
127
128_info_header() {
129 if [ "$1" = "add" ]; then
130 _info ""
131 _info "+---------------------------------------------+"
132 _info "| Adding DNS TXT entry to your cyon.ch domain |"
133 _info "+---------------------------------------------+"
134 _info ""
135 _info " * Full Domain: ${fulldomain}"
136 _info " * TXT Value: ${txtvalue}"
137 _info " * Cookie Jar: ${cookiejar}"
138 _info ""
139 elif [ "$1" = "delete" ]; then
140 _info ""
141 _info "+-------------------------------------------------+"
142 _info "| Deleting DNS TXT entry from your cyon.ch domain |"
143 _info "+-------------------------------------------------+"
144 _info ""
145 _info " * Full Domain: ${fulldomain}"
146 _info " * Cookie Jar: ${cookiejar}"
147 _info ""
148 fi
149}
150
151_login() {
152 _info " - Logging in..."
153 login_response=$(curl \
154 "https://my.cyon.ch/auth/index/dologin-async" \
155 -s \
156 -c "${cookiejar}" \
157 -H "X-Requested-With: XMLHttpRequest" \
158 --data-urlencode "username=${cyon_username}" \
159 --data-urlencode "password=${cyon_password}" \
160 --data-urlencode "pathname=/")
161
162 _debug login_response "${login_response}"
163
164 # Bail if login fails.
165 if [ "$(echo "${login_response}" | jq -r '.onSuccess')" != "success" ]; then
166 _fail " $(echo "${login_response}" | jq -r '.message')"
167 fi
168
169 _info " success"
170
171
172 # NECESSARY!! Load the main page after login, before the OTP check.
173 curl "https://my.cyon.ch/" -s --compressed -b "${cookiejar}" >/dev/null
174
175
176 # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request.
177
178
179 # 2FA authentication with OTP?
180 if [ ! -z "${cyon_otp_secret}" ] ; then
181 _info " - Authorising with OTP code..."
182
183 if ! _exists oathtool; then
184 _fail "Please install oathtool to use 2 Factor Authentication."
185 fi
186
187 # Get OTP code with the defined secret.
188 otp_code=$(oathtool --base32 --totp "${cyon_otp_secret}" 2>/dev/null)
189
190 otp_response=$(curl \
191 "https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" \
192 -s \
193 --compressed \
194 -b "${cookiejar}" \
195 -c "${cookiejar}" \
196 -H "X-Requested-With: XMLHttpRequest" \
197 -d "totpcode=${otp_code}&pathname=%2F&rememberme=0")
198
199 _debug otp_response "${otp_response}"
200
201 # Bail if OTP authentication fails.
202 if [ "$(echo "${otp_response}" | jq -r '.onSuccess')" != "success" ]; then
203 _fail " $(echo "${otp_response}" | jq -r '.message')"
204 fi
205
206 _info " success"
207 fi
208
209 _info ""
210}
211
212_domain_env() {
213 _info " - Changing domain environment..."
214
215 # Get the "example.com" part of the full domain name.
216 domain_env=$(echo "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')
217 _debug "Changing domain environment to ${domain_env}"
218
219 domain_env_response=$(curl \
220 "https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/domain%3A${domain_env}" \
221 -s \
222 --compressed \
223 -b "${cookiejar}" \
224 -H "X-Requested-With: XMLHttpRequest")
225
226 _debug domain_env_response "${domain_env_response}"
227
228 _check_2fa_miss "${domain_env_response}"
229
230 domain_env_success=$(echo "${domain_env_response}" | jq -r '.authenticated')
231
232 # Bail if domain environment change fails.
233 if [ "${domain_env_success}" != "true" ]; then
234 _fail " $(echo "${domain_env_response}" | jq -r '.message')"
235 fi
236
237 _info " success"
238 _info ""
239}
240
241_add_txt() {
242 _info " - Adding DNS TXT entry..."
243 addtxt_response=$(curl \
244 "https://my.cyon.ch/domain/dnseditor/add-record-async" \
245 -s \
246 --compressed \
247 -b "${cookiejar}" \
248 -H "X-Requested-With: XMLHttpRequest" \
249 -d "zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}")
250
251 _debug addtxt_response "${addtxt_response}"
252
253 _check_2fa_miss "${addtxt_response}"
254
255 addtxt_message=$(echo "${addtxt_response}" | jq -r '.message')
256 addtxt_status=$(echo "${addtxt_response}" | jq -r '.status')
257
258 # Bail if adding TXT entry fails.
259 if [ "${addtxt_status}" != "true" ]; then
260 if [ "${addtxt_status}" = "null" ]; then
261 addtxt_message=$(echo "${addtxt_response}" | jq -r '.error.message')
262 fi
263 _fail " ${addtxt_message}"
264 fi
265
266 _info " success"
267 _info ""
268}
269
270_delete_txt() {
271 _info " - Deleting DNS TXT entry..."
272
273 list_txt_response=$(curl \
274 "https://my.cyon.ch/domain/dnseditor/list-async" \
275 -s \
276 -b "${cookiejar}" \
277 --compressed \
278 -H "X-Requested-With: XMLHttpRequest")
279
280 _debug list_txt_response "${list_txt_response}"
281
282 _check_2fa_miss "${list_txt_response}"
283
284 # Find and delete all acme challenge entries for the $fulldomain.
285 _dns_entries=$(echo "$list_txt_response" | jq -r --arg fulldomain_idn "${fulldomain_idn}." '
286 .rows[] |
287 label $out|
288 if .[0] != $fulldomain_idn then
289 break $out
290 else
291 .[4]|
292 capture("data-hash=\"(?<hash>[^\"]*)\" data-identifier=\"(?<identifier>[^\"]*)\"";"g")|
293 .hash + " " + .identifier
294 end')
295 _dns_entries_cnt=$(echo "${_dns_entries}" | wc -l | grep -o '\d')
296
297 _info " (entries found: ${_dns_entries_cnt})"
298
299 _dns_entry_num=0
300
301 echo "${_dns_entries}" | while read -r _hash _identifier
302 do
303 ((_dns_entry_num++))
304
305 delete_txt_response=$(curl \
306 "https://my.cyon.ch/domain/dnseditor/delete-record-async" \
307 -s \
308 --compressed \
309 -b "${cookiejar}" \
310 -H "X-Requested-With: XMLHttpRequest" \
311 --data-urlencode "hash=${_hash}" \
312 --data-urlencode "identifier=${_identifier}")
313
314 _debug delete_txt_response "${delete_txt_response}"
315
316 _check_2fa_miss "${delete_txt_response}"
317
318 delete_txt_message=$(echo "${delete_txt_response}" | jq -r '.message')
319 delete_txt_status=$(echo "${delete_txt_response}" | jq -r '.status')
320
321 # Skip if deleting TXT entry fails.
322 if [ "${delete_txt_status}" != "true" ]; then
323 if [ "${delete_txt_status}" = "null" ]; then
324 delete_txt_message=$(echo "${delete_txt_response}" | jq -r '.error.message')
325 fi
326 _err " [${_dns_entry_num}/${_dns_entries_cnt}] ${delete_txt_message} (${_identifier})"
327 else
328 _info " [${_dns_entry_num}/${_dns_entries_cnt}] success (${_identifier})"
329 fi
330 done
331
332 _info " done"
333 _info ""
334}
335
336_check_2fa_miss() {
337 # Did we miss the 2FA?
338 if [[ "$1" =~ "multi_factor_form" ]] ; then
339 _fail " Missed OTP authentication!"
340 fi
341}
342
343_fail() {
344 _err "$1"
345 _err ""
346 _cleanup
347 exit 1
348}
349
350_cleanup() {
351 _info " - Cleanup."
352 _debug "Remove cookie jar: ${cookiejar}"
353 rm "${cookiejar}" 2>/dev/null
354 _info ""
355}