Browse Source

Merge pull request #650 from Neilpang/dev

Dev
neil 8 years ago
parent
commit
086444c73a
4 changed files with 348 additions and 1 deletions
  1. 1 0
      README.md
  2. 1 1
      acme.sh
  3. 18 0
      dnsapi/README.md
  4. 328 0
      dnsapi/dns_cyon.sh

+ 1 - 0
README.md

@@ -292,6 +292,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...**
 

+ 1 - 1
acme.sh

@@ -1730,7 +1730,7 @@ _setopt() {
     _debug3 APP
     echo "$__opt$__sep$__val$__end" >>"$__conf"
   fi
-  _debug2 "$(grep -n "^$__opt$__sep" "$__conf")"
+  _debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
 }
 
 #_save_conf  file key  value

+ 18 - 0
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 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:
+```
+acme.sh --issue --dns dns_cyon -d example.com -d www.example.com
+```
+
+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
 
 If your API is not supported yet, you can write your own DNS API.

+ 328 - 0
dnsapi/dns_cyon.sh

@@ -0,0 +1,328 @@
+#!/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:
+# -------------
+# - 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 <armando@noplanman.ch>
+########
+
+dns_cyon_add() {
+  _cyon_load_credentials \
+    && _cyon_load_parameters "$@" \
+    && _cyon_print_header "add" \
+    && _cyon_login \
+    && _cyon_change_domain_env \
+    && _cyon_add_txt \
+    && _cyon_logout
+}
+
+dns_cyon_rm() {
+  _cyon_load_credentials \
+    && _cyon_load_parameters "$@" \
+    && _cyon_print_header "delete" \
+    && _cyon_login \
+    && _cyon_change_domain_env \
+    && _cyon_delete_txt \
+    && _cyon_logout
+}
+
+#########################
+### PRIVATE FUNCTIONS ###
+#########################
+
+_cyon_load_credentials() {
+  # Convert loaded password to/from base64 as needed.
+  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 "${CY_Username}" ] || [ -z "${CY_Password}" ]; then
+    # Dummy entries to satify script checker.
+    CY_Username=""
+    CY_Password=""
+    CY_OTP_Secret=""
+
+    _err ""
+    _err "You haven't set your cyon.ch login credentials yet."
+    _err "Please set the required cyon environment variables."
+    _err ""
+    return 1
+  fi
+
+  # Save the login credentials to the account.conf file.
+  _debug "Save credentials to account.conf"
+  _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 CY_OTP_Secret
+  fi
+}
+
+_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" ]
+}
+
+_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}"
+
+  # Special case for IDNs, as cyon needs a domain environment change,
+  # which uses the "pretty" instead of the punycode version.
+  if _cyon_is_idn "${fulldomain}"; then
+    if ! _exists idn; then
+      _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}"
+
+  txtvalue="${2}"
+  _debug txtvalue "${txtvalue}"
+
+  # This header is required for curl calls.
+  _H1="X-Requested-With: XMLHttpRequest"
+  export _H1
+}
+
+_cyon_print_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 ""
+  elif [ "${1}" = "delete" ]; then
+    _info ""
+    _info "+-------------------------------------------------+"
+    _info "| Deleting DNS TXT entry from your cyon.ch domain |"
+    _info "+-------------------------------------------------+"
+    _info ""
+    _info "  * Full Domain: ${fulldomain}"
+    _info ""
+  fi
+}
+
+_cyon_get_cookie_header() {
+  printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')"
+}
+
+_cyon_login() {
+  _info "  - Logging in..."
+
+  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")"
+
+  login_response="$(_post "$login_data" "$login_url")"
+  _debug login_response "${login_response}"
+
+  # Bail if login fails.
+  if [ "$(printf "%s" "${login_response}" | _cyon_get_response_success)" != "success" ]; then
+    _err "    $(printf "%s" "${login_response}" | _cyon_get_response_message)"
+    _err ""
+    return 1
+  fi
+
+  _info "    success"
+
+  # 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.
+
+  # 2FA authentication with OTP?
+  if [ ! -z "${CY_OTP_Secret}" ]; then
+    _info "  - Authorising with OTP code..."
+
+    if ! _exists oathtool; then
+      _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 "${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"
+
+    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" "${login_otp_response}" | _cyon_get_response_success)" != "success" ]; then
+      _err "    $(printf "%s" "${login_otp_response}" | _cyon_get_response_message)"
+      _err ""
+      return 1
+    fi
+
+    _info "    success"
+  fi
+
+  _info ""
+}
+
+_cyon_logout() {
+  _info "  - Logging out..."
+
+  _get "https://my.cyon.ch/auth/index/dologout" >/dev/null
+
+  _info "    success"
+  _info ""
+}
+
+_cyon_change_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/')"
+  _debug "Changing domain environment to ${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}"
+
+  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}" | _cyon_get_response_message)"
+    _err ""
+    return 1
+  fi
+
+  _info "    success"
+  _info ""
+}
+
+_cyon_add_txt() {
+  _info "  - Adding DNS TXT entry..."
+
+  add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async"
+  add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}"
+
+  add_txt_response="$(_post "$add_txt_data" "$add_txt_url")"
+  _debug add_txt_response "${add_txt_response}"
+
+  if ! _cyon_check_if_2fa_missed "${add_txt_response}"; then return 1; fi
+
+  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
+    _err "    ${add_txt_message}"
+    _err ""
+    return 1
+  fi
+
+  _info "    success (TXT|${fulldomain_idn}.|${txtvalue})"
+  _info ""
+}
+
+_cyon_delete_txt() {
+  _info "  - Deleting DNS TXT entry..."
+
+  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}"
+
+  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')"
+
+  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
+    fi
+
+    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}")"
+
+    delete_txt_response="$(_post "$delete_txt_data" "$delete_txt_url")"
+    _debug delete_txt_response "${delete_txt_response}"
+
+    if ! _cyon_check_if_2fa_missed "${delete_txt_response}"; then return 1; fi
+
+    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
+      _err "    ${delete_txt_message} (${_identifier})"
+    else
+      _info "    success (${_identifier})"
+    fi
+  done
+
+  _info "    done"
+  _info ""
+}
+
+_cyon_get_response_message() {
+  _egrep_o '"message":"[^"]*"' | cut -d : -f 2 | tr -d '"'
+}
+
+_cyon_get_response_status() {
+  _egrep_o '"status":\w*' | cut -d : -f 2
+}
+
+_cyon_get_response_success() {
+  _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"'
+}
+
+_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
+  fi
+}