Browse Source

Merge branch 'dev' into patch-3

shar0119 8 years ago
parent
commit
90c70fa5bf
10 changed files with 455 additions and 28 deletions
  1. 8 1
      Dockerfile
  2. 3 1
      README.md
  3. 43 20
      acme.sh
  4. 1 0
      dnsapi/README.md
  5. 16 0
      dnsapi/dns_aws.sh
  6. 15 2
      dnsapi/dns_cf.sh
  7. 216 0
      dnsapi/dns_dynu.sh
  8. 3 3
      dnsapi/dns_freedns.sh
  9. 1 1
      dnsapi/dns_ovh.sh
  10. 149 0
      dnsapi/dns_vscale.sh

+ 8 - 1
Dockerfile

@@ -48,5 +48,12 @@ RUN for verb in help \
     printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
   ; done
 
-ENTRYPOINT ["/root/.acme.sh/acme.sh", "--config-home", "/acme.sh"]
+RUN printf "%b" '#!'"/usr/bin/env sh\n \
+if [ \"\$1\" = \"daemon\" ];  then \n \
+ crond; tail -f /dev/null;\n \
+else \n \
+ /root/.acme.sh/acme.sh --config-home /acme.sh \"\$@\"\n \
+fi" >/entry.sh && chmod +x /entry.sh
+
+ENTRYPOINT ["/entry.sh"]
 CMD ["--help"]

+ 3 - 1
README.md

@@ -9,7 +9,7 @@
 - DOES NOT require `root/sudoer` access.
 - Docker friendly
 
-It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
+It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
 
 Wiki: https://github.com/Neilpang/acme.sh/wiki
 
@@ -31,6 +31,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 - [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html)
 - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
 - [archlinux](https://aur.archlinux.org/packages/acme.sh-git/)
+- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
 - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials)
 
 # Tested OS
