Browse Source

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.

Armando Lüscher 8 years ago
parent
commit
2698ef6c5f
1 changed files with 118 additions and 124 deletions
  1. 118 124
      dnsapi/dns_cyon.sh

+ 118 - 124
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!"
-  fi
-}
-
-_fail() {
-  _err "$1"
+  if test "${1#*multi_factor_form}" != "${1}"; then
+    _err "    Missed OTP authentication!"
   _err ""
-  _cleanup
-  exit 1
-}
-
-_cleanup() {
-  _info "  - Cleanup."
-  _debug "Remove cookie jar: ${cookiejar}"
-  rm "${cookiejar}" 2>/dev/null
-  _info ""
+  return 1
+  fi
 }