Browse Source

Merge branch 'master' of github.com:Neilpang/acme.sh

Ivar Larsson 7 years ago
parent
commit
50dee5d464
13 changed files with 253 additions and 86 deletions
  1. 6 2
      README.md
  2. 53 17
      acme.sh
  3. 0 6
      deploy/keychain.sh
  4. 19 3
      dnsapi/README.md
  5. 6 2
      dnsapi/dns_azure.sh
  6. 4 4
      dnsapi/dns_cf.sh
  7. 25 5
      dnsapi/dns_dgon.sh
  8. 18 35
      dnsapi/dns_dnsimple.sh
  9. 1 1
      dnsapi/dns_freedns.sh
  10. 1 1
      dnsapi/dns_he.sh
  11. 107 0
      dnsapi/dns_kinghost.sh
  12. 10 7
      dnsapi/dns_pdns.sh
  13. 3 3
      dnsapi/dns_yandex.sh

+ 6 - 2
README.md

@@ -37,6 +37,8 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 - [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)
+- [CentOS Web Panel](http://centos-webpanel.com/)
+- [lnmp.org](https://lnmp.org/)
 - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials)
 
 # Tested OS
@@ -313,9 +315,9 @@ You don't have to do anything manually!
 1. zonomi.com DNS API
 1. DreamHost.com API
 1. DirectAdmin API
+1. KingHost (https://www.kinghost.com.br/)
 1. Loopia.se API
 
-
 And: 
 
 **lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api
@@ -330,6 +332,8 @@ For more details: [How to use DNS API](dnsapi)
 
 # 8. Use DNS manual mode:
 
+See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first.
+
 If your dns provider doesn't support any api access, you can add the txt record by your hand.
 
 ```bash
@@ -399,7 +403,7 @@ Valid values are:
 It's simple, just give a wildcard domain as the `-d` parameter.
 
 ```sh
-acme.sh  --issue -d example.com  -d *.example.com  --dns dns_cf
+acme.sh  --issue -d example.com  -d '*.example.com'  --dns dns_cf
 ```
 
 

+ 53 - 17
acme.sh

@@ -110,10 +110,14 @@ _STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode"
 
 _DNS_ALIAS_WIKI="https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode"
 
+_DNS_MANUAL_WIKI="https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode"
+
 _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
 
 _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR"
 
+_DNS_MANUAL_ERROR="It seems that you are using dns manual mode. Read this link first: $_DNS_MANUAL_WIKI"
+
 __INTERACTIVE=""
 if [ -t 1 ]; then
   __INTERACTIVE="1"
@@ -1617,6 +1621,7 @@ _post() {
   _debug $httpmethod
   _debug "_post_url" "$_post_url"
   _debug2 "body" "$body"
+  _debug2 "_postContentType" "$_postContentType"
 
   _inithttp
 
@@ -1625,14 +1630,19 @@ _post() {
     if [ "$HTTPS_INSECURE" ]; then
       _CURL="$_CURL --insecure  "
     fi
-    if [ "$_postContentType" ]; then
-      _CURL="$_CURL -H \"Content-Type: $_postContentType\" "
-    fi
     _debug "_CURL" "$_CURL"
     if [ "$needbase64" ]; then
-      response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+      if [ "$_postContentType" ]; then
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+      else
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+      fi
     else
-      response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+      if [ "$_postContentType" ]; then
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+      else
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+      fi
     fi
     _ret="$?"
     if [ "$_ret" != "0" ]; then
@@ -1785,19 +1795,25 @@ _send_signed_request() {
     return 1
   fi
 
+  if [ "$ACME_VERSION" = "2" ]; then
+    __request_conent_type="$CONTENT_TYPE_JSON"
+  else
+    __request_conent_type=""
+  fi
   payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
   _debug3 payload64 "$payload64"
 
   MAX_REQUEST_RETRY_TIMES=5
   _request_retry_times=0
   while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
+    _request_retry_times=$(_math "$_request_retry_times" + 1)
     _debug3 _request_retry_times "$_request_retry_times"
     if [ -z "$_CACHED_NONCE" ]; then
       _headers=""
       if [ "$ACME_NEW_NONCE" ]; then
         _debug2 "Get nonce. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
         nonceurl="$ACME_NEW_NONCE"
-        if _post "" "$nonceurl" "" "HEAD" "$CONTENT_TYPE_JSON"; then
+        if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then
           _headers="$(cat "$HTTP_HEADER")"
         fi
       fi
@@ -1821,7 +1837,11 @@ _send_signed_request() {
     fi
     nonce="$_CACHED_NONCE"
     _debug2 nonce "$nonce"
-
+    if [ -z "$nonce" ]; then
+      _info "Could not get nonce, let's try again."
+      _sleep 2
+      continue
+    fi
     if [ "$ACME_VERSION" = "2" ]; then
       if [ "$url" = "$ACME_NEW_ACCOUNT" ] || [ "$url" = "$ACME_REVOKE_CERT" ]; then
         protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
@@ -1852,7 +1872,7 @@ _send_signed_request() {
     fi
     _debug3 body "$body"
 
-    response="$(_post "$body" "$url" "$needbase64" "POST" "$CONTENT_TYPE_JSON")"
+    response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")"
     _CACHED_NONCE=""
 
     if [ "$?" != "0" ]; then
@@ -1879,7 +1899,6 @@ _send_signed_request() {
 
     if _contains "$_body" "JWS has invalid anti-replay nonce"; then
       _info "It seems the CA server is busy now, let's wait and retry."
-      _request_retry_times=$(_math "$_request_retry_times" + 1)
       _sleep 5
       continue
     fi
@@ -3247,10 +3266,16 @@ _regAccount() {
     return 1
   fi
 
+  _debug2 responseHeaders "$responseHeaders"
   _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
   _debug "_accUri" "$_accUri"
+  if [ -z "$_accUri" ]; then
+    _err "Can not find account id url."
+    _err "$responseHeaders"
+    return 1
+  fi
   _savecaconf "ACCOUNT_URL" "$_accUri"
-  export ACCOUNT_URL="$ACCOUNT_URL"
+  export ACCOUNT_URL="$_accUri"
 
   CA_KEY_HASH="$(__calcAccountKeyHash)"
   _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
@@ -3460,6 +3485,11 @@ issue() {
     mkdir -p "$DOMAIN_PATH"
   fi
 
+  if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
+    _err "$_DNS_MANUAL_ERROR"
+    return 1
+  fi
+
   _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY"
 
   _initAPI
@@ -3521,7 +3551,7 @@ issue() {
   _saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")"
   _debug2 _saved_account_key_hash "$_saved_account_key_hash"
 
-  if [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
+  if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
     if ! _regAccount "$_accountkeylength"; then
       _on_issue_err "$_post_hook"
       return 1
@@ -3819,7 +3849,7 @@ $_authorizations_map"
     if [ "$dnsadded" = '0' ]; then
       _savedomainconf "Le_Vlist" "$vlist"
       _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
-      _err "Please add the TXT records to the domains, and retry again."
+      _err "Please add the TXT records to the domains, and re-run with --renew."
       _clearup
       _on_issue_err "$_post_hook"
       return 1
@@ -4083,13 +4113,15 @@ $_authorizations_map"
     fi
     if [ "$code" != "200" ]; then
       _err "Sign failed, code is not 200."
+      _err "$response"
       _on_issue_err "$_post_hook"
       return 1
     fi
     Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
 
     if ! _get "$Le_LinkCert" >"$CERT_PATH"; then
-      _err "Sign failed, code is not 200."
+      _err "Sign failed, can not download cert:$Le_LinkCert."
+      _err "$response"
       _on_issue_err "$_post_hook"
       return 1
     fi
@@ -4105,12 +4137,12 @@ $_authorizations_map"
     fi
   else
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
-      _err "Sign failed."
+      _err "Sign failed. $response"
       _on_issue_err "$_post_hook"
       return 1
     fi
     _rcert="$response"
-    Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+    Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
     echo "$BEGIN_CERT" >"$CERT_PATH"
 
     #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
@@ -5456,8 +5488,8 @@ Parameters:
   --cert-home                       Specifies the home dir to save all the certs, only valid for '--install' command.
   --config-home                     Specifies the home dir to save all the configurations.
   --useragent                       Specifies the user agent string. it will be saved for future use too.
-  --accountemail                    Specifies the account email for registering, Only valid for the '--install' command.
-  --accountkey                      Specifies the account key path, Only valid for the '--install' command.
+  --accountemail                    Specifies the account email, only valid for the '--install' and '--update-account' command.
+  --accountkey                      Specifies the account key path, only valid for the '--install' command.
   --days                            Specifies the days to renew the cert when using '--issue' command. The max value is $MAX_RENEW days.
   --httpport                        Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
   --local-address                   Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
@@ -5481,6 +5513,7 @@ Parameters:
   --listen-v6                       Force standalone/tls server to listen at ipv6.
   --openssl-bin                     Specifies a custom openssl bin location.
   --use-wget                        Force to use wget, if you have both curl and wget installed.
+  --yes-I-know-dns-manual-mode-enough-go-ahead-please  Force to use dns manual mode: $_DNS_MANUAL_WIKI
   "
 }
 
@@ -5969,6 +6002,9 @@ _process() {
           shift
         fi
         ;;
+      --yes-I-know-dns-manual-mode-enough-go-ahead-please)
+        export FORCE_DNS_MANUAL=1
+        ;;
       --log | --logfile)
         _log="1"
         _logfile="$2"

+ 0 - 6
deploy/keychain.sh

@@ -1,11 +1,5 @@
 #!/usr/bin/env sh
 
-#Here is a sample custom api script.
-#This file name is "myapi.sh"
-#So, here must be a method   myapi_deploy()
-#Which will be called by acme.sh to deploy the cert
-#returns 0 means success, otherwise error.
-
 ########  Public functions #####################
 
 #domain keyfile certfile cafile fullchain

+ 19 - 3
dnsapi/README.md

@@ -325,6 +325,8 @@ The `CY_Username`, `CY_Password` and `CY_OTP_Secret` will be saved in `~/.acme.s
 
 ## 17. Use Domain-Offensive/Resellerinterface/Domainrobot API
 
+ATTENTION: You need to be a registered Reseller to be able to use the ResellerInterface. As a normal user you can not use this method.
+
 You will need your login credentials (Partner ID+Password) to the Resellerinterface, and export them before you run `acme.sh`:
 ```
 export DO_PID="KD-1234567"
@@ -525,8 +527,9 @@ For issues, please report to https://github.com/raidenii/acme.sh/issues.
 
 ## 28. Use Name.com API
 
-You'll need to fill out the form at https://www.name.com/reseller/apply to apply
-for API username and token.
+Create your API token here: https://www.name.com/account/settings/api
+
+Note: `Namecom_Username` should be your Name.com username and not the token name.  If you accidentally run the script with the token name as the username see `~/.acme.sh/account.conf` to fix the issue
 
 ```
 export Namecom_Username="testuser"
@@ -784,7 +787,19 @@ acme.sh --issue --dns dns_da -d example.com -d www.example.com
 
 The `DA_Api` and `DA_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
-## 42. Use Loopia.se API
+## 42. Use KingHost DNS API
+
+API access must be enabled at https://painel.kinghost.com.br/painel.api.php
+
+```
+export KINGHOST_Username="yourusername"
+export KINGHOST_Password="yourpassword"
+acme.sh --issue --dns dns_kinghost -d example.com -d *.example.com
+```
+
+The `KINGHOST_username` and `KINGHOST_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 43. Use Loopia.se API
 User must provide login credentials to the Loopia API.
 The user needs the following permissions:
 
@@ -806,6 +821,7 @@ acme.sh --issue --dns dns_loopia -d example.com -d *.example.com
 
 The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
+=======
 
 # Use custom API
 

+ 6 - 2
dnsapi/dns_azure.sh

@@ -99,6 +99,7 @@ dns_azure_add() {
   _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
   if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
     _info "validation value added"
+    return 0
   else
     _err "error adding validation value ($_code)"
     return 1
@@ -194,6 +195,7 @@ dns_azure_rm() {
       _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
       if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
         _info "validation value removed"
+        return 0
       else
         _err "error removing validation value ($_code)"
         return 1
@@ -226,6 +228,7 @@ _azure_rest() {
     else
       response="$(_get "$ep")"
     fi
+    _ret="$?"
     _secure_debug2 "response $response"
     _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
     _debug "http response code $_code"
@@ -236,7 +239,7 @@ _azure_rest() {
       return 1
     fi
     # See https://docs.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes
-    if [ "$?" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then
+    if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then
       _request_retry_times="$(_math "$_request_retry_times" + 1)"
       _info "REST call error $_code retrying $ep in $_request_retry_times s"
       _sleep "$_request_retry_times"
@@ -281,6 +284,7 @@ _azure_getaccess_token() {
   body="resource=$(printf "%s" 'https://management.core.windows.net/' | _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret" | _url_encode)&grant_type=client_credentials"
   _secure_debug2 "data $body"
   response="$(_post "$body" "https://login.microsoftonline.com/$tenantID/oauth2/token" "" "POST")"
+  _ret="$?"
   _secure_debug2 "response $response"
   response="$(echo "$response" | _normalizeJson)"
   accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
@@ -290,7 +294,7 @@ _azure_getaccess_token() {
     _err "no acccess token received. Check your Azure settings see $WIKI"
     return 1
   fi
-  if [ "$?" != "0" ]; then
+  if [ "$_ret" != "0" ]; then
     _err "error $response"
     return 1
   fi

+ 4 - 4
dnsapi/dns_cf.sh

@@ -19,8 +19,8 @@ dns_cf_add() {
   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."
+    _err "You didn't specify a cloudflare api key and email yet."
+    _err "Please create the key and try again."
     return 1
   fi
 
@@ -94,8 +94,8 @@ dns_cf_rm() {
   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."
+    _err "You didn't specify a cloudflare api key and email yet."
+    _err "Please create the key and try again."
     return 1
   fi
 

+ 25 - 5
dnsapi/dns_dgon.sh

@@ -20,12 +20,22 @@
 dns_dgon_add() {
   fulldomain="$(echo "$1" | _lower_case)"
   txtvalue=$2
+
+  DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}"
+  # Check if API Key Exist
+  if [ -z "$DO_API_KEY" ]; then
+    DO_API_KEY=""
+    _err "You did not specify DigitalOcean API key."
+    _err "Please export DO_API_KEY and try again."
+    return 1
+  fi
+
   _info "Using digitalocean dns validation - add record"
   _debug fulldomain "$fulldomain"
   _debug txtvalue "$txtvalue"
 
   ## save the env vars (key and domain split location) for later automated use
-  _saveaccountconf DO_API_KEY "$DO_API_KEY"
+  _saveaccountconf_mutable DO_API_KEY "$DO_API_KEY"
 
   ## split the domain for DO API
   if ! _get_base_domain "$fulldomain"; then
@@ -39,7 +49,7 @@ dns_dgon_add() {
   export _H1="Content-Type: application/json"
   export _H2="Authorization: Bearer $DO_API_KEY"
   PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records'
-  PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'"}'
+  PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'","ttl":120}'
 
   _debug PURL "$PURL"
   _debug PBODY "$PBODY"
@@ -65,6 +75,16 @@ dns_dgon_add() {
 dns_dgon_rm() {
   fulldomain="$(echo "$1" | _lower_case)"
   txtvalue=$2
+
+  DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}"
+  # Check if API Key Exist
+  if [ -z "$DO_API_KEY" ]; then
+    DO_API_KEY=""
+    _err "You did not specify DigitalOcean API key."
+    _err "Please export DO_API_KEY and try again."
+    return 1
+  fi
+
   _info "Using digitalocean dns validation - remove record"
   _debug fulldomain "$fulldomain"
   _debug txtvalue "$txtvalue"
@@ -92,11 +112,11 @@ dns_dgon_rm() {
     domain_list="$(_get "$GURL")"
     ## 2) find record
     ## check for what we are looing for: "type":"A","name":"$_sub_domain"
-    record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*\d+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")"
+    record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")"
     ## 3) check record and get next page
     if [ -z "$record" ]; then
       ## find the next page if we dont have a match
-      nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=\d+")"
+      nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")"
       if [ -z "$nextpage" ]; then
         _err "no record and no nextpage in digital ocean DNS removal"
         return 1
@@ -108,7 +128,7 @@ dns_dgon_rm() {
   done
 
   ## we found the record
-  rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*\d+" | _egrep_o "\d+")"
+  rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
   _debug rec_id "$rec_id"
 
   ## delete the record

+ 18 - 35
dnsapi/dns_dnsimple.sh

@@ -39,34 +39,17 @@ dns_dnsimple_add() {
 
   _get_records "$_account_id" "$_domain" "$_sub_domain"
 
-  if [ "$_records_count" = "0" ]; then
-    _info "Adding record"
-    if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
-      if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then
-        _info "Added"
-        return 0
-      else
-        _err "Unexpected response while adding text record."
-        return 1
-      fi
-    fi
-    _err "Add txt record error."
-  else
-    _info "Updating record"
-    _extract_record_id "$_records" "$_sub_domain"
-
-    if _dnsimple_rest \
-      PATCH \
-      "$_account_id/zones/$_domain/records/$_record_id" \
-      "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
-
-      _info "Updated!"
+  _info "Adding record"
+  if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+    if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then
+      _info "Added"
       return 0
+    else
+      _err "Unexpected response while adding text record."
+      return 1
     fi
-
-    _err "Update error"
-    return 1
   fi
+  _err "Add txt record error."
 }
 
 # fulldomain
@@ -84,19 +67,19 @@ dns_dnsimple_rm() {
   fi
 
   _get_records "$_account_id" "$_domain" "$_sub_domain"
-  _extract_record_id "$_records" "$_sub_domain"
 
+  _extract_record_id "$_records" "$_sub_domain"
   if [ "$_record_id" ]; then
-
-    if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then
-      _info "removed record" "$_record_id"
-      return 0
-    fi
+    echo "$_record_id" | while read -r item; do
+      if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$item"; then
+        _info "removed record" "$item"
+        return 0
+      else
+        _err "failed to remove record" "$item"
+        return 1
+      fi
+    done
   fi
-
-  _err "failed to remove record" "$_record_id"
-  return 1
-
 }
 
 ####################  Private functions bellow ##################################

+ 1 - 1
dnsapi/dns_freedns.sh

@@ -279,7 +279,7 @@ _freedns_add_txt_record() {
   domain_id="$2"
   subdomain="$3"
   value="$(printf '%s' "$4" | _url_encode)"
-  url="http://freedns.afraid.org/subdomain/save.php?step=2"
+  url="https://freedns.afraid.org/subdomain/save.php?step=2"
 
   htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")"
 

+ 1 - 1
dnsapi/dns_he.sh

@@ -143,7 +143,7 @@ _find_zone() {
 
     _debug "Looking for zone \"${_attempted_zone}\""
 
-    line_num="$(echo "$_zone_names" | grep -n "$_attempted_zone" | cut -d : -f 1)"
+    line_num="$(echo "$_zone_names" | grep -n "^$_attempted_zone" | cut -d : -f 1)"
 
     if [ "$line_num" ]; then
       _zone_id=$(echo "$_zone_ids" | sed -n "${line_num}p")

+ 107 - 0
dnsapi/dns_kinghost.sh

@@ -0,0 +1,107 @@
+#!/usr/bin/env sh
+
+############################################################
+# KingHost API support                                     #
+# http://api.kinghost.net/doc/                             #
+#                                                          #
+# Author: Felipe Keller Braz <felipebraz@kinghost.com.br>  #
+# Report Bugs here: https://github.com/kinghost/acme.sh    #
+#                                                          #
+# Values to export:                                        #
+# export KINGHOST_Username="email@provider.com"            #
+# export KINGHOST_Password="xxxxxxxxxx"                    #
+############################################################
+
+KING_Api="https://api.kinghost.net/acme"
+
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+dns_kinghost_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}"
+  KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}"
+  if [ -z "$KINGHOST_Username" ] || [ -z "$KINGHOST_Password" ]; then
+    KINGHOST_Username=""
+    KINGHOST_Password=""
+    _err "You don't specify KingHost api password and email yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the credentials to the account conf file.
+  _saveaccountconf_mutable KINGHOST_Username "$KINGHOST_Username"
+  _saveaccountconf_mutable KINGHOST_Password "$KINGHOST_Password"
+
+  _debug "Getting txt records"
+  _kinghost_rest GET "dns" "name=$fulldomain&content=$txtvalue"
+
+  #This API call returns "status":"ok" if dns record does not exists
+  #We are creating a new txt record here, so we expect the "ok" status
+  if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
+    _err "Error"
+    _err "$response"
+    return 1
+  fi
+
+  _kinghost_rest POST "dns" "name=$fulldomain&content=$txtvalue"
+  if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
+    _err "Error"
+    _err "$response"
+    return 1
+  fi
+
+  return 0
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_kinghost_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}"
+  KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}"
+  if [ -z "$KINGHOST_Password" ] || [ -z "$KINGHOST_Username" ]; then
+    KINGHOST_Password=""
+    KINGHOST_Username=""
+    _err "You don't specify KingHost api key and email yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  _kinghost_rest DELETE "dns" "name=$fulldomain&content=$txtvalue"
+  if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
+    _err "Error"
+    _err "$response"
+    return 1
+  fi
+
+  return 0
+}
+
+####################  Private functions below ##################################
+_kinghost_rest() {
+  method=$1
+  uri="$2"
+  data="$3"
+  _debug "$uri"
+
+  export _H1="X-Auth-Email: $KINGHOST_Username"
+  export _H2="X-Auth-Key: $KINGHOST_Password"
+
+  if [ "$method" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$KING_Api/$uri.json" "" "$method")"
+  else
+    response="$(_get "$KING_Api/$uri.json?$data")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $uri"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 10 - 7
dnsapi/dns_pdns.sh

@@ -90,7 +90,7 @@ set_record() {
   full=$2
   txtvalue=$3
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then
+  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then
     _err "Set txt record error."
     return 1
   fi
@@ -107,7 +107,7 @@ rm_record() {
   root=$1
   full=$2
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
+  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
     _err "Delete txt record error."
     return 1
   fi
@@ -122,7 +122,7 @@ rm_record() {
 notify_slaves() {
   root=$1
 
-  if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root./notify"; then
+  if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root/notify"; then
     _err "Notify slaves error."
     return 1
   fi
@@ -144,15 +144,18 @@ _get_root() {
 
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
-    if [ -z "$h" ]; then
-      return 1
-    fi
 
     if _contains "$_zones_response" "\"name\": \"$h.\""; then
-      _domain="$h"
+      _domain="$h."
+      if [ -z "$h" ]; then
+        _domain="=2E"
+      fi
       return 0
     fi
 
+    if [ -z "$h" ]; then
+      return 1
+    fi
     i=$(_math $i + 1)
   done
   _debug "$domain not found"

+ 3 - 3
dnsapi/dns_yandex.sh

@@ -50,9 +50,9 @@ _PDD_get_domain() {
   __last=0
   while [ $__last -eq 0 ]; do
     uri1="https://pddimp.yandex.ru/api2/admin/domain/domains?page=${__page}&on_page=20"
-    res1=$(_get "$uri1" | _normalizeJson)
-    #_debug "$res1"
-    __found=$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')
+    res1="$(_get "$uri1" | _normalizeJson)"
+    _debug2 "res1" "$res1"
+    __found="$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')"
     _debug "found: $__found results on page"
     if [ "$__found" -lt 20 ]; then
       _debug "last page: $__page"