@@ -313,6 +314,7 @@ You don't have to do anything manually!
 1. DigitalOcean API (native)
 1. ClouDNS.net API
 1. Infoblox NIOS API (https://www.infoblox.com/)
+1. VSCALE (https://vscale.io/)
 
 **More APIs coming soon...**
 

+ 43 - 20
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-VER=2.6.8
+VER=2.6.9
 
 PROJECT_NAME="acme.sh"
 
@@ -107,7 +107,7 @@ __green() {
   if [ "$__INTERACTIVE" ]; then
     printf '\033[1;31;32m'
   fi
-  printf -- "$1"
+  printf -- "%b" "$1"
   if [ "$__INTERACTIVE" ]; then
     printf '\033[0m'
   fi
@@ -117,7 +117,7 @@ __red() {
   if [ "$__INTERACTIVE" ]; then
     printf '\033[1;31;40m'
   fi
-  printf -- "$1"
+  printf -- "%b" "$1"
   if [ "$__INTERACTIVE" ]; then
     printf '\033[0m'
   fi
@@ -347,7 +347,7 @@ _hasfield() {
     fi
   done
   _debug2 "'$_str' does not contain '$_field'"
-  return 1 #not contains 
+  return 1 #not contains
 }
 
 _getfield() {
@@ -722,7 +722,7 @@ _url_encode() {
       "7e")
         printf "%s" "~"
         ;;
-      #other hex  
+      #other hex
       *)
         printf '%%%s' "$_hex_code"
         ;;
@@ -1025,7 +1025,7 @@ _createcsr() {
     else
       alt="DNS:$domainlist"
     fi
-    #multi 
+    #multi
     _info "Multi domain" "$alt"
     printf -- "\nsubjectAltName=$alt" >>"$csrconf"
   fi
@@ -1093,7 +1093,7 @@ _readSubjectAltNamesFromCSR() {
   printf "%s" "$_dnsAltnames" | sed "s/DNS://g"
 }
 
-#_csrfile 
+#_csrfile
 _readKeyLengthFromCSR() {
   _csrfile="$1"
   if [ -z "$_csrfile" ]; then
@@ -1102,12 +1102,13 @@ _readKeyLengthFromCSR() {
   fi
 
   _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")"
+  _debug2 _outcsr "$_outcsr"
   if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then
     _debug "ECC CSR"
-    echo "$_outcsr" | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
+    echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
   else
     _debug "RSA CSR"
-    echo "$_outcsr" | _egrep_o "(^ *|^RSA )Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1
+    echo "$_outcsr" | tr "\t" " " | _egrep_o "(^ *|RSA )Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1
   fi
 }
 
@@ -1191,7 +1192,7 @@ toPkcs8() {
 
 }
 
-#[2048]  
+#[2048]
 createAccountKey() {
   _info "Creating account key"
   if [ -z "$1" ]; then
@@ -1846,6 +1847,24 @@ _saveaccountconf() {
   _save_conf "$ACCOUNT_CONF_PATH" "$1" "$2"
 }
 
+#key  value
+_saveaccountconf_mutable() {
+  _save_conf "$ACCOUNT_CONF_PATH" "SAVED_$1" "$2"
+  #remove later
+  _clearaccountconf "$1"
+}
+
+#key
+_readaccountconf() {
+  _read_conf "$ACCOUNT_CONF_PATH" "$1"
+}
+
+#key
+_readaccountconf_mutable() {
+  _rac_key="$1"
+  _readaccountconf "SAVED_$_rac_key"
+}
+
 #_clearaccountconf   key
 _clearaccountconf() {
   _clear_conf "$ACCOUNT_CONF_PATH" "$1"
@@ -2527,7 +2546,7 @@ _setNginx() {
 location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
   default_type text/plain;
   return 200 \"\$1.$_thumbpt\";
-}  
+}
 #NGINX_START
 " >>"$FOUND_REAL_NGINX_CONF"
 
@@ -2564,7 +2583,7 @@ _checkConf() {
   if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then
     _debug "wildcard"
     for _w_f in $2; do
-      if [ -f "$_w_f"] && _checkConf "$1" "$_w_f"; then
+      if [ -f "$_w_f" ] && _checkConf "$1" "$_w_f"; then
         return 0
       fi
     done
@@ -3114,12 +3133,16 @@ __trigger_validation() {
   _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}"
 }
 
-#webroot, domain domainlist  keylength 
+#webroot, domain domainlist  keylength
 issue() {
   if [ -z "$2" ]; then
     _usage "Usage: $PROJECT_ENTRY --issue  -d  a.com  -w /path/to/webroot/a.com/ "
     return 1
   fi
+  if [ -z "$1" ]; then
+    _usage "Please specify at least one validation method: '--webroot', '--standalone', '--apache', '--nginx' or '--dns' etc."
+    return 1
+  fi
   _web_roots="$1"
   _main_domain="$2"
   _alt_domains="$3"
@@ -3643,7 +3666,7 @@ issue() {
 
     #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
     #  _debug "Get cert failed. Let's try last response."
-    #  printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH" 
+    #  printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH"
     #fi
 
     if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then
@@ -3860,7 +3883,7 @@ renewAll() {
         return "$rc"
       else
         _ret="$rc"
-        _err "Error renew $d, Go ahead to next one."
+        _err "Error renew $d."
       fi
     fi
   done
@@ -4784,7 +4807,7 @@ Commands:
   --create-domain-key      Create an domain private key, professional use.
   --createCSR, -ccsr       Create CSR , professional use.
   --deactivate             Deactivate the domain authz, professional use.
-  
+
 Parameters:
   --domain, -d   domain.tld         Specifies a domain, used to issue, renew or revoke etc.
   --force, -f                       Used to force to install or force to renew a cert immediately.
@@ -4798,20 +4821,20 @@ Parameters:
   --apache                          Use apache mode.
   --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file]   Use dns mode or dns api.
   --dnssleep  [$DEFAULT_DNS_SLEEP]                  The time in seconds to wait for all the txt records to take effect in dns api mode. Default $DEFAULT_DNS_SLEEP seconds.
-  
+
   --keylength, -k [2048]            Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384.
   --accountkeylength, -ak [2048]    Specifies the account key length.
   --log    [/path/to/logfile]       Specifies the log file. The default is: \"$DEFAULT_LOG_FILE\" if you don't give a file path here.
   --log-level 1|2                   Specifies the log level, default is 1.
   --syslog [0|3|6|7]                Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
-  
+
   These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert:
-  
+
   --cert-file                       After issue/renew, the cert will be copied to this path.
   --key-file                        After issue/renew, the key will be copied to this path.
   --ca-file                         After issue/renew, the intermediate cert will be copied to this path.
   --fullchain-file                  After issue/renew, the fullchain cert will be copied to this path.
-  
+
   --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
 
   --accountconf                     Specifies a customized account config file.

+ 1 - 0
dnsapi/README.md

@@ -468,6 +468,7 @@ acme.sh --issue --dns dns_dynu -d example.com -d www.example.com
 
 The `Dynu_ClientId` and `Dynu_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.

+ 16 - 0
dnsapi/dns_aws.sh

@@ -88,6 +88,19 @@ _get_root() {
     while true; do
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       if [ -z "$h" ]; then
+        if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then
+          _debug "IsTruncated"
+          _nextMarker="$(echo "$response" | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1)"
+          _debug "NextMarker" "$_nextMarker"
+          if aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"; then
+            _debug "Truncated request OK"
+            i=2
+            p=1
+            continue
+          else
+            _err "Truncated request error."
+          fi
+        fi
         #not valid
         return 1
       fi
@@ -208,6 +221,9 @@ aws_rest() {
   _debug _H2 "$_H2"
 
   url="$AWS_URL/$ep"
+  if [ "$qsr" ]; then
+    url="$AWS_URL/$ep?$qsr"
+  fi
 
   if [ "$mtd" = "GET" ]; then
     response="$(_get "$url")"

+ 15 - 2
dnsapi/dns_cf.sh

@@ -14,6 +14,8 @@ dns_cf_add() {
   fulldomain=$1
   txtvalue=$2
 
+  CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
+  CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}"
   if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
     CF_Key=""
     CF_Email=""
@@ -29,8 +31,8 @@ dns_cf_add() {
   fi
 
   #save the api key and email to the account conf file.
-  _saveaccountconf CF_Key "$CF_Key"
-  _saveaccountconf CF_Email "$CF_Email"
+  _saveaccountconf_mutable CF_Key "$CF_Key"
+  _saveaccountconf_mutable CF_Email "$CF_Email"
 
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
@@ -83,6 +85,17 @@ dns_cf_add() {
 dns_cf_rm() {
   fulldomain=$1
   txtvalue=$2
+
+  CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
+  CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}"
+  if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
+    CF_Key=""
+    CF_Email=""
+    _err "You don't specify cloudflare api key and email yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"

+ 216 - 0
dnsapi/dns_dynu.sh

@@ -0,0 +1,216 @@
+#!/usr/bin/env sh
+
+#Client ID
+#Dynu_ClientId="0b71cae7-a099-4f6b-8ddf-94571cdb760d"
+#
+#Secret
+#Dynu_Secret="aCUEY4BDCV45KI8CSIC3sp2LKQ9"
+#
+#Token
+Dynu_Token=""
+#
+#Endpoint
+Dynu_EndPoint="https://api.dynu.com/v1"
+#
+#Author: Dynu Systems, Inc.
+#Report Bugs here: https://github.com/shar0119/acme.sh
+#
+########  Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dynu_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
+    Dynu_ClientId=""
+    Dynu_Secret=""
+    _err "Dynu client id and secret is not specified."
+    _err "Please create you API client id and secret and try again."
+    return 1
+  fi
+
+  #save the client id and secret to the account conf file.
+  _saveaccountconf Dynu_ClientId "$Dynu_ClientId"
+  _saveaccountconf Dynu_Secret "$Dynu_Secret"
+
+  if [ -z "$Dynu_Token" ]; then
+    _info "Getting Dynu token."
+    if ! _dynu_authentication; then
+      _err "Can not get token."
+    fi
+  fi
+
+  _debug "Detect root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Invalid domain."
+    return 1
+  fi
+
+  _debug _node "$_node"
+  _debug _domain_name "$_domain_name"
+
+  _info "Creating TXT record."
+  if ! _dynu_rest POST "dns/record/add" "{\"domain_name\":\"$_domain_name\",\"node_name\":\"$_node\",\"record_type\":\"TXT\",\"text_data\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "text_data"; then
+    _err "Could not add TXT record."
+    return 1
+  fi
+
+  return 0
+}
+
+#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dynu_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
+    Dynu_ClientId=""
+    Dynu_Secret=""
+    _err "Dynu client id and secret is not specified."
+    _err "Please create you API client id and secret and try again."
+    return 1
+  fi
+
+  #save the client id and secret to the account conf file.
+  _saveaccountconf Dynu_ClientId "$Dynu_ClientId"
+  _saveaccountconf Dynu_Secret "$Dynu_Secret"
+
+  if [ -z "$Dynu_Token" ]; then
+    _info "Getting Dynu token."
+    if ! _dynu_authentication; then
+      _err "Can not get token."
+    fi
+  fi
+
+  _debug "Detect root zone."
+  if ! _get_root "$fulldomain"; then
+    _err "Invalid domain."
+    return 1
+  fi
+
+  _debug _node "$_node"
+  _debug _domain_name "$_domain_name"
+
+  _info "Checking for TXT record."
+  if ! _get_recordid "$fulldomain" "$txtvalue"; then
+    _err "Could not get TXT record id."
+    return 1
+  fi
+
+  if [ "$_dns_record_id" = "" ]; then
+    _err "TXT record not found."
+    return 1
+  fi
+
+  _info "Removing TXT record."
+  if ! _delete_txt_record "$_dns_record_id"; then
+    _err "Could not remove TXT record $_dns_record_id."
+  fi
+
+  return 0
+}
+
+########  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _node=_acme-challenge.www
+# _domain_name=domain.com
+_get_root() {
+  domain=$1
+  if ! _dynu_rest GET "dns/getroot/$domain"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "domain_name"; then
+    _debug "Domain name not found."
+    return 1
+  fi
+
+  _domain_name=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2)
+  _node=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 3 | cut -d : -f 2 | cut -d '"' -f 2)
+  return 0
+}
+
+_get_recordid() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _dynu_rest GET "dns/record/get?hostname=$fulldomain&rrtype=TXT"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "$txtvalue"; then
+    _dns_record_id=0
+    return 0
+  fi
+
+  _dns_record_id=$(printf "%s" "$response" | _egrep_o "{[^}]*}" | grep "\"text_data\":\"$txtvalue\"" | grep -Po '"id":\K[0-9]+')
+
+  return 0
+}
+
+_delete_txt_record() {
+  _dns_record_id=$1
+
+  if ! _dynu_rest GET "dns/record/delete/$_dns_record_id"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "true"; then
+    return 1
+  fi
+
+  return 0
+}
+
+_dynu_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="Authorization: Bearer $Dynu_Token"
+  export _H2="Content-Type: application/json"
+
+  if [ "$data" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")"
+  else
+    _info "Getting $Dynu_EndPoint/$ep"
+    response="$(_get "$Dynu_EndPoint/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}
+
+_dynu_authentication() {
+  realm="$(printf "%s" "$Dynu_ClientId:$Dynu_Secret" | _base64)"
+
+  export _H1="Authorization: Basic $realm"
+  export _H2="Content-Type: application/json"
+
+  response="$(_get "$Dynu_EndPoint/oauth2/token")"
+  if [ "$?" != "0" ]; then
+    _err "Authentication failed."
+    return 1
+  fi
+  if _contains "$response" "accessToken"; then
+    Dynu_Token=$(printf "%s" "$response" | tr -d "[]" | cut -d , -f 2 | cut -d : -f 2 | cut -d '"' -f 2)
+  fi
+  if _contains "$Dynu_Token" "null"; then
+    Dynu_Token=""
+  fi
+
+  _debug2 response "$response"
+  return 0
+}

+ 3 - 3
dnsapi/dns_freedns.sh

@@ -53,7 +53,7 @@ dns_freedns_add() {
   i="$(_math "$i" - 1)"
   sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")"
 
-  # Sometimes FreeDNS does not return the subdomain page but rather 
+  # Sometimes FreeDNS does not return the subdomain page but rather
   # returns a page regarding becoming a premium member.  This usually
   # happens after a period of inactivity.  Immediately trying again
   # returns the correct subdomain page.  So, we will try twice to
@@ -72,7 +72,7 @@ dns_freedns_add() {
     fi
 
     # Now convert the tables in the HTML to CSV.  This litte gem from
-    # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv    
+    # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv
     subdomain_csv="$(echo "$htmlpage" \
       | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \
       | sed 's/^[\ \t]*//g' \
@@ -196,7 +196,7 @@ dns_freedns_rm() {
   FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")"
   _debug "FreeDNS login cookies: $FREEDNS_COOKIE"
 
-  # Sometimes FreeDNS does not return the subdomain page but rather 
+  # Sometimes FreeDNS does not return the subdomain page but rather
   # returns a page regarding becoming a premium member.  This usually
   # happens after a period of inactivity.  Immediately trying again
   # returns the correct subdomain page.  So, we will try twice to

+ 1 - 1
dnsapi/dns_ovh.sh

@@ -14,7 +14,7 @@
 #'ovh-eu'
 OVH_EU='https://eu.api.ovh.com/1.0'
 
-#'ovh-ca': 
+#'ovh-ca':
 OVH_CA='https://ca.api.ovh.com/1.0'
 
 #'kimsufi-eu'

+ 149 - 0
dnsapi/dns_vscale.sh

@@ -0,0 +1,149 @@
+#!/usr/bin/env sh
+
+#This is the vscale.io api wrapper for acme.sh
+#
+#Author: Alex Loban
+#Report Bugs here: https://github.com/LAV45/acme.sh
+
+#VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
+VSCALE_API_URL="https://api.vscale.io/v1"
+
+########  Public functions #####################
+
+#Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_vscale_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$VSCALE_API_KEY" ]; then
+    VSCALE_API_KEY=""
+    _err "You didn't specify the VSCALE api key yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  _saveaccountconf VSCALE_API_KEY "$VSCALE_API_KEY"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _vscale_tmpl_json="{\"type\":\"TXT\",\"name\":\"$_sub_domain.$_domain\",\"content\":\"$txtvalue\"}"
+
+  if _vscale_rest POST "domains/$_domain_id/records/" "$_vscale_tmpl_json"; then
+    response=$(printf "%s\n" "$response" | _egrep_o "{\"error\": \".+\"" | cut -d : -f 2)
+    if [ -z "$response" ]; then
+      _info "txt record updated success."
+      return 0
+    fi
+  fi
+
+  return 1
+}
+
+#fulldomain txtvalue
+dns_vscale_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  _vscale_rest GET "domains/$_domain_id/records/"
+
+  if [ -n "$response" ]; then
+    record_id=$(printf "%s\n" "$response" | _egrep_o "\"TXT\", \"id\": [0-9]+, \"name\": \"$_sub_domain.$_domain\"" | cut -d : -f 2 | tr -d ", \"name\"")
+    _debug record_id "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if _vscale_rest DELETE "domains/$_domain_id/records/$record_id" && [ -z "$response" ]; then
+      _info "txt record deleted success."
+      return 0
+    fi
+    _debug response "$response"
+    return 1
+  fi
+
+  return 1
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=12345
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+
+  if _vscale_rest GET "domains/"; then
+    response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')"
+    while true; do
+      h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+      _debug h "$h"
+      if [ -z "$h" ]; then
+        #not valid
+        return 1
+      fi
+
+      hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")"
+      if [ "$hostedzone" ]; then
+        _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+        if [ "$_domain_id" ]; then
+          _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+          _domain=$h
+          return 0
+        fi
+        return 1
+      fi
+      p=$i
+      i=$(_math "$i" + 1)
+    done
+  fi
+  return 1
+}
+
+#method uri qstr data
+_vscale_rest() {
+  mtd="$1"
+  ep="$2"
+  data="$3"
+
+  _debug mtd "$mtd"
+  _debug ep "$ep"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+  export _H3="X-Token: ${VSCALE_API_KEY}"
+
+  if [ "$mtd" != "GET" ]; then
+    # both POST and DELETE.
+    _debug data "$data"
+    response="$(_post "$data" "$VSCALE_API_URL/$ep" "" "$mtd")"
+  else
+    response="$(_get "$VSCALE_API_URL/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}