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