]>
Commit | Line | Data |
---|---|---|
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 | # | |
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 | ||
20 | dns_cyon_add() { | |
98b3dcbf AL |
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 | ||
30 | dns_cyon_rm() { | |
98b3dcbf AL |
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" | |
69 | if [ ! -z "${CY_OTP_Secret}" ]; then | |
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? |
afa3fc8b | 167 | if [ ! -z "${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 | } |