From 0ec9b9823f3ff32108bab78f5e2b5f1c0750adf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Tue, 22 Nov 2016 00:24:26 +0100 Subject: [PATCH 01/16] Add DNS API for cyon.ch --- dnsapi/dns_cyon.sh | 355 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 dnsapi/dns_cyon.sh diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh new file mode 100644 index 00000000..95e690bb --- /dev/null +++ b/dnsapi/dns_cyon.sh @@ -0,0 +1,355 @@ +#!/usr/bin/env sh + +######## +# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/Neilpang/acme.sh) +# +# Usage: acme.sh --issue --dns dns_cyon -d www.domain.com +# +# Dependencies: +# ------------- +# - jq (get it here: https://stedolan.github.io/jq/download) +# - oathtool (When using 2 Factor Authentication) +# +# Author: Armando Lüscher +######## + +######## +# Define cyon.ch login credentials: +# +# Either set them here: (uncomment these lines) +# +# cyon_username='your_cyon_username' +# cyon_password='your_cyon_password' +# cyon_otp_secret='your_otp_secret' # Only required if using 2FA +# +# ...or export them as environment variables in your shell: +# +# $ export cyon_username='your_cyon_username' +# $ export cyon_password='your_cyon_password' +# $ export cyon_otp_secret='your_otp_secret' # Only required if using 2FA +# +# *Note:* +# After the first run, the credentials are saved in the "account.conf" +# file, so any hard-coded or environment variables can then be removed. +######## + +dns_cyon_add() { + if ! _exists jq; then + _fail "Please install jq to use cyon.ch DNS API." + fi + + _load_credentials + _load_parameters "$@" + + _info_header "add" + _login + _domain_env + _add_txt + _cleanup + + return 0 +} + +dns_cyon_rm() { + _load_credentials + _load_parameters "$@" + + _info_header "delete" + _login + _domain_env + _delete_txt + _cleanup + + return 0 +} + +######################### +### PRIVATE FUNCTIONS ### +######################### + +_load_credentials() { + # Convert loaded password to/from base64 as needed. + if [ "${cyon_password_b64}" ] ; then + cyon_password="$(echo "${cyon_password_b64}" | _dbase64)" + elif [ "${cyon_password}" ] ; then + cyon_password_b64="$(echo "${cyon_password}" | _base64)" + fi + + if [ -z "${cyon_username}" ] || [ -z "${cyon_password}" ] ; then + _err "" + _err "You haven't set your cyon.ch login credentials yet." + _err "Please set the required cyon environment variables." + _err "" + exit 1 + fi + + # Save the login credentials to the account.conf file. + _debug "Save credentials to account.conf" + _saveaccountconf cyon_username "${cyon_username}" + _saveaccountconf cyon_password_b64 "$cyon_password_b64" + if [ ! -z "${cyon_otp_secret}" ] ; then + _saveaccountconf cyon_otp_secret "$cyon_otp_secret" + fi +} + +_is_idn() { + _idn_temp=$(printf "%s" "$1" | tr -d "[0-9a-zA-Z.,-]") + _idn_temp2="$(printf "%s" "$1" | grep -o "xn--")" + [ "$_idn_temp" ] || [ "$_idn_temp2" ] +} + +_load_parameters() { + # Read the required parameters to add the TXT entry. + fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')" + fulldomain_idn="${fulldomain}" + + # Special case for IDNs, as cyon needs a domain environment change, + # which uses the "pretty" instead of the punycode version. + if _is_idn "$1" ; then + if ! _exists idn; then + _fail "Please install idn to process IDN names." + fi + + fulldomain="$(idn -u "${fulldomain}")" + fulldomain_idn="$(idn -a "${fulldomain}")" + fi + + _debug fulldomain "$fulldomain" + _debug fulldomain_idn "$fulldomain_idn" + + txtvalue="$2" + _debug txtvalue "$txtvalue" + + # Cookiejar required for login session, as cyon.ch has no official API (yet). + cookiejar=$(tempfile) + _debug cookiejar "$cookiejar" +} + +_info_header() { + if [ "$1" = "add" ]; then + _info "" + _info "+---------------------------------------------+" + _info "| Adding DNS TXT entry to your cyon.ch domain |" + _info "+---------------------------------------------+" + _info "" + _info " * Full Domain: ${fulldomain}" + _info " * TXT Value: ${txtvalue}" + _info " * Cookie Jar: ${cookiejar}" + _info "" + elif [ "$1" = "delete" ]; then + _info "" + _info "+-------------------------------------------------+" + _info "| Deleting DNS TXT entry from your cyon.ch domain |" + _info "+-------------------------------------------------+" + _info "" + _info " * Full Domain: ${fulldomain}" + _info " * Cookie Jar: ${cookiejar}" + _info "" + fi +} + +_login() { + _info " - Logging in..." + login_response=$(curl \ + "https://my.cyon.ch/auth/index/dologin-async" \ + -s \ + -c "${cookiejar}" \ + -H "X-Requested-With: XMLHttpRequest" \ + --data-urlencode "username=${cyon_username}" \ + --data-urlencode "password=${cyon_password}" \ + --data-urlencode "pathname=/") + + _debug login_response "${login_response}" + + # Bail if login fails. + if [ "$(echo "${login_response}" | jq -r '.onSuccess')" != "success" ]; then + _fail " $(echo "${login_response}" | jq -r '.message')" + fi + + _info " success" + + + # NECESSARY!! Load the main page after login, before the OTP check. + curl "https://my.cyon.ch/" -s --compressed -b "${cookiejar}" >/dev/null + + + # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. + + + # 2FA authentication with OTP? + if [ ! -z "${cyon_otp_secret}" ] ; then + _info " - Authorising with OTP code..." + + if ! _exists oathtool; then + _fail "Please install oathtool to use 2 Factor Authentication." + fi + + # Get OTP code with the defined secret. + otp_code=$(oathtool --base32 --totp "${cyon_otp_secret}" 2>/dev/null) + + otp_response=$(curl \ + "https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" \ + -s \ + --compressed \ + -b "${cookiejar}" \ + -c "${cookiejar}" \ + -H "X-Requested-With: XMLHttpRequest" \ + -d "totpcode=${otp_code}&pathname=%2F&rememberme=0") + + _debug otp_response "${otp_response}" + + # Bail if OTP authentication fails. + if [ "$(echo "${otp_response}" | jq -r '.onSuccess')" != "success" ]; then + _fail " $(echo "${otp_response}" | jq -r '.message')" + fi + + _info " success" + fi + + _info "" +} + +_domain_env() { + _info " - Changing domain environment..." + + # Get the "example.com" part of the full domain name. + domain_env=$(echo "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/') + _debug "Changing domain environment to ${domain_env}" + + domain_env_response=$(curl \ + "https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/domain%3A${domain_env}" \ + -s \ + --compressed \ + -b "${cookiejar}" \ + -H "X-Requested-With: XMLHttpRequest") + + _debug domain_env_response "${domain_env_response}" + + _check_2fa_miss "${domain_env_response}" + + domain_env_success=$(echo "${domain_env_response}" | jq -r '.authenticated') + + # Bail if domain environment change fails. + if [ "${domain_env_success}" != "true" ]; then + _fail " $(echo "${domain_env_response}" | jq -r '.message')" + fi + + _info " success" + _info "" +} + +_add_txt() { + _info " - Adding DNS TXT entry..." + addtxt_response=$(curl \ + "https://my.cyon.ch/domain/dnseditor/add-record-async" \ + -s \ + --compressed \ + -b "${cookiejar}" \ + -H "X-Requested-With: XMLHttpRequest" \ + -d "zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}") + + _debug addtxt_response "${addtxt_response}" + + _check_2fa_miss "${addtxt_response}" + + addtxt_message=$(echo "${addtxt_response}" | jq -r '.message') + addtxt_status=$(echo "${addtxt_response}" | jq -r '.status') + + # Bail if adding TXT entry fails. + if [ "${addtxt_status}" != "true" ]; then + if [ "${addtxt_status}" = "null" ]; then + addtxt_message=$(echo "${addtxt_response}" | jq -r '.error.message') + fi + _fail " ${addtxt_message}" + fi + + _info " success" + _info "" +} + +_delete_txt() { + _info " - Deleting DNS TXT entry..." + + list_txt_response=$(curl \ + "https://my.cyon.ch/domain/dnseditor/list-async" \ + -s \ + -b "${cookiejar}" \ + --compressed \ + -H "X-Requested-With: XMLHttpRequest") + + _debug list_txt_response "${list_txt_response}" + + _check_2fa_miss "${list_txt_response}" + + # Find and delete all acme challenge entries for the $fulldomain. + _dns_entries=$(echo "$list_txt_response" | jq -r --arg fulldomain_idn "${fulldomain_idn}." ' + .rows[] | + label $out| + if .[0] != $fulldomain_idn then + break $out + else + .[4]| + capture("data-hash=\"(?[^\"]*)\" data-identifier=\"(?[^\"]*)\"";"g")| + .hash + " " + .identifier + end') + _dns_entries_cnt=$(echo "${_dns_entries}" | wc -l | grep -o '\d') + + _info " (entries found: ${_dns_entries_cnt})" + + _dns_entry_num=0 + + echo "${_dns_entries}" | while read -r _hash _identifier + do + ((_dns_entry_num++)) + + delete_txt_response=$(curl \ + "https://my.cyon.ch/domain/dnseditor/delete-record-async" \ + -s \ + --compressed \ + -b "${cookiejar}" \ + -H "X-Requested-With: XMLHttpRequest" \ + --data-urlencode "hash=${_hash}" \ + --data-urlencode "identifier=${_identifier}") + + _debug delete_txt_response "${delete_txt_response}" + + _check_2fa_miss "${delete_txt_response}" + + delete_txt_message=$(echo "${delete_txt_response}" | jq -r '.message') + delete_txt_status=$(echo "${delete_txt_response}" | jq -r '.status') + + # Skip if deleting TXT entry fails. + if [ "${delete_txt_status}" != "true" ]; then + if [ "${delete_txt_status}" = "null" ]; then + delete_txt_message=$(echo "${delete_txt_response}" | jq -r '.error.message') + fi + _err " [${_dns_entry_num}/${_dns_entries_cnt}] ${delete_txt_message} (${_identifier})" + else + _info " [${_dns_entry_num}/${_dns_entries_cnt}] success (${_identifier})" + fi + done + + _info " done" + _info "" +} + +_check_2fa_miss() { + # Did we miss the 2FA? + if [[ "$1" =~ "multi_factor_form" ]] ; then + _fail " Missed OTP authentication!" + fi +} + +_fail() { + _err "$1" + _err "" + _cleanup + exit 1 +} + +_cleanup() { + _info " - Cleanup." + _debug "Remove cookie jar: ${cookiejar}" + rm "${cookiejar}" 2>/dev/null + _info "" +} From c90fa3bcfc71f298cdbb1f2207df981ffc126c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Tue, 22 Nov 2016 00:45:56 +0100 Subject: [PATCH 02/16] Fix problems found by travis. --- dnsapi/dns_cyon.sh | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 95e690bb..3ebd3099 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -69,13 +69,16 @@ dns_cyon_rm() { _load_credentials() { # Convert loaded password to/from base64 as needed. - if [ "${cyon_password_b64}" ] ; then + if [ "${cyon_password_b64}" ]; then cyon_password="$(echo "${cyon_password_b64}" | _dbase64)" - elif [ "${cyon_password}" ] ; then + elif [ "${cyon_password}" ]; then cyon_password_b64="$(echo "${cyon_password}" | _base64)" fi - if [ -z "${cyon_username}" ] || [ -z "${cyon_password}" ] ; then + if [ -z "${cyon_username}" ] || [ -z "${cyon_password}" ]; then + cyon_username="" + cyon_password="" + cyon_otp_secret="" _err "" _err "You haven't set your cyon.ch login credentials yet." _err "Please set the required cyon environment variables." @@ -87,7 +90,7 @@ _load_credentials() { _debug "Save credentials to account.conf" _saveaccountconf cyon_username "${cyon_username}" _saveaccountconf cyon_password_b64 "$cyon_password_b64" - if [ ! -z "${cyon_otp_secret}" ] ; then + if [ ! -z "${cyon_otp_secret}" ]; then _saveaccountconf cyon_otp_secret "$cyon_otp_secret" fi } @@ -105,7 +108,7 @@ _load_parameters() { # Special case for IDNs, as cyon needs a domain environment change, # which uses the "pretty" instead of the punycode version. - if _is_idn "$1" ; then + if _is_idn "$1"; then if ! _exists idn; then _fail "Please install idn to process IDN names." fi @@ -168,16 +171,13 @@ _login() { _info " success" - # NECESSARY!! Load the main page after login, before the OTP check. curl "https://my.cyon.ch/" -s --compressed -b "${cookiejar}" >/dev/null - # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. - # 2FA authentication with OTP? - if [ ! -z "${cyon_otp_secret}" ] ; then + if [ ! -z "${cyon_otp_secret}" ]; then _info " - Authorising with OTP code..." if ! _exists oathtool; then @@ -298,9 +298,8 @@ _delete_txt() { _dns_entry_num=0 - echo "${_dns_entries}" | while read -r _hash _identifier - do - ((_dns_entry_num++)) + echo "${_dns_entries}" | while read -r _hash _identifier; do + _dns_entry_num=$((_dns_entry_num + 1)) delete_txt_response=$(curl \ "https://my.cyon.ch/domain/dnseditor/delete-record-async" \ @@ -335,7 +334,7 @@ _delete_txt() { _check_2fa_miss() { # Did we miss the 2FA? - if [[ "$1" =~ "multi_factor_form" ]] ; then + if test "${1#*multi_factor_form}" != "$1"; then _fail " Missed OTP authentication!" fi } From 0085e6f83bbf78bffd8f34681be6e6d6865a19a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Tue, 22 Nov 2016 15:21:04 +0100 Subject: [PATCH 03/16] Don't use jq to fetch list of DNS entries to be deleted. --- dnsapi/dns_cyon.sh | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 3ebd3099..605585e6 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -275,31 +275,23 @@ _delete_txt() { -s \ -b "${cookiejar}" \ --compressed \ - -H "X-Requested-With: XMLHttpRequest") + -H "X-Requested-With: XMLHttpRequest" | \ + sed -e 's/data-hash/\\ndata-hash/g') _debug list_txt_response "${list_txt_response}" _check_2fa_miss "${list_txt_response}" # Find and delete all acme challenge entries for the $fulldomain. - _dns_entries=$(echo "$list_txt_response" | jq -r --arg fulldomain_idn "${fulldomain_idn}." ' - .rows[] | - label $out| - if .[0] != $fulldomain_idn then - break $out - else - .[4]| - capture("data-hash=\"(?[^\"]*)\" data-identifier=\"(?[^\"]*)\"";"g")| - .hash + " " + .identifier - end') - _dns_entries_cnt=$(echo "${_dns_entries}" | wc -l | grep -o '\d') - - _info " (entries found: ${_dns_entries_cnt})" - - _dns_entry_num=0 + _dns_entries=$(echo -e "$list_txt_response" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p') echo "${_dns_entries}" | while read -r _hash _identifier; do - _dns_entry_num=$((_dns_entry_num + 1)) + dns_type="$(echo "$_identifier" | cut -d'|' -f1)" + dns_domain="$(echo "$_identifier" | cut -d'|' -f2)" + + if [ "${dns_type}" != "TXT" ] || [ "${dns_domain}" != "${fulldomain_idn}." ]; then + continue + fi delete_txt_response=$(curl \ "https://my.cyon.ch/domain/dnseditor/delete-record-async" \ @@ -322,9 +314,9 @@ _delete_txt() { if [ "${delete_txt_status}" = "null" ]; then delete_txt_message=$(echo "${delete_txt_response}" | jq -r '.error.message') fi - _err " [${_dns_entry_num}/${_dns_entries_cnt}] ${delete_txt_message} (${_identifier})" + _err " ${delete_txt_message} (${_identifier})" else - _info " [${_dns_entry_num}/${_dns_entries_cnt}] success (${_identifier})" + _info " success (${_identifier})" fi done From e7ee3a7dd55dc505b0cfb959b15161fb35704272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Tue, 22 Nov 2016 18:06:16 +0100 Subject: [PATCH 04/16] Remove jq completely to not require it as a dependency. --- dnsapi/dns_cyon.sh | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 605585e6..af5a225b 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -7,7 +7,6 @@ # # Dependencies: # ------------- -# - jq (get it here: https://stedolan.github.io/jq/download) # - oathtool (When using 2 Factor Authentication) # # Author: Armando Lüscher @@ -34,10 +33,6 @@ ######## dns_cyon_add() { - if ! _exists jq; then - _fail "Please install jq to use cyon.ch DNS API." - fi - _load_credentials _load_parameters "$@" @@ -165,8 +160,8 @@ _login() { _debug login_response "${login_response}" # Bail if login fails. - if [ "$(echo "${login_response}" | jq -r '.onSuccess')" != "success" ]; then - _fail " $(echo "${login_response}" | jq -r '.message')" + if [ "$(echo "${login_response}" | _get_response_success)" != "success" ]; then + _fail " $(echo "${login_response}" | _get_response_message)" fi _info " success" @@ -199,8 +194,8 @@ _login() { _debug otp_response "${otp_response}" # Bail if OTP authentication fails. - if [ "$(echo "${otp_response}" | jq -r '.onSuccess')" != "success" ]; then - _fail " $(echo "${otp_response}" | jq -r '.message')" + if [ "$(echo "${otp_response}" | _get_response_success)" != "success" ]; then + _fail " $(echo "${otp_response}" | _get_response_message)" fi _info " success" @@ -227,11 +222,11 @@ _domain_env() { _check_2fa_miss "${domain_env_response}" - domain_env_success=$(echo "${domain_env_response}" | jq -r '.authenticated') + domain_env_success=$(echo "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2) # Bail if domain environment change fails. if [ "${domain_env_success}" != "true" ]; then - _fail " $(echo "${domain_env_response}" | jq -r '.message')" + _fail " $(echo "${domain_env_response}" | _get_response_message)" fi _info " success" @@ -252,14 +247,11 @@ _add_txt() { _check_2fa_miss "${addtxt_response}" - addtxt_message=$(echo "${addtxt_response}" | jq -r '.message') - addtxt_status=$(echo "${addtxt_response}" | jq -r '.status') + addtxt_message=$(echo "${addtxt_response}" | _get_response_message) + addtxt_status=$(echo "${addtxt_response}" | _get_response_status) # Bail if adding TXT entry fails. if [ "${addtxt_status}" != "true" ]; then - if [ "${addtxt_status}" = "null" ]; then - addtxt_message=$(echo "${addtxt_response}" | jq -r '.error.message') - fi _fail " ${addtxt_message}" fi @@ -306,14 +298,11 @@ _delete_txt() { _check_2fa_miss "${delete_txt_response}" - delete_txt_message=$(echo "${delete_txt_response}" | jq -r '.message') - delete_txt_status=$(echo "${delete_txt_response}" | jq -r '.status') + delete_txt_message=$(echo "${delete_txt_response}" | _get_response_message) + delete_txt_status=$(echo "${delete_txt_response}" | _get_response_status) # Skip if deleting TXT entry fails. if [ "${delete_txt_status}" != "true" ]; then - if [ "${delete_txt_status}" = "null" ]; then - delete_txt_message=$(echo "${delete_txt_response}" | jq -r '.error.message') - fi _err " ${delete_txt_message} (${_identifier})" else _info " success (${_identifier})" @@ -324,6 +313,18 @@ _delete_txt() { _info "" } +_get_response_message() { + _egrep_o '"message":"[^"]*"' | cut -d : -f 2 | tr -d '"' +} + +_get_response_status() { + _egrep_o '"status":\w*' | cut -d : -f 2 +} + +_get_response_success() { + _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' +} + _check_2fa_miss() { # Did we miss the 2FA? if test "${1#*multi_factor_form}" != "$1"; then From 46b2ee3bae604619e3d191085581846853e25ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Tue, 22 Nov 2016 18:31:38 +0100 Subject: [PATCH 05/16] Replace all echos with printf. --- dnsapi/dns_cyon.sh | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index af5a225b..28cb9e6e 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -65,9 +65,9 @@ dns_cyon_rm() { _load_credentials() { # Convert loaded password to/from base64 as needed. if [ "${cyon_password_b64}" ]; then - cyon_password="$(echo "${cyon_password_b64}" | _dbase64)" + cyon_password="$(printf "%s" "${cyon_password_b64}" | _dbase64)" elif [ "${cyon_password}" ]; then - cyon_password_b64="$(echo "${cyon_password}" | _base64)" + cyon_password_b64="$(printf "%s" "${cyon_password}" | _base64)" fi if [ -z "${cyon_username}" ] || [ -z "${cyon_password}" ]; then @@ -98,7 +98,7 @@ _is_idn() { _load_parameters() { # Read the required parameters to add the TXT entry. - fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')" + fulldomain="$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]')" fulldomain_idn="${fulldomain}" # Special case for IDNs, as cyon needs a domain environment change, @@ -160,8 +160,8 @@ _login() { _debug login_response "${login_response}" # Bail if login fails. - if [ "$(echo "${login_response}" | _get_response_success)" != "success" ]; then - _fail " $(echo "${login_response}" | _get_response_message)" + if [ "$(printf "%s" "${login_response}" | _get_response_success)" != "success" ]; then + _fail " $(printf "%s" "${login_response}" | _get_response_message)" fi _info " success" @@ -194,8 +194,8 @@ _login() { _debug otp_response "${otp_response}" # Bail if OTP authentication fails. - if [ "$(echo "${otp_response}" | _get_response_success)" != "success" ]; then - _fail " $(echo "${otp_response}" | _get_response_message)" + if [ "$(printf "%s" "${otp_response}" | _get_response_success)" != "success" ]; then + _fail " $(printf "%s" "${otp_response}" | _get_response_message)" fi _info " success" @@ -208,7 +208,7 @@ _domain_env() { _info " - Changing domain environment..." # Get the "example.com" part of the full domain name. - domain_env=$(echo "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/') + domain_env=$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/') _debug "Changing domain environment to ${domain_env}" domain_env_response=$(curl \ @@ -222,11 +222,11 @@ _domain_env() { _check_2fa_miss "${domain_env_response}" - domain_env_success=$(echo "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2) + domain_env_success=$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2) # Bail if domain environment change fails. if [ "${domain_env_success}" != "true" ]; then - _fail " $(echo "${domain_env_response}" | _get_response_message)" + _fail " $(printf "%s" "${domain_env_response}" | _get_response_message)" fi _info " success" @@ -247,8 +247,8 @@ _add_txt() { _check_2fa_miss "${addtxt_response}" - addtxt_message=$(echo "${addtxt_response}" | _get_response_message) - addtxt_status=$(echo "${addtxt_response}" | _get_response_status) + addtxt_message=$(printf "%s" "${addtxt_response}" | _get_response_message) + addtxt_status=$(printf "%s" "${addtxt_response}" | _get_response_status) # Bail if adding TXT entry fails. if [ "${addtxt_status}" != "true" ]; then @@ -267,19 +267,19 @@ _delete_txt() { -s \ -b "${cookiejar}" \ --compressed \ - -H "X-Requested-With: XMLHttpRequest" | \ - sed -e 's/data-hash/\\ndata-hash/g') + -H "X-Requested-With: XMLHttpRequest" \ + | sed -e 's/data-hash/\\ndata-hash/g') _debug list_txt_response "${list_txt_response}" _check_2fa_miss "${list_txt_response}" # Find and delete all acme challenge entries for the $fulldomain. - _dns_entries=$(echo -e "$list_txt_response" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p') + _dns_entries=$(printf "%s" "$list_txt_response" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p') - echo "${_dns_entries}" | while read -r _hash _identifier; do - dns_type="$(echo "$_identifier" | cut -d'|' -f1)" - dns_domain="$(echo "$_identifier" | cut -d'|' -f2)" + printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do + dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)" + dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)" if [ "${dns_type}" != "TXT" ] || [ "${dns_domain}" != "${fulldomain_idn}." ]; then continue @@ -298,8 +298,8 @@ _delete_txt() { _check_2fa_miss "${delete_txt_response}" - delete_txt_message=$(echo "${delete_txt_response}" | _get_response_message) - delete_txt_status=$(echo "${delete_txt_response}" | _get_response_status) + delete_txt_message=$(printf "%s" "${delete_txt_response}" | _get_response_message) + delete_txt_status=$(printf "%s" "${delete_txt_response}" | _get_response_status) # Skip if deleting TXT entry fails. if [ "${delete_txt_status}" != "true" ]; then From 2698ef6c5f7795703c49c49e8225d6e99e94dc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Wed, 23 Nov 2016 11:16:51 +0100 Subject: [PATCH 06/16] Return instead of exit. Clear OTP secret if environment variable is set to empty. This is for when the 2FA is disabled. Rename `_is_idn` function to `_is_idn_cyon`. Remove usage of curl (except for URL encoding of data). Instead of cleaning up the cookie jar, get rid of it completely and logout of cyon instead. --- dnsapi/dns_cyon.sh | 242 ++++++++++++++++++++++----------------------- 1 file changed, 118 insertions(+), 124 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 28cb9e6e..05079351 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -33,29 +33,23 @@ ######## dns_cyon_add() { - _load_credentials - _load_parameters "$@" - - _info_header "add" - _login - _domain_env - _add_txt - _cleanup - - return 0 + _load_credentials \ + && _load_parameters "$@" \ + && _info_header "add" \ + && _login \ + && _domain_env \ + && _add_txt \ + && _logout } dns_cyon_rm() { - _load_credentials - _load_parameters "$@" - - _info_header "delete" - _login - _domain_env - _delete_txt - _cleanup - - return 0 + _load_credentials \ + && _load_parameters "$@" \ + && _info_header "delete" \ + && _login \ + && _domain_env \ + && _delete_txt \ + && _logout } ######################### @@ -65,20 +59,22 @@ dns_cyon_rm() { _load_credentials() { # Convert loaded password to/from base64 as needed. if [ "${cyon_password_b64}" ]; then - cyon_password="$(printf "%s" "${cyon_password_b64}" | _dbase64)" + cyon_password="$(printf "%s" "${cyon_password_b64}" | _dbase64 "multiline")" elif [ "${cyon_password}" ]; then cyon_password_b64="$(printf "%s" "${cyon_password}" | _base64)" fi if [ -z "${cyon_username}" ] || [ -z "${cyon_password}" ]; then + # Dummy entries to satify script checker. cyon_username="" cyon_password="" cyon_otp_secret="" + _err "" _err "You haven't set your cyon.ch login credentials yet." _err "Please set the required cyon environment variables." _err "" - exit 1 + return 1 fi # Save the login credentials to the account.conf file. @@ -87,44 +83,52 @@ _load_credentials() { _saveaccountconf cyon_password_b64 "$cyon_password_b64" if [ ! -z "${cyon_otp_secret}" ]; then _saveaccountconf cyon_otp_secret "$cyon_otp_secret" + else + _clearaccountconf cyon_otp_secret fi } -_is_idn() { - _idn_temp=$(printf "%s" "$1" | tr -d "[0-9a-zA-Z.,-]") - _idn_temp2="$(printf "%s" "$1" | grep -o "xn--")" +_is_idn_cyon() { + _idn_temp="$(printf "%s" "${1}" | tr -d "[0-9a-zA-Z.,-_]")" + _idn_temp2="$(printf "%s" "${1}" | grep -o "xn--")" [ "$_idn_temp" ] || [ "$_idn_temp2" ] } +# comment on https://stackoverflow.com/a/10797966 +_urlencode_cyon() { + curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3- +} + _load_parameters() { # Read the required parameters to add the TXT entry. - fulldomain="$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]')" + fulldomain="$(printf "%s" "${1}" | tr '[:upper:]' '[:lower:]')" fulldomain_idn="${fulldomain}" # Special case for IDNs, as cyon needs a domain environment change, # which uses the "pretty" instead of the punycode version. - if _is_idn "$1"; then + if _is_idn_cyon "${fulldomain}"; then if ! _exists idn; then - _fail "Please install idn to process IDN names." + _err "Please install idn to process IDN names." + _err "" + return 1 fi fulldomain="$(idn -u "${fulldomain}")" fulldomain_idn="$(idn -a "${fulldomain}")" fi - _debug fulldomain "$fulldomain" - _debug fulldomain_idn "$fulldomain_idn" + _debug fulldomain "${fulldomain}" + _debug fulldomain_idn "${fulldomain_idn}" - txtvalue="$2" - _debug txtvalue "$txtvalue" + txtvalue="${2}" + _debug txtvalue "${txtvalue}" - # Cookiejar required for login session, as cyon.ch has no official API (yet). - cookiejar=$(tempfile) - _debug cookiejar "$cookiejar" + # This header is required for curl calls. + _H1="X-Requested-With: XMLHttpRequest" } _info_header() { - if [ "$1" = "add" ]; then + if [ "${1}" = "add" ]; then _info "" _info "+---------------------------------------------+" _info "| Adding DNS TXT entry to your cyon.ch domain |" @@ -132,42 +136,46 @@ _info_header() { _info "" _info " * Full Domain: ${fulldomain}" _info " * TXT Value: ${txtvalue}" - _info " * Cookie Jar: ${cookiejar}" _info "" - elif [ "$1" = "delete" ]; then + elif [ "${1}" = "delete" ]; then _info "" _info "+-------------------------------------------------+" _info "| Deleting DNS TXT entry from your cyon.ch domain |" _info "+-------------------------------------------------+" _info "" _info " * Full Domain: ${fulldomain}" - _info " * Cookie Jar: ${cookiejar}" _info "" fi } +_get_cookie_header() { + printf "%s" "$(sed -n 's/Set-\(Cookie:.*cyon=[^;]*\).*/\1/p' "$HTTP_HEADER" | _tail_n 1)" +} + _login() { _info " - Logging in..." - login_response=$(curl \ - "https://my.cyon.ch/auth/index/dologin-async" \ - -s \ - -c "${cookiejar}" \ - -H "X-Requested-With: XMLHttpRequest" \ - --data-urlencode "username=${cyon_username}" \ - --data-urlencode "password=${cyon_password}" \ - --data-urlencode "pathname=/") + username_encoded="$(printf "%s" "${cyon_username}" | _urlencode_cyon)" + password_encoded="$(printf "%s" "${cyon_password}" | _urlencode_cyon)" + + login_url="https://my.cyon.ch/auth/index/dologin-async" + login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")" + + login_response="$(_post "$login_data" "$login_url")" _debug login_response "${login_response}" # Bail if login fails. if [ "$(printf "%s" "${login_response}" | _get_response_success)" != "success" ]; then - _fail " $(printf "%s" "${login_response}" | _get_response_message)" + _err " $(printf "%s" "${login_response}" | _get_response_message)" + _err "" + return 1 fi _info " success" - # NECESSARY!! Load the main page after login, before the OTP check. - curl "https://my.cyon.ch/" -s --compressed -b "${cookiejar}" >/dev/null + # NECESSARY!! Load the main page after login, to get the new cookie. + _H2="$(_get_cookie_header)" + _get "https://my.cyon.ch/" > /dev/null # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. @@ -176,26 +184,25 @@ _login() { _info " - Authorising with OTP code..." if ! _exists oathtool; then - _fail "Please install oathtool to use 2 Factor Authentication." + _err "Please install oathtool to use 2 Factor Authentication." + _err "" + return 1 fi # Get OTP code with the defined secret. - otp_code=$(oathtool --base32 --totp "${cyon_otp_secret}" 2>/dev/null) + otp_code="$(oathtool --base32 --totp "${cyon_otp_secret}" 2>/dev/null)" - otp_response=$(curl \ - "https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" \ - -s \ - --compressed \ - -b "${cookiejar}" \ - -c "${cookiejar}" \ - -H "X-Requested-With: XMLHttpRequest" \ - -d "totpcode=${otp_code}&pathname=%2F&rememberme=0") + login_otp_url="https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" + login_otp_data="totpcode=${otp_code}&pathname=%2F&rememberme=0" - _debug otp_response "${otp_response}" + login_otp_response="$(_post "$login_otp_data" "$login_otp_url")" + _debug login_otp_response "${login_otp_response}" # Bail if OTP authentication fails. - if [ "$(printf "%s" "${otp_response}" | _get_response_success)" != "success" ]; then - _fail " $(printf "%s" "${otp_response}" | _get_response_message)" + if [ "$(printf "%s" "${login_otp_response}" | _get_response_success)" != "success" ]; then + _err " $(printf "%s" "${login_otp_response}" | _get_response_message)" + _err "" + return 1 fi _info " success" @@ -204,29 +211,36 @@ _login() { _info "" } +_logout() { + _info " - Logging out..." + + _get "https://my.cyon.ch/auth/index/dologout" > /dev/null + + _info " success" + _info "" +} + _domain_env() { _info " - Changing domain environment..." # Get the "example.com" part of the full domain name. - domain_env=$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/') + domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')" _debug "Changing domain environment to ${domain_env}" - domain_env_response=$(curl \ - "https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/domain%3A${domain_env}" \ - -s \ - --compressed \ - -b "${cookiejar}" \ - -H "X-Requested-With: XMLHttpRequest") + domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/domain%3A${domain_env}" + domain_env_response="$(_get "${domain_env_url}")" _debug domain_env_response "${domain_env_response}" - _check_2fa_miss "${domain_env_response}" + if ! _check_if_2fa_missed "${domain_env_response}"; then return 1; fi - domain_env_success=$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2) + domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)" # Bail if domain environment change fails. if [ "${domain_env_success}" != "true" ]; then - _fail " $(printf "%s" "${domain_env_response}" | _get_response_message)" + _err " $(printf "%s" "${domain_env_response}" | _get_response_message)" + _err "" + return 1 fi _info " success" @@ -235,47 +249,41 @@ _domain_env() { _add_txt() { _info " - Adding DNS TXT entry..." - addtxt_response=$(curl \ - "https://my.cyon.ch/domain/dnseditor/add-record-async" \ - -s \ - --compressed \ - -b "${cookiejar}" \ - -H "X-Requested-With: XMLHttpRequest" \ - -d "zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}") - _debug addtxt_response "${addtxt_response}" + add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async" + add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}" - _check_2fa_miss "${addtxt_response}" + add_txt_response="$(_post "$add_txt_data" "$add_txt_url")" + _debug add_txt_response "${add_txt_response}" - addtxt_message=$(printf "%s" "${addtxt_response}" | _get_response_message) - addtxt_status=$(printf "%s" "${addtxt_response}" | _get_response_status) + if ! _check_if_2fa_missed "${add_txt_response}"; then return 1; fi + + add_txt_message="$(printf "%s" "${add_txt_response}" | _get_response_message)" + add_txt_status="$(printf "%s" "${add_txt_response}" | _get_response_status)" # Bail if adding TXT entry fails. - if [ "${addtxt_status}" != "true" ]; then - _fail " ${addtxt_message}" + if [ "${add_txt_status}" != "true" ]; then + _err " ${add_txt_message}" + _err "" + return 1 fi - _info " success" + _info " success (TXT|${fulldomain_idn}.|${txtvalue})" _info "" } _delete_txt() { _info " - Deleting DNS TXT entry..." - list_txt_response=$(curl \ - "https://my.cyon.ch/domain/dnseditor/list-async" \ - -s \ - -b "${cookiejar}" \ - --compressed \ - -H "X-Requested-With: XMLHttpRequest" \ - | sed -e 's/data-hash/\\ndata-hash/g') + list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async" + list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')" _debug list_txt_response "${list_txt_response}" - _check_2fa_miss "${list_txt_response}" + if ! _check_if_2fa_missed "${list_txt_response}"; then return 1; fi # Find and delete all acme challenge entries for the $fulldomain. - _dns_entries=$(printf "%s" "$list_txt_response" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p') + _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')" printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)" @@ -285,21 +293,19 @@ _delete_txt() { continue fi - delete_txt_response=$(curl \ - "https://my.cyon.ch/domain/dnseditor/delete-record-async" \ - -s \ - --compressed \ - -b "${cookiejar}" \ - -H "X-Requested-With: XMLHttpRequest" \ - --data-urlencode "hash=${_hash}" \ - --data-urlencode "identifier=${_identifier}") + hash_encoded="$(printf "%s" "${_hash}" | _urlencode_cyon)" + identifier_encoded="$(printf "%s" "${_identifier}" | _urlencode_cyon)" + delete_txt_url="https://my.cyon.ch/domain/dnseditor/delete-record-async" + delete_txt_data="$(printf "%s" "hash=${hash_encoded}&identifier=${identifier_encoded}")" + + delete_txt_response="$(_post "$delete_txt_data" "$delete_txt_url")" _debug delete_txt_response "${delete_txt_response}" - _check_2fa_miss "${delete_txt_response}" + if ! _check_if_2fa_missed "${delete_txt_response}"; then return 1; fi - delete_txt_message=$(printf "%s" "${delete_txt_response}" | _get_response_message) - delete_txt_status=$(printf "%s" "${delete_txt_response}" | _get_response_status) + delete_txt_message="$(printf "%s" "${delete_txt_response}" | _get_response_message)" + delete_txt_status="$(printf "%s" "${delete_txt_response}" | _get_response_status)" # Skip if deleting TXT entry fails. if [ "${delete_txt_status}" != "true" ]; then @@ -325,23 +331,11 @@ _get_response_success() { _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' } -_check_2fa_miss() { +_check_if_2fa_missed() { # Did we miss the 2FA? - if test "${1#*multi_factor_form}" != "$1"; then - _fail " Missed OTP authentication!" + if test "${1#*multi_factor_form}" != "${1}"; then + _err " Missed OTP authentication!" + _err "" + return 1 fi } - -_fail() { - _err "$1" - _err "" - _cleanup - exit 1 -} - -_cleanup() { - _info " - Cleanup." - _debug "Remove cookie jar: ${cookiejar}" - rm "${cookiejar}" 2>/dev/null - _info "" -} From 98b3dcbf37fd462609b92e72080542e4b01b48c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Fri, 16 Dec 2016 03:44:48 +0100 Subject: [PATCH 07/16] Prefix all private functions with `_cyon`. Satisfy shellcheck. --- dnsapi/dns_cyon.sh | 106 ++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 05079351..3a441aaf 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -33,30 +33,30 @@ ######## dns_cyon_add() { - _load_credentials \ - && _load_parameters "$@" \ - && _info_header "add" \ - && _login \ - && _domain_env \ - && _add_txt \ - && _logout + _cyon_load_credentials \ + && _cyon_load_parameters "$@" \ + && _cyon_print_header "add" \ + && _cyon_login \ + && _cyon_change_domain_env \ + && _cyon_add_txt \ + && _cyon_logout } dns_cyon_rm() { - _load_credentials \ - && _load_parameters "$@" \ - && _info_header "delete" \ - && _login \ - && _domain_env \ - && _delete_txt \ - && _logout + _cyon_load_credentials \ + && _cyon_load_parameters "$@" \ + && _cyon_print_header "delete" \ + && _cyon_login \ + && _cyon_change_domain_env \ + && _cyon_delete_txt \ + && _cyon_logout } ######################### ### PRIVATE FUNCTIONS ### ######################### -_load_credentials() { +_cyon_load_credentials() { # Convert loaded password to/from base64 as needed. if [ "${cyon_password_b64}" ]; then cyon_password="$(printf "%s" "${cyon_password_b64}" | _dbase64 "multiline")" @@ -88,25 +88,25 @@ _load_credentials() { fi } -_is_idn_cyon() { +_cyon_is_idn() { _idn_temp="$(printf "%s" "${1}" | tr -d "[0-9a-zA-Z.,-_]")" _idn_temp2="$(printf "%s" "${1}" | grep -o "xn--")" [ "$_idn_temp" ] || [ "$_idn_temp2" ] } # comment on https://stackoverflow.com/a/10797966 -_urlencode_cyon() { - curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3- +_cyon_urlencode() { + curl -Gso /dev/null -w "%{url_effective}" --data-urlencode @- "" | cut -c 3- } -_load_parameters() { +_cyon_load_parameters() { # Read the required parameters to add the TXT entry. fulldomain="$(printf "%s" "${1}" | tr '[:upper:]' '[:lower:]')" fulldomain_idn="${fulldomain}" # Special case for IDNs, as cyon needs a domain environment change, # which uses the "pretty" instead of the punycode version. - if _is_idn_cyon "${fulldomain}"; then + if _cyon_is_idn "${fulldomain}"; then if ! _exists idn; then _err "Please install idn to process IDN names." _err "" @@ -127,7 +127,7 @@ _load_parameters() { _H1="X-Requested-With: XMLHttpRequest" } -_info_header() { +_cyon_print_header() { if [ "${1}" = "add" ]; then _info "" _info "+---------------------------------------------+" @@ -148,15 +148,15 @@ _info_header() { fi } -_get_cookie_header() { +_cyon_get_cookie_header() { printf "%s" "$(sed -n 's/Set-\(Cookie:.*cyon=[^;]*\).*/\1/p' "$HTTP_HEADER" | _tail_n 1)" } -_login() { +_cyon_login() { _info " - Logging in..." - username_encoded="$(printf "%s" "${cyon_username}" | _urlencode_cyon)" - password_encoded="$(printf "%s" "${cyon_password}" | _urlencode_cyon)" + username_encoded="$(printf "%s" "${cyon_username}" | _cyon_urlencode)" + password_encoded="$(printf "%s" "${cyon_password}" | _cyon_urlencode)" login_url="https://my.cyon.ch/auth/index/dologin-async" login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")" @@ -165,8 +165,8 @@ _login() { _debug login_response "${login_response}" # Bail if login fails. - if [ "$(printf "%s" "${login_response}" | _get_response_success)" != "success" ]; then - _err " $(printf "%s" "${login_response}" | _get_response_message)" + if [ "$(printf "%s" "${login_response}" | _cyon_get_response_success)" != "success" ]; then + _err " $(printf "%s" "${login_response}" | _cyon_get_response_message)" _err "" return 1 fi @@ -174,8 +174,8 @@ _login() { _info " success" # NECESSARY!! Load the main page after login, to get the new cookie. - _H2="$(_get_cookie_header)" - _get "https://my.cyon.ch/" > /dev/null + _H2="$(_cyon_get_cookie_header)" + _get "https://my.cyon.ch/" >/dev/null # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. @@ -199,8 +199,8 @@ _login() { _debug login_otp_response "${login_otp_response}" # Bail if OTP authentication fails. - if [ "$(printf "%s" "${login_otp_response}" | _get_response_success)" != "success" ]; then - _err " $(printf "%s" "${login_otp_response}" | _get_response_message)" + if [ "$(printf "%s" "${login_otp_response}" | _cyon_get_response_success)" != "success" ]; then + _err " $(printf "%s" "${login_otp_response}" | _cyon_get_response_message)" _err "" return 1 fi @@ -211,16 +211,16 @@ _login() { _info "" } -_logout() { +_cyon_logout() { _info " - Logging out..." - _get "https://my.cyon.ch/auth/index/dologout" > /dev/null + _get "https://my.cyon.ch/auth/index/dologout" >/dev/null _info " success" _info "" } -_domain_env() { +_cyon_change_domain_env() { _info " - Changing domain environment..." # Get the "example.com" part of the full domain name. @@ -232,13 +232,13 @@ _domain_env() { domain_env_response="$(_get "${domain_env_url}")" _debug domain_env_response "${domain_env_response}" - if ! _check_if_2fa_missed "${domain_env_response}"; then return 1; fi + if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)" # Bail if domain environment change fails. if [ "${domain_env_success}" != "true" ]; then - _err " $(printf "%s" "${domain_env_response}" | _get_response_message)" + _err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)" _err "" return 1 fi @@ -247,7 +247,7 @@ _domain_env() { _info "" } -_add_txt() { +_cyon_add_txt() { _info " - Adding DNS TXT entry..." add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async" @@ -256,10 +256,10 @@ _add_txt() { add_txt_response="$(_post "$add_txt_data" "$add_txt_url")" _debug add_txt_response "${add_txt_response}" - if ! _check_if_2fa_missed "${add_txt_response}"; then return 1; fi + if ! _cyon_check_if_2fa_missed "${add_txt_response}"; then return 1; fi - add_txt_message="$(printf "%s" "${add_txt_response}" | _get_response_message)" - add_txt_status="$(printf "%s" "${add_txt_response}" | _get_response_status)" + add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)" + add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)" # Bail if adding TXT entry fails. if [ "${add_txt_status}" != "true" ]; then @@ -272,7 +272,7 @@ _add_txt() { _info "" } -_delete_txt() { +_cyon_delete_txt() { _info " - Deleting DNS TXT entry..." list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async" @@ -280,7 +280,7 @@ _delete_txt() { list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')" _debug list_txt_response "${list_txt_response}" - if ! _check_if_2fa_missed "${list_txt_response}"; then return 1; fi + if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi # Find and delete all acme challenge entries for the $fulldomain. _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')" @@ -293,8 +293,8 @@ _delete_txt() { continue fi - hash_encoded="$(printf "%s" "${_hash}" | _urlencode_cyon)" - identifier_encoded="$(printf "%s" "${_identifier}" | _urlencode_cyon)" + hash_encoded="$(printf "%s" "${_hash}" | _cyon_urlencode)" + identifier_encoded="$(printf "%s" "${_identifier}" | _cyon_urlencode)" delete_txt_url="https://my.cyon.ch/domain/dnseditor/delete-record-async" delete_txt_data="$(printf "%s" "hash=${hash_encoded}&identifier=${identifier_encoded}")" @@ -302,10 +302,10 @@ _delete_txt() { delete_txt_response="$(_post "$delete_txt_data" "$delete_txt_url")" _debug delete_txt_response "${delete_txt_response}" - if ! _check_if_2fa_missed "${delete_txt_response}"; then return 1; fi + if ! _cyon_check_if_2fa_missed "${delete_txt_response}"; then return 1; fi - delete_txt_message="$(printf "%s" "${delete_txt_response}" | _get_response_message)" - delete_txt_status="$(printf "%s" "${delete_txt_response}" | _get_response_status)" + delete_txt_message="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_message)" + delete_txt_status="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_status)" # Skip if deleting TXT entry fails. if [ "${delete_txt_status}" != "true" ]; then @@ -319,23 +319,23 @@ _delete_txt() { _info "" } -_get_response_message() { +_cyon_get_response_message() { _egrep_o '"message":"[^"]*"' | cut -d : -f 2 | tr -d '"' } -_get_response_status() { +_cyon_get_response_status() { _egrep_o '"status":\w*' | cut -d : -f 2 } -_get_response_success() { +_cyon_get_response_success() { _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' } -_check_if_2fa_missed() { +_cyon_check_if_2fa_missed() { # Did we miss the 2FA? if test "${1#*multi_factor_form}" != "${1}"; then _err " Missed OTP authentication!" - _err "" - return 1 + _err "" + return 1 fi } From edfefb6763e6e2ecc523aac762e4db4d247bf72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Tue, 27 Dec 2016 00:56:05 +0100 Subject: [PATCH 08/16] Add usage instructions and repo link to post issues. --- README.md | 1 + dnsapi/README.md | 18 ++++++++++++++++++ dnsapi/dns_cyon.sh | 25 +++++-------------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f1c74806..5f82effb 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,7 @@ You don't have to do anything manually! 1. Alwaysdata.com API 1. Linode.com API 1. FreeDNS (https://freedns.afraid.org/) +1. cyon.ch **More APIs coming soon...** diff --git a/dnsapi/README.md b/dnsapi/README.md index 6a86bf4c..70af17e7 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -305,6 +305,24 @@ Note that you cannot use acme.sh automatic DNS validation for FreeDNS public dom you create under a FreeDNS public domain. You must own the top level domain in order to automaitcally validate with acme.sh at FreeDNS. +## 16. Use cyon.ch + +You only need to set your cyon.ch login credentials. +If you also have 2 Factor Authentication (OTP) enabled, you need to set your secret token too and have `oathtool` installed. + +``` +export cyon_username="your_cyon_username" +export cyon_password="your_cyon_password" +export cyon_otp_secret="your_otp_secret" # Only required if using 2FA +``` + +To issue a cert: +``` +acme.sh --issue --dns dns_cyon -d example.com -d www.example.com +``` + +The `cyon_username`, `cyon_password` and `cyon_otp_secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 3a441aaf..6c9c5c5d 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -9,29 +9,14 @@ # ------------- # - oathtool (When using 2 Factor Authentication) # +# Issues: +# ------- +# Any issues / questions / suggestions can be posted here: +# https://github.com/noplanman/cyon-api/issues +# # Author: Armando Lüscher ######## -######## -# Define cyon.ch login credentials: -# -# Either set them here: (uncomment these lines) -# -# cyon_username='your_cyon_username' -# cyon_password='your_cyon_password' -# cyon_otp_secret='your_otp_secret' # Only required if using 2FA -# -# ...or export them as environment variables in your shell: -# -# $ export cyon_username='your_cyon_username' -# $ export cyon_password='your_cyon_password' -# $ export cyon_otp_secret='your_otp_secret' # Only required if using 2FA -# -# *Note:* -# After the first run, the credentials are saved in the "account.conf" -# file, so any hard-coded or environment variables can then be removed. -######## - dns_cyon_add() { _cyon_load_credentials \ && _cyon_load_parameters "$@" \ From 09eccf6fc075c8f39ff0ec9ee1d7d20aa9826b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Wed, 28 Dec 2016 13:37:24 +0100 Subject: [PATCH 09/16] Use more flexible version of uppercase to lowercase conversion. --- dnsapi/dns_cyon.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 6c9c5c5d..ab8a38e5 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -86,7 +86,7 @@ _cyon_urlencode() { _cyon_load_parameters() { # Read the required parameters to add the TXT entry. - fulldomain="$(printf "%s" "${1}" | tr '[:upper:]' '[:lower:]')" + fulldomain="$(printf "%s" "${1}" | tr '[A-Z]' '[a-z]')" fulldomain_idn="${fulldomain}" # Special case for IDNs, as cyon needs a domain environment change, From afa3fc8bf921209476329cf9ebc6f58415dc0907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Wed, 28 Dec 2016 14:51:39 +0100 Subject: [PATCH 10/16] Adapt to use general naming rule for account variables. --- dnsapi/README.md | 8 ++++---- dnsapi/dns_cyon.sh | 34 +++++++++++++++++----------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 70af17e7..fd88d579 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -311,9 +311,9 @@ You only need to set your cyon.ch login credentials. If you also have 2 Factor Authentication (OTP) enabled, you need to set your secret token too and have `oathtool` installed. ``` -export cyon_username="your_cyon_username" -export cyon_password="your_cyon_password" -export cyon_otp_secret="your_otp_secret" # Only required if using 2FA +export CY_Username="your_cyon_username" +export CY_Password="your_cyon_password" +export CY_OTP_Secret="your_otp_secret" # Only required if using 2FA ``` To issue a cert: @@ -321,7 +321,7 @@ To issue a cert: acme.sh --issue --dns dns_cyon -d example.com -d www.example.com ``` -The `cyon_username`, `cyon_password` and `cyon_otp_secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +The `CY_Username`, `CY_Password` and `CY_OTP_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. # Use custom API diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index ab8a38e5..ca952db4 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -43,17 +43,17 @@ dns_cyon_rm() { _cyon_load_credentials() { # Convert loaded password to/from base64 as needed. - if [ "${cyon_password_b64}" ]; then - cyon_password="$(printf "%s" "${cyon_password_b64}" | _dbase64 "multiline")" - elif [ "${cyon_password}" ]; then - cyon_password_b64="$(printf "%s" "${cyon_password}" | _base64)" + if [ "${CY_Password_B64}" ]; then + CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64 "multiline")" + elif [ "${CY_Password}" ]; then + CY_Password_B64="$(printf "%s" "${CY_Password}" | _base64)" fi - if [ -z "${cyon_username}" ] || [ -z "${cyon_password}" ]; then + if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then # Dummy entries to satify script checker. - cyon_username="" - cyon_password="" - cyon_otp_secret="" + CY_Username="" + CY_Password="" + CY_OTP_Secret="" _err "" _err "You haven't set your cyon.ch login credentials yet." @@ -64,12 +64,12 @@ _cyon_load_credentials() { # Save the login credentials to the account.conf file. _debug "Save credentials to account.conf" - _saveaccountconf cyon_username "${cyon_username}" - _saveaccountconf cyon_password_b64 "$cyon_password_b64" - if [ ! -z "${cyon_otp_secret}" ]; then - _saveaccountconf cyon_otp_secret "$cyon_otp_secret" + _saveaccountconf CY_Username "${CY_Username}" + _saveaccountconf CY_Password_B64 "$CY_Password_B64" + if [ ! -z "${CY_OTP_Secret}" ]; then + _saveaccountconf CY_OTP_Secret "$CY_OTP_Secret" else - _clearaccountconf cyon_otp_secret + _clearaccountconf CY_OTP_Secret fi } @@ -140,8 +140,8 @@ _cyon_get_cookie_header() { _cyon_login() { _info " - Logging in..." - username_encoded="$(printf "%s" "${cyon_username}" | _cyon_urlencode)" - password_encoded="$(printf "%s" "${cyon_password}" | _cyon_urlencode)" + username_encoded="$(printf "%s" "${CY_Username}" | _cyon_urlencode)" + password_encoded="$(printf "%s" "${CY_Password}" | _cyon_urlencode)" login_url="https://my.cyon.ch/auth/index/dologin-async" login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")" @@ -165,7 +165,7 @@ _cyon_login() { # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. # 2FA authentication with OTP? - if [ ! -z "${cyon_otp_secret}" ]; then + if [ ! -z "${CY_OTP_Secret}" ]; then _info " - Authorising with OTP code..." if ! _exists oathtool; then @@ -175,7 +175,7 @@ _cyon_login() { fi # Get OTP code with the defined secret. - otp_code="$(oathtool --base32 --totp "${cyon_otp_secret}" 2>/dev/null)" + otp_code="$(oathtool --base32 --totp "${CY_OTP_Secret}" 2>/dev/null)" login_otp_url="https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" login_otp_data="totpcode=${otp_code}&pathname=%2F&rememberme=0" From ce9fae82bd7a31c52514e5f0f7d6f6a0f5854117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Thu, 29 Dec 2016 16:12:42 +0100 Subject: [PATCH 11/16] Update cookie retrieval using _egrep_o (thanks @Neilpang) --- dnsapi/dns_cyon.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index ca952db4..0390592a 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -134,7 +134,7 @@ _cyon_print_header() { } _cyon_get_cookie_header() { - printf "%s" "$(sed -n 's/Set-\(Cookie:.*cyon=[^;]*\).*/\1/p' "$HTTP_HEADER" | _tail_n 1)" + printf "Cookie: %s" "$(cat "$HTTP_HEADER" | grep "cyon=" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')" } _cyon_login() { From 6e8dcdce7834938db893f85955aef27e1d71ca9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Wed, 4 Jan 2017 03:19:58 +0100 Subject: [PATCH 12/16] Satisfy shellcheck. --- dnsapi/dns_cyon.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 0390592a..54162198 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -134,7 +134,7 @@ _cyon_print_header() { } _cyon_get_cookie_header() { - printf "Cookie: %s" "$(cat "$HTTP_HEADER" | grep "cyon=" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')" + printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')" } _cyon_login() { From 9499a1142bfdaf0c3fb9f729421fd8ae853705ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Mon, 30 Jan 2017 16:36:49 +0100 Subject: [PATCH 13/16] Remove custom URL encoding and use library's implementation. --- dnsapi/dns_cyon.sh | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 54162198..d225138b 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -79,11 +79,6 @@ _cyon_is_idn() { [ "$_idn_temp" ] || [ "$_idn_temp2" ] } -# comment on https://stackoverflow.com/a/10797966 -_cyon_urlencode() { - curl -Gso /dev/null -w "%{url_effective}" --data-urlencode @- "" | cut -c 3- -} - _cyon_load_parameters() { # Read the required parameters to add the TXT entry. fulldomain="$(printf "%s" "${1}" | tr '[A-Z]' '[a-z]')" @@ -140,8 +135,8 @@ _cyon_get_cookie_header() { _cyon_login() { _info " - Logging in..." - username_encoded="$(printf "%s" "${CY_Username}" | _cyon_urlencode)" - password_encoded="$(printf "%s" "${CY_Password}" | _cyon_urlencode)" + username_encoded="$(printf "%s" "${CY_Username}" | _url_encode)" + password_encoded="$(printf "%s" "${CY_Password}" | _url_encode)" login_url="https://my.cyon.ch/auth/index/dologin-async" login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")" @@ -278,8 +273,8 @@ _cyon_delete_txt() { continue fi - hash_encoded="$(printf "%s" "${_hash}" | _cyon_urlencode)" - identifier_encoded="$(printf "%s" "${_identifier}" | _cyon_urlencode)" + hash_encoded="$(printf "%s" "${_hash}" | _url_encode)" + identifier_encoded="$(printf "%s" "${_identifier}" | _url_encode)" delete_txt_url="https://my.cyon.ch/domain/dnseditor/delete-record-async" delete_txt_data="$(printf "%s" "hash=${hash_encoded}&identifier=${identifier_encoded}")" From 884f70fb399cd73c1004c9eb5c11a582e9e35c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Tue, 31 Jan 2017 15:23:40 +0100 Subject: [PATCH 14/16] Remove square brackets from ranges. Export curl header variables. --- dnsapi/dns_cyon.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index d225138b..85ff028c 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -74,14 +74,14 @@ _cyon_load_credentials() { } _cyon_is_idn() { - _idn_temp="$(printf "%s" "${1}" | tr -d "[0-9a-zA-Z.,-_]")" + _idn_temp="$(printf "%s" "${1}" | tr -d "0-9a-zA-Z.,-_")" _idn_temp2="$(printf "%s" "${1}" | grep -o "xn--")" [ "$_idn_temp" ] || [ "$_idn_temp2" ] } _cyon_load_parameters() { # Read the required parameters to add the TXT entry. - fulldomain="$(printf "%s" "${1}" | tr '[A-Z]' '[a-z]')" + fulldomain="$(printf "%s" "${1}" | tr "A-Z" "a-z")" fulldomain_idn="${fulldomain}" # Special case for IDNs, as cyon needs a domain environment change, @@ -105,6 +105,7 @@ _cyon_load_parameters() { # This header is required for curl calls. _H1="X-Requested-With: XMLHttpRequest" + export _H1 } _cyon_print_header() { @@ -155,6 +156,8 @@ _cyon_login() { # NECESSARY!! Load the main page after login, to get the new cookie. _H2="$(_cyon_get_cookie_header)" + export _H2 + _get "https://my.cyon.ch/" >/dev/null # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. From 3e1418d662a3dc15a8359c2441397d63688d596e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Sun, 12 Feb 2017 12:30:06 +0100 Subject: [PATCH 15/16] Use gloo item key for environment change, to support different account types. (this isn't working 100% yet, still looking for a solution) --- dnsapi/dns_cyon.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 85ff028c..0ced4217 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -210,7 +210,10 @@ _cyon_change_domain_env() { domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')" _debug "Changing domain environment to ${domain_env}" - domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/domain%3A${domain_env}" + gloo_item_key="$(_get "https://my.cyon.ch/domain/" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")" + _debug gloo_item_key "${gloo_item_key}" + + domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}" domain_env_response="$(_get "${domain_env_url}")" _debug domain_env_response "${domain_env_response}" From a6d2e3a1e6b34c1fbf0675401672b76e8925809b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20L=C3=BCscher?= Date: Sun, 19 Feb 2017 13:26:32 +0100 Subject: [PATCH 16/16] Suppress shellcheck warnings. --- dnsapi/dns_cyon.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 0ced4217..c096d8b0 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -81,6 +81,7 @@ _cyon_is_idn() { _cyon_load_parameters() { # Read the required parameters to add the TXT entry. + # shellcheck disable=SC2018,SC2019 fulldomain="$(printf "%s" "${1}" | tr "A-Z" "a-z")" fulldomain_idn="${fulldomain}"