Browse Source

Merge pull request #1817 from Neilpang/dev

Dev
neil 6 years ago
parent
commit
c31db83b26
15 changed files with 1156 additions and 67 deletions
  1. 5 0
      README.md
  2. 1 0
      acme.sh
  3. 21 0
      deploy/README.md
  4. 80 0
      deploy/gitlab.sh
  5. 3 3
      deploy/ssh.sh
  6. 94 1
      dnsapi/README.md
  7. 7 7
      dnsapi/dns_aws.sh
  8. 253 0
      dnsapi/dns_conoha.sh
  9. 161 0
      dnsapi/dns_dpi.sh
  10. 167 0
      dnsapi/dns_gcloud.sh
  11. 168 0
      dnsapi/dns_gdnsdk.sh
  12. 1 2
      dnsapi/dns_inwx.sh
  13. 40 15
      dnsapi/dns_lexicon.sh
  14. 133 0
      dnsapi/dns_netcup.sh
  15. 22 39
      dnsapi/dns_unoeuro.sh

+ 5 - 0
README.md

@@ -321,6 +321,11 @@ You don't have to do anything manually!
 1. acme-dns (https://github.com/joohoi/acme-dns)
 1. TELE3 (https://www.tele3.cz)
 1. EUSERV.EU (https://www.euserv.eu)
+1. DNSPod.com API (https://www.dnspod.com)
+1. Google Cloud DNS API
+1. ConoHa (https://www.conoha.jp)
+1. netcup DNS API (https://www.netcup.de)
+1. GratisDNS.dk (https://gratisdns.dk)
 
 And: 
 

+ 1 - 0
acme.sh

@@ -1327,6 +1327,7 @@ createDomainKey() {
     if _createkey "$_cdl" "$CERT_KEY_PATH"; then
       _savedomainconf Le_Keylength "$_cdl"
       _info "The domain key is here: $(__green $CERT_KEY_PATH)"
+      return 0
     fi
   else
     if [ "$IS_RENEW" ]; then

+ 21 - 0
deploy/README.md

@@ -275,3 +275,24 @@ acme.sh --deploy -d haproxy.example.com --deploy-hook haproxy
 ```
 
 The path for the PEM file will be stored with the domain configuration and will be available when renewing, so that deploy will happen automatically when renewed.
+
+## 11. Deploy your cert to Gitlab pages
+
+You must define the API key and the informations for the project and Gitlab page you are updating the certificate for.
+
+```sh
+# The token can be created in your user settings under "Access Tokens"
+export GITLAB_TOKEN="xxxxxxxxxxx"
+
+# The project ID is displayed on the home page of the project
+export GITLAB_PROJECT_ID=12345678
+
+# The domain must match the one defined for the Gitlab page, without "https://"
+export GITLAB_DOMAIN="www.mydomain.com"
+```
+
+You can then deploy the certificate as follows
+
+```sh
+acme.sh --deploy -d www.mydomain.com --deploy-hook gitlab
+```

+ 80 - 0
deploy/gitlab.sh

@@ -0,0 +1,80 @@
+#!/usr/bin/env sh
+
+# Script to deploy certificate to a Gitlab hosted page
+
+# The following variables exported from environment will be used.
+# If not set then values previously saved in domain.conf file are used.
+
+# All the variables are required
+
+# export GITLAB_TOKEN="xxxxxxx"
+# export GITLAB_PROJECT_ID=012345
+# export GITLAB_DOMAIN="mydomain.com"
+
+gitlab_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  if [ -z "$GITLAB_TOKEN" ]; then
+    if [ -z "$Le_Deploy_gitlab_token" ]; then
+      _err "GITLAB_TOKEN not defined."
+      return 1
+    fi
+  else
+    Le_Deploy_gitlab_token="$GITLAB_TOKEN"
+    _savedomainconf Le_Deploy_gitlab_token "$Le_Deploy_gitlab_token"
+  fi
+
+  if [ -z "$GITLAB_PROJECT_ID" ]; then
+    if [ -z "$Le_Deploy_gitlab_project_id" ]; then
+      _err "GITLAB_PROJECT_ID not defined."
+      return 1
+    fi
+  else
+    Le_Deploy_gitlab_project_id="$GITLAB_PROJECT_ID"
+    _savedomainconf Le_Deploy_gitlab_project_id "$Le_Deploy_gitlab_project_id"
+  fi
+
+  if [ -z "$GITLAB_DOMAIN" ]; then
+    if [ -z "$Le_Deploy_gitlab_domain" ]; then
+      _err "GITLAB_DOMAIN not defined."
+      return 1
+    fi
+  else
+    Le_Deploy_gitlab_domain="$GITLAB_DOMAIN"
+    _savedomainconf Le_Deploy_gitlab_domain "$Le_Deploy_gitlab_domain"
+  fi
+
+  string_fullchain=$(_url_encode <"$_cfullchain")
+  string_key=$(_url_encode <"$_ckey")
+
+  body="certificate=$string_fullchain&key=$string_key"
+
+  export _H1="PRIVATE-TOKEN: $Le_Deploy_gitlab_token"
+
+  gitlab_url="https://gitlab.com/api/v4/projects/$Le_Deploy_gitlab_project_id/pages/domains/$Le_Deploy_gitlab_domain"
+
+  _response=$(_post "$body" "$gitlab_url" 0 PUT | _dbase64 "multiline")
+
+  error_response="error"
+
+  if test "${_response#*$error_response}" != "$_response"; then
+    _err "Error in deploying certificate:"
+    _err "$_response"
+    return 1
+  fi
+
+  _debug response "$_response"
+  _info "Certificate successfully deployed"
+
+  return 0
+}

+ 3 - 3
deploy/ssh.sh

@@ -11,7 +11,7 @@
 #
 # Only a username is required.  All others are optional.
 #
-# The following examples are for QNAP NAS running QTS 4.2 
+# The following examples are for QNAP NAS running QTS 4.2
 # export DEPLOY_SSH_CMD=""  # defaults to ssh
 # export DEPLOY_SSH_USER="admin"  # required
 # export DEPLOY_SSH_SERVER="qnap"  # defaults to domain name
@@ -101,7 +101,7 @@ ssh_deploy() {
   fi
 
   # CERTFILE is optional.
-  # If provided then private key will be copied or appended to provided filename.
+  # If provided then certificate will be copied or appended to provided filename.
   if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
     Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
     _savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
@@ -190,7 +190,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
     _info "Backup directories erased after 180 days."
   fi
 
-  _debug "Remote commands to execute: $_cmdstr"
+  _secure_debug "Remote commands to execute: " "$_cmdstr"
   _info "Submitting sequence of commands to remote server by ssh"
   # quotations in bash cmd below intended.  Squash travis spellcheck error
   # shellcheck disable=SC2029

+ 94 - 1
dnsapi/README.md

@@ -876,6 +876,7 @@ acme.sh --issue --dns dns_tele3 -d example.com -d *.example.com
 ```
 
 The TELE3_Key and TELE3_Secret will be saved in ~/.acme.sh/account.conf and will be reused when needed.
+
 ## 47. Use Euserv.eu API
 
 First you need to login to your euserv.eu account and activate your API Administration (API Verwaltung).
@@ -897,6 +898,98 @@ acme.sh --issue --dns dns_euserv -d example.com -d *.example.com --insecure
 The `EUSERV_Username` and `EUSERV_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 Please report any issues to https://github.com/initit/acme.sh or to <github@initit.de>
+
+## 48. Use DNSPod.com domain API to automatically issue cert
+
+First you need to get your API Key and ID by this [get-the-user-token](https://www.dnspod.com/docs/info.html#get-the-user-token).
+
+```
+export DPI_Id="1234"
+export DPI_Key="sADDsdasdgdsf"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_dpi -d example.com -d www.example.com
+```
+
+The `DPI_Id` and `DPI_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 49. Use Google Cloud DNS API to automatically issue cert
+
+First you need to authenticate to gcloud.
+
+```
+gcloud init
+```
+
+**The `dns_gcloud` script uses the active gcloud configuration and credentials.**
+There is no logic inside `dns_gcloud` to override the project and other settings.
+If needed, create additional [gcloud configurations](https://cloud.google.com/sdk/gcloud/reference/topic/configurations).
+You can change the configuration being used without *activating* it; simply set the `CLOUDSDK_ACTIVE_CONFIG_NAME` environment variable.
+
+To issue a certificate you can:
+```
+export CLOUDSDK_ACTIVE_CONFIG_NAME=default  # see the note above
+acme.sh --issue --dns dns_gcloud -d example.com -d '*.example.com'
+```
+
+`dns_gcloud` also supports [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode).
+
+## 50. Use ConoHa API
+
+First you need to login to your ConoHa account to get your API credentials.
+
+```
+export CONOHA_Username="xxxxxx"
+export CONOHA_Password="xxxxxx"
+export CONOHA_TenantId="xxxxxx"
+export CONOHA_IdentityServiceApi="https://identity.xxxx.conoha.io/v2.0"
+```
+
+To issue a cert:
+```
+acme.sh --issue --dns dns_conoha -d example.com -d www.example.com
+```
+
+The `CONOHA_Username`, `CONOHA_Password`, `CONOHA_TenantId` and `CONOHA_IdentityServiceApi` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 51. Use netcup DNS API to automatically issue cert
+
+First you need to login in your CCP account to get your API Key and API Password.
+```
+export NC_Apikey="<Apikey>"
+export NC_Apipw="<Apipassword>"
+export NC_CID="<Customernumber>"
+```
+
+Now, let's issue a cert:
+```
+acme.sh --issue --dns dns_netcup -d example.com -d www.example.com
+```
+
+The `NC_Apikey`,`NC_Apipw` and `NC_CID` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 52. Use GratisDNS.dk
+
+GratisDNS.dk (https://gratisdns.dj/) does not provide an API to update DNS records (other than IPv4 and IPv6
+dynamic DNS addresses).  The acme.sh plugin therefore retrieves and updates domain TXT records by logging
+into the GratisDNS website to read the HTML and posting updates as HTTP.  The plugin needs to know your
+userid and password for the GratisDNS website.
+
+```sh
+export GDNSDK_Username="..."
+export GDNSDK_Password="..."
+```
+The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+
+Now you can issue a certificate.
+
+```sh
+acme.sh --issue --dns dns_gdnsdk -d example.com -d *.example.com
+```
+
 # Use custom API
 
 If your API is not supported yet, you can write your own DNS API.
@@ -917,4 +1010,4 @@ See:  https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
 
 # Use lexicon DNS API
 
-https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api
+https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api

+ 7 - 7
dnsapi/dns_aws.sh

@@ -29,7 +29,7 @@ dns_aws_add() {
   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
     AWS_ACCESS_KEY_ID=""
     AWS_SECRET_ACCESS_KEY=""
-    _err "You don't specify aws route53 api key id and and api key secret yet."
+    _err "You haven't specifed the aws route53 api key id and and api key secret yet."
     _err "Please create your key and try again. see $(__green $AWS_WIKI)"
     return 1
   fi
@@ -62,7 +62,7 @@ dns_aws_add() {
   fi
 
   if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then
-    _info "The txt record already exists, skip"
+    _info "The TXT record already exists. Skipping."
     return 0
   fi
 
@@ -71,7 +71,7 @@ dns_aws_add() {
   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords>$_resource_record<ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
 
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
-    _info "txt record updated success."
+    _info "TXT record updated successfully."
     return 0
   fi
 
@@ -99,7 +99,7 @@ dns_aws_rm() {
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
 
-  _info "Geting existing records for $fulldomain"
+  _info "Getting existing records for $fulldomain"
   if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
     return 1
   fi
@@ -108,14 +108,14 @@ dns_aws_rm() {
     _resource_record="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep "<Name>$fulldomain.</Name>" | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##")"
     _debug "_resource_record" "$_resource_record"
   else
-    _debug "no records exists, skip"
+    _debug "no records exist, skip"
     return 0
   fi
 
   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords>$_resource_record</ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
 
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
-    _info "txt record deleted success."
+    _info "TXT record deleted successfully."
     return 0
   fi
 
@@ -163,7 +163,7 @@ _get_root() {
             _domain=$h
             return 0
           fi
-          _err "Can not find domain id: $h"
+          _err "Can't find domain with id: $h"
           return 1
         fi
       fi

+ 253 - 0
dnsapi/dns_conoha.sh

@@ -0,0 +1,253 @@
+#!/usr/bin/env sh
+
+CONOHA_DNS_EP_PREFIX_REGEXP="https://dns-service\."
+
+########  Public functions #####################
+
+#Usage: dns_conoha_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_conoha_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using conoha"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _debug "Check uesrname and password"
+  CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}"
+  CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}"
+  CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}"
+  CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}"
+  if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then
+    CONOHA_Username=""
+    CONOHA_Password=""
+    CONOHA_TenantId=""
+    CONOHA_IdentityServiceApi=""
+    _err "You didn't specify a conoha api username and password yet."
+    _err "Please create the user and try again."
+    return 1
+  fi
+
+  _saveaccountconf_mutable CONOHA_Username "$CONOHA_Username"
+  _saveaccountconf_mutable CONOHA_Password "$CONOHA_Password"
+  _saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId"
+  _saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi"
+
+  if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then
+    accesstoken="$(printf "%s" "$token" | sed -n 1p)"
+    CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)"
+  else
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _info "Adding record"
+  body="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"data\":\"$txtvalue\",\"ttl\":60}"
+  if _conoha_rest POST "$CONOHA_Api/v1/domains/$_domain_id/records" "$body" "$accesstoken"; then
+    if _contains "$response" '"data":"'"$txtvalue"'"'; then
+      _info "Added, OK"
+      return 0
+    else
+      _err "Add txt record error."
+      return 1
+    fi
+  fi
+
+  _err "Add txt record error."
+  return 1
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_conoha_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using conoha"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _debug "Check uesrname and password"
+  CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}"
+  CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}"
+  CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}"
+  CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}"
+  if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then
+    CONOHA_Username=""
+    CONOHA_Password=""
+    CONOHA_TenantId=""
+    CONOHA_IdentityServiceApi=""
+    _err "You didn't specify a conoha api username and password yet."
+    _err "Please create the user and try again."
+    return 1
+  fi
+
+  _saveaccountconf_mutable CONOHA_Username "$CONOHA_Username"
+  _saveaccountconf_mutable CONOHA_Password "$CONOHA_Password"
+  _saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId"
+  _saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi"
+
+  if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then
+    accesstoken="$(printf "%s" "$token" | sed -n 1p)"
+    CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)"
+  else
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  if ! _conoha_rest GET "$CONOHA_Api/v1/domains/$_domain_id/records" "" "$accesstoken"; then
+    _err "Error"
+    return 1
+  fi
+
+  record_id=$(printf "%s" "$response" | _egrep_o '{[^}]*}' \
+    | grep '"type":"TXT"' | grep "\"data\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" \
+    | _head_n 1 | cut -d : -f 2 | tr -d \")
+  if [ -z "$record_id" ]; then
+    _err "Can not get record id to remove."
+    return 1
+  fi
+  _debug record_id "$record_id"
+
+  _info "Removing the txt record"
+  if ! _conoha_rest DELETE "$CONOHA_Api/v1/domains/$_domain_id/records/$record_id" "" "$accesstoken"; then
+    _err "Delete record error."
+    return 1
+  fi
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+_conoha_rest() {
+  m="$1"
+  ep="$2"
+  data="$3"
+  accesstoken="$4"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+  if [ -n "$accesstoken" ]; then
+    export _H3="X-Auth-Token: $accesstoken"
+  fi
+
+  _debug "$ep"
+  if [ "$m" != "GET" ]; then
+    _secure_debug2 data "$data"
+    response="$(_post "$data" "$ep" "" "$m")"
+  else
+    response="$(_get "$ep")"
+  fi
+  _ret="$?"
+  _secure_debug2 response "$response"
+  if [ "$_ret" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+
+  response="$(printf "%s" "$response" | _normalizeJson)"
+  return 0
+}
+
+_conoha_get_accesstoken() {
+  ep="$1"
+  username="$2"
+  password="$3"
+  tenantId="$4"
+
+  accesstoken="$(_readaccountconf_mutable conoha_accesstoken)"
+  expires="$(_readaccountconf_mutable conoha_tokenvalidto)"
+  CONOHA_Api="$(_readaccountconf_mutable conoha_dns_ep)"
+
+  # can we reuse the access token?
+  if [ -n "$accesstoken" ] && [ -n "$expires" ] && [ -n "$CONOHA_Api" ]; then
+    utc_date="$(_utc_date | sed "s/ /T/")"
+    if expr "$utc_date" "<" "$expires" >/dev/null; then
+      # access token is still valid - reuse it
+      _debug "reusing access token"
+      printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api"
+      return 0
+    else
+      _debug "access token expired"
+    fi
+  fi
+  _debug "getting new access token"
+
+  body="$(printf '{"auth":{"passwordCredentials":{"username":"%s","password":"%s"},"tenantId":"%s"}}' "$username" "$password" "$tenantId")"
+  if ! _conoha_rest POST "$ep" "$body" ""; then
+    _err error "$response"
+    return 1
+  fi
+  accesstoken=$(printf "%s" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
+  expires=$(printf "%s" "$response" | _egrep_o "\"expires\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2-4 | tr -d \" | tr -d Z) #expect UTC
+  if [ -z "$accesstoken" ] || [ -z "$expires" ]; then
+    _err "no acccess token received. Check your Conoha settings see $WIKI"
+    return 1
+  fi
+  _saveaccountconf_mutable conoha_accesstoken "$accesstoken"
+  _saveaccountconf_mutable conoha_tokenvalidto "$expires"
+
+  CONOHA_Api=$(printf "%s" "$response" | _egrep_o 'publicURL":"'"$CONOHA_DNS_EP_PREFIX_REGEXP"'[^"]*"' | _head_n 1 | cut -d : -f 2-3 | tr -d \")
+  if [ -z "$CONOHA_Api" ]; then
+    _err "failed to get conoha dns endpoint url"
+    return 1
+  fi
+  _saveaccountconf_mutable conoha_dns_ep "$CONOHA_Api"
+
+  printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api"
+  return 0
+}
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain="$1"
+  ep="$2"
+  accesstoken="$3"
+  i=2
+  p=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100).
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if ! _conoha_rest GET "$ep/v1/domains?name=$h" "" "$accesstoken"; then
+      return 1
+    fi
+
+    if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | 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
+  return 1
+}

+ 161 - 0
dnsapi/dns_dpi.sh

@@ -0,0 +1,161 @@
+#!/usr/bin/env sh
+
+# Dnspod.com Domain api
+#
+#DPI_Id="1234"
+#
+#DPI_Key="sADDsdasdgdsf"
+
+REST_API="https://api.dnspod.com"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dpi_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}"
+  DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}"
+  if [ -z "$DPI_Id" ] || [ -z "$DPI_Key" ]; then
+    DPI_Id=""
+    DPI_Key=""
+    _err "You don't specify dnspod api key and key id yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable DPI_Id "$DPI_Id"
+  _saveaccountconf_mutable DPI_Key "$DPI_Key"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  add_record "$_domain" "$_sub_domain" "$txtvalue"
+
+}
+
+#fulldomain txtvalue
+dns_dpi_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}"
+  DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  if ! _rest POST "Record.List" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then
+    _err "Record.Lis error."
+    return 1
+  fi
+
+  if _contains "$response" 'No records'; then
+    _info "Don't need to remove."
+    return 0
+  fi
+
+  record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \")
+  _debug record_id "$record_id"
+  if [ -z "$record_id" ]; then
+    _err "Can not get record id."
+    return 1
+  fi
+
+  if ! _rest POST "Record.Remove" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then
+    _err "Record.Remove error."
+    return 1
+  fi
+
+  _contains "$response" "Action completed successful"
+
+}
+
+#add the txt record.
+#usage: root  sub  txtvalue
+add_record() {
+  root=$1
+  sub=$2
+  txtvalue=$3
+  fulldomain="$sub.$root"
+
+  _info "Adding record"
+
+  if ! _rest POST "Record.Create" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=default"; then
+    return 1
+  fi
+
+  _contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists"
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if ! _rest POST "Domain.Info" "user_token=$DPI_Id,$DPI_Key&format=json&domain=$h"; then
+      return 1
+    fi
+
+    if _contains "$response" "Action completed successful"; then
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
+      _debug _domain_id "$_domain_id"
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _debug _sub_domain "$_sub_domain"
+        _domain="$h"
+        _debug _domain "$_domain"
+        return 0
+      fi
+      return 1
+    fi
+    p="$i"
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+#Usage: method  URI  data
+_rest() {
+  m="$1"
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+  url="$REST_API/$ep"
+
+  _debug url "$url"
+
+  if [ "$m" = "GET" ]; then
+    response="$(_get "$url" | tr -d '\r')"
+  else
+    _debug2 data "$data"
+    response="$(_post "$data" "$url" | tr -d '\r')"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 167 - 0
dnsapi/dns_gcloud.sh

@@ -0,0 +1,167 @@
+#!/usr/bin/env sh
+
+# Author: Janos Lenart <janos@lenart.io>
+
+########  Public functions #####################
+
+# Usage: dns_gcloud_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_gcloud_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gcloud"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _dns_gcloud_find_zone || return $?
+
+  # Add an extra RR
+  _dns_gcloud_start_tr || return $?
+  _dns_gcloud_get_rrdatas || return $?
+  echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
+  printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $?
+  _dns_gcloud_execute_tr || return $?
+
+  _info "$fulldomain record added"
+}
+
+# Usage: dns_gcloud_rm   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Remove the txt record after validation.
+dns_gcloud_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gcloud"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _dns_gcloud_find_zone || return $?
+
+  # Remove one RR
+  _dns_gcloud_start_tr || return $?
+  _dns_gcloud_get_rrdatas || return $?
+  echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
+  echo "$rrdatas" | grep -F -v "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
+  _dns_gcloud_execute_tr || return $?
+
+  _info "$fulldomain record added"
+}
+
+####################  Private functions below ##################################
+
+_dns_gcloud_start_tr() {
+  if ! trd=$(mktemp -d); then
+    _err "_dns_gcloud_start_tr: failed to create temporary directory"
+    return 1
+  fi
+  tr="$trd/tr.yaml"
+  _debug tr "$tr"
+
+  if ! gcloud dns record-sets transaction start \
+    --transaction-file="$tr" \
+    --zone="$managedZone"; then
+    rm -r "$trd"
+    _err "_dns_gcloud_start_tr: failed to execute transaction"
+    return 1
+  fi
+}
+
+_dns_gcloud_execute_tr() {
+  if ! gcloud dns record-sets transaction execute \
+    --transaction-file="$tr" \
+    --zone="$managedZone"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_execute_tr: failed to execute transaction"
+    return 1
+  fi
+  rm -r "$trd"
+
+  for i in $(seq 1 120); do
+    if gcloud dns record-sets changes list \
+      --zone="$managedZone" \
+      --filter='status != done' \
+      | grep -q '^.*'; then
+      _info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..."
+      sleep 5
+    else
+      return 0
+    fi
+  done
+
+  _err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes"
+  rm -r "$trd"
+  return 1
+}
+
+_dns_gcloud_remove_rrs() {
+  if ! xargs --no-run-if-empty gcloud dns record-sets transaction remove \
+    --name="$fulldomain." \
+    --ttl="$ttl" \
+    --type=TXT \
+    --zone="$managedZone" \
+    --transaction-file="$tr"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_remove_rrs: failed to remove RRs"
+    return 1
+  fi
+}
+
+_dns_gcloud_add_rrs() {
+  ttl=60
+  if ! xargs --no-run-if-empty gcloud dns record-sets transaction add \
+    --name="$fulldomain." \
+    --ttl="$ttl" \
+    --type=TXT \
+    --zone="$managedZone" \
+    --transaction-file="$tr"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_add_rrs: failed to add RRs"
+    return 1
+  fi
+}
+
+_dns_gcloud_find_zone() {
+  # Prepare a filter that matches zones that are suiteable for this entry.
+  # For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com;
+  # this function finds the longest postfix that has a managed zone.
+  part="$fulldomain"
+  filter="dnsName=( "
+  while [ "$part" != "" ]; do
+    filter="$filter$part. "
+    part="$(echo "$part" | sed 's/[^.]*\.*//')"
+  done
+  filter="$filter)"
+  _debug filter "$filter"
+
+  # List domains and find the longest match (in case of some levels of delegation)
+  if ! match=$(gcloud dns managed-zones list \
+    --format="value(name, dnsName)" \
+    --filter="$filter" \
+    | while read -r dnsName name; do
+      printf "%s\t%s\t%s\n" "${#dnsName}" "$dnsName" "$name"
+    done \
+    | sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
+    _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"
+    return 1
+  fi
+
+  dnsName=$(echo "$match" | cut -f2)
+  _debug dnsName "$dnsName"
+  managedZone=$(echo "$match" | cut -f1)
+  _debug managedZone "$managedZone"
+}
+
+_dns_gcloud_get_rrdatas() {
+  if ! rrdatas=$(gcloud dns record-sets list \
+    --zone="$managedZone" \
+    --name="$fulldomain." \
+    --type=TXT \
+    --format="value(ttl,rrdatas)"); then
+    _err "_dns_gcloud_get_rrdatas: Failed to list record-sets"
+    rm -r "$trd"
+    return 1
+  fi
+  ttl=$(echo "$rrdatas" | cut -f1)
+  rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/","/"\n"/g')
+}

+ 168 - 0
dnsapi/dns_gdnsdk.sh

@@ -0,0 +1,168 @@
+#!/usr/bin/env sh
+#Author: Herman Sletteng
+#Report Bugs here: https://github.com/loial/acme.sh
+#
+#
+# Note, gratisdns requires a login first, so the script needs to handle
+# temporary cookies. Since acme.sh _get/_post currently don't directly support
+# cookies, I've defined wrapper functions _myget/_mypost to set the headers
+
+GDNSDK_API="https://admin.gratisdns.com"
+########  Public functions #####################
+#Usage: dns_gdnsdk_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_gdnsdk_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gratisdns.dk"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+  if ! _gratisdns_login; then
+    _err "Login failed!"
+    return 1
+  fi
+  #finding domain zone
+  if ! _get_domain; then
+    _err "No matching root domain for $fulldomain found"
+    return 1
+  fi
+  # adding entry
+  _info "Adding the entry"
+  _mypost "action=dns_primary_record_added_txt&user_domain=$_domain&name=$fulldomain&txtdata=$txtvalue&ttl=1"
+  if _successful_update; then return 0; fi
+  _err "Couldn't create entry!"
+  return 1
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_gdnsdk_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gratisdns.dk"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+  if ! _gratisdns_login; then
+    _err "Login failed!"
+    return 1
+  fi
+  if ! _get_domain; then
+    _err "No matching root domain for $fulldomain found"
+    return 1
+  fi
+  _findentry "$fulldomain" "$txtvalue"
+  if [ -z "$_id" ]; then
+    _info "Entry doesn't exist, nothing to delete"
+    return 0
+  fi
+  _debug "Deleting record..."
+  _mypost "action=dns_primary_delete_txt&user_domain=$_domain&id=$_id"
+  # removing entry
+
+  if _successful_update; then return 0; fi
+  _err "Couldn't delete entry!"
+  return 1
+}
+
+####################  Private functions below ##################################
+
+_checkcredentials() {
+  GDNSDK_Username="${GDNSDK_Username:-$(_readaccountconf_mutable GDNSDK_Username)}"
+  GDNSDK_Password="${GDNSDK_Password:-$(_readaccountconf_mutable GDNSDK_Password)}"
+
+  if [ -z "$GDNSDK_Username" ] || [ -z "$GDNSDK_Password" ]; then
+    GDNSDK_Username=""
+    GDNSDK_Password=""
+    _err "You haven't specified gratisdns.dk username and password yet."
+    _err "Please add credentials and try again."
+    return 1
+  fi
+  #save the credentials to the account conf file.
+  _saveaccountconf_mutable GDNSDK_Username "$GDNSDK_Username"
+  _saveaccountconf_mutable GDNSDK_Password "$GDNSDK_Password"
+  return 0
+}
+
+_checkcookie() {
+  GDNSDK_Cookie="${GDNSDK_Cookie:-$(_readaccountconf_mutable GDNSDK_Cookie)}"
+  if [ -z "$GDNSDK_Cookie" ]; then
+    _debug "No cached cookie found"
+    return 1
+  fi
+  _myget "action="
+  if (echo "$_result" | grep -q "logmeout"); then
+    _debug "Cached cookie still valid"
+    return 0
+  fi
+  _debug "Cached cookie no longer valid"
+  GDNSDK_Cookie=""
+  _saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie"
+  return 1
+}
+
+_gratisdns_login() {
+  if ! _checkcredentials; then return 1; fi
+
+  if _checkcookie; then
+    _debug "Already logged in"
+    return 0
+  fi
+  _debug "Logging into GratisDNS with user $GDNSDK_Username"
+
+  if ! _mypost "login=$GDNSDK_Username&password=$GDNSDK_Password&action=logmein"; then
+    _err "GratisDNS login failed for user $GDNSDK_Username bad RC from _post"
+    return 1
+  fi
+
+  GDNSDK_Cookie="$(grep -A 15 '302 Found' "$HTTP_HEADER" | _egrep_o 'Cookie: [^;]*' | _head_n 1 | cut -d ' ' -f2)"
+
+  if [ -z "$GDNSDK_Cookie" ]; then
+    _err "GratisDNS login failed for user $GDNSDK_Username. Check $HTTP_HEADER file"
+    return 1
+  fi
+  export GDNSDK_Cookie
+  _saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie"
+  return 0
+}
+
+_myget() {
+  #Adds cookie to request
+  export _H1="Cookie: $GDNSDK_Cookie"
+  _result=$(_get "$GDNSDK_API?$1")
+}
+_mypost() {
+  #Adds cookie to request
+  export _H1="Cookie: $GDNSDK_Cookie"
+  _result=$(_post "$1" "$GDNSDK_API")
+}
+
+_get_domain() {
+  _myget 'action=dns_primarydns'
+  _domains=$(echo "$_result" | _egrep_o ' domain="[[:alnum:].-_]+' | sed 's/^.*"//')
+  if [ -z "$_domains" ]; then
+    _err "Primary domain list not found!"
+    return 1
+  fi
+  for _domain in $_domains; do
+    if (_endswith "$fulldomain" "$_domain"); then
+      _debug "Root domain: $_domain"
+      return 0
+    fi
+  done
+  return 1
+}
+
+_successful_update() {
+  if (echo "$_result" | grep -q 'table-success'); then return 0; fi
+  return 1
+}
+
+_findentry() {
+  #returns id of dns entry, if it exists
+  _myget "action=dns_primary_changeDNSsetup&user_domain=$_domain"
+  _id=$(echo "$_result" | _egrep_o "<td>$1</td>\s*<td>$2</td>[^?]*[^&]*&id=[^&]*" | sed 's/^.*=//')
+  if [ -n "$_id" ]; then
+    _debug "Entry found with _id=$_id"
+    return 0
+  fi
+  return 1
+}

+ 1 - 2
dnsapi/dns_inwx.sh

@@ -158,8 +158,7 @@ _inwx_login() {
   export _H1
 
   #https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
-  if _contains "$response" "<member><name>code</name><value><int>1000</int></value></member>" \
-    && _contains "$response" "<member><name>tfa</name><value><string>GOOGLE-AUTH</string></value></member>"; then
+  if _contains "$response" "tfa"; then
     if [ -z "$INWX_Shared_Secret" ]; then
       _err "Mobile TAN detected."
       _err "Please define a shared secret."

+ 40 - 15
dnsapi/dns_lexicon.sh

@@ -7,20 +7,13 @@ lexicon_cmd="lexicon"
 
 wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api"
 
-########  Public functions #####################
-
-#Usage: add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_lexicon_add() {
-  fulldomain=$1
-  txtvalue=$2
-
-  domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
-
+_lexicon_init() {
   if ! _exists "$lexicon_cmd"; then
     _err "Please install $lexicon_cmd first: $wiki"
     return 1
   fi
 
+  PROVIDER="${PROVIDER:-$(_readdomainconf PROVIDER)}"
   if [ -z "$PROVIDER" ]; then
     PROVIDER=""
     _err "Please define env PROVIDER first: $wiki"
@@ -33,46 +26,78 @@ dns_lexicon_add() {
   # e.g. busybox-ash does not know [:upper:]
   # shellcheck disable=SC2018,SC2019
   Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z')
+  eval "$Lx_name=\${$Lx_name:-$(_readaccountconf_mutable "$Lx_name")}"
   Lx_name_v=$(eval echo \$"$Lx_name")
   _secure_debug "$Lx_name" "$Lx_name_v"
   if [ "$Lx_name_v" ]; then
-    _saveaccountconf "$Lx_name" "$Lx_name_v"
+    _saveaccountconf_mutable "$Lx_name" "$Lx_name_v"
     eval export "$Lx_name"
   fi
 
   # shellcheck disable=SC2018,SC2019
   Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z')
+  eval "$Lx_token=\${$Lx_token:-$(_readaccountconf_mutable "$Lx_token")}"
   Lx_token_v=$(eval echo \$"$Lx_token")
   _secure_debug "$Lx_token" "$Lx_token_v"
   if [ "$Lx_token_v" ]; then
-    _saveaccountconf "$Lx_token" "$Lx_token_v"
+    _saveaccountconf_mutable "$Lx_token" "$Lx_token_v"
     eval export "$Lx_token"
   fi
 
   # shellcheck disable=SC2018,SC2019
   Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z')
+  eval "$Lx_password=\${$Lx_password:-$(_readaccountconf_mutable "$Lx_password")}"
   Lx_password_v=$(eval echo \$"$Lx_password")
   _secure_debug "$Lx_password" "$Lx_password_v"
   if [ "$Lx_password_v" ]; then
-    _saveaccountconf "$Lx_password" "$Lx_password_v"
+    _saveaccountconf_mutable "$Lx_password" "$Lx_password_v"
     eval export "$Lx_password"
   fi
 
   # shellcheck disable=SC2018,SC2019
   Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z')
+  eval "$Lx_domaintoken=\${$Lx_domaintoken:-$(_readaccountconf_mutable "$Lx_domaintoken")}"
   Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken")
   _secure_debug "$Lx_domaintoken" "$Lx_domaintoken_v"
   if [ "$Lx_domaintoken_v" ]; then
+    _saveaccountconf_mutable "$Lx_domaintoken" "$Lx_domaintoken_v"
     eval export "$Lx_domaintoken"
-    _saveaccountconf "$Lx_domaintoken" "$Lx_domaintoken_v"
   fi
+}
+
+########  Public functions #####################
+
+#Usage: dns_lexicon_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_lexicon_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _lexicon_init; then
+    return 1
+  fi
+
+  domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
+
+  _secure_debug LEXICON_OPTS "$LEXICON_OPTS"
+  _savedomainconf LEXICON_OPTS "$LEXICON_OPTS"
 
-  $lexicon_cmd "$PROVIDER" create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
+  # shellcheck disable=SC2086
+  $lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
 
 }
 
-#fulldomain
+#Usage: dns_lexicon_rm   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_lexicon_rm() {
   fulldomain=$1
+  txtvalue=$2
+
+  if ! _lexicon_init; then
+    return 1
+  fi
+
+  domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
+
+  # shellcheck disable=SC2086
+  $lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
 
 }

+ 133 - 0
dnsapi/dns_netcup.sh

@@ -0,0 +1,133 @@
+#!/usr/bin/env sh
+#developed by linux-insideDE
+
+NC_Apikey="${NC_Apikey:-$(_readaccountconf_mutable NC_Apikey)}"
+NC_Apipw="${NC_Apipw:-$(_readaccountconf_mutable NC_Apipw)}"
+NC_CID="${NC_CID:-$(_readaccountconf_mutable NC_CID)}"
+end="https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON"
+client=""
+
+dns_netcup_add() {
+  login
+  if [ "$NC_Apikey" = "" ] || [ "$NC_Apipw" = "" ] || [ "$NC_CID" = "" ]; then
+    _err "No Credentials given"
+    return 1
+  fi
+  _saveaccountconf_mutable NC_Apikey "$NC_Apikey"
+  _saveaccountconf_mutable NC_Apipw "$NC_Apipw"
+  _saveaccountconf_mutable NC_CID "$NC_CID"
+  fulldomain=$1
+  txtvalue=$2
+  domain=""
+  exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
+  exit=$(_math "$exit" + 1)
+  i=$exit
+
+  while
+    [ "$exit" -gt 0 ]
+  do
+    tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
+    if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
+      domain="$tmp"
+    else
+      domain="$tmp.$domain"
+    fi
+    if [ "$(_math "$i" - "$exit")" -ge 1 ]; then
+      msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"false\", \"state\": \"yes\"} ]}}}" "$end" "" "POST")
+      _debug "$msg"
+      if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then
+        if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+          _err "$msg"
+          return 1
+        else
+          break
+        fi
+      fi
+    fi
+    exit=$(_math "$exit" - 1)
+  done
+  logout
+}
+
+dns_netcup_rm() {
+  login
+  fulldomain=$1
+  txtvalue=$2
+
+  domain=""
+  exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
+  exit=$(_math "$exit" + 1)
+  i=$exit
+  rec=""
+
+  while
+    [ "$exit" -gt 0 ]
+  do
+    tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
+    if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
+      domain="$tmp"
+    else
+      domain="$tmp.$domain"
+    fi
+    if [ "$(_math "$i" - "$exit")" -ge 1 ]; then
+      msg=$(_post "{\"action\": \"infoDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\", \"domainname\": \"$domain\"}}" "$end" "" "POST")
+      rec=$(echo "$msg" | sed 's/\[//g' | sed 's/\]//g' | sed 's/{\"serverrequestid\".*\"dnsrecords\"://g' | sed 's/},{/};{/g' | sed 's/{//g' | sed 's/}//g')
+      _debug "$msg"
+      if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then
+        if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+          _err "$msg"
+          return 1
+        else
+          break
+        fi
+      fi
+    fi
+    exit=$(_math "$exit" - 1)
+  done
+
+  ida=0000
+  idv=0001
+  ids=0000000000
+  i=1
+  while
+    [ "$i" -ne 0 ]
+  do
+    specrec=$(_getfield "$rec" "$i" ";")
+    idv="$ida"
+    ida=$(_getfield "$specrec" "1" "," | sed 's/\"id\":\"//g' | sed 's/\"//g')
+    txtv=$(_getfield "$specrec" "5" "," | sed 's/\"destination\":\"//g' | sed 's/\"//g')
+    i=$(_math "$i" + 1)
+    if [ "$txtvalue" = "$txtv" ]; then
+      i=0
+      ids="$ida"
+    fi
+    if [ "$ida" = "$idv" ]; then
+      i=0
+    fi
+  done
+  msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"$ids\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"TRUE\", \"state\": \"yes\"} ]}}}" "$end" "" "POST")
+  _debug "$msg"
+  if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+    _err "$msg"
+    return 1
+  fi
+  logout
+}
+
+login() {
+  tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
+  sid=$(_getfield "$tmp" "8" | sed s/\"responsedata\":\{\"apisessionid\":\"//g | sed 's/\"\}\}//g')
+  _debug "$tmp"
+  if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+    _err "$msg"
+    return 1
+  fi
+}
+logout() {
+  tmp=$(_post "{\"action\": \"logout\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
+  _debug "$tmp"
+  if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+    _err "$msg"
+    return 1
+  fi
+}

+ 22 - 39
dnsapi/dns_unoeuro.sh

@@ -50,34 +50,16 @@ dns_unoeuro_add() {
     _err "Error"
     return 1
   fi
+  _info "Adding record"
 
-  if ! _contains "$response" "$_sub_domain" >/dev/null; then
-    _info "Adding record"
-
-    if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then
-      if _contains "$response" "\"status\": 200" >/dev/null; then
-        _info "Added, OK"
-        return 0
-      else
-        _err "Add txt record error."
-        return 1
-      fi
-    fi
-    _err "Add txt record error."
-  else
-    _info "Updating record"
-    record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1)
-    record_line_number=$(_math "$record_line_number" - 1)
-    record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
-    _debug "record_id" "$record_id"
-
-    _uno_rest PUT "my/products/$h/dns/records/$record_id" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"
+  if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then
     if _contains "$response" "\"status\": 200" >/dev/null; then
-      _info "Updated, OK"
+      _info "Added, OK"
       return 0
+    else
+      _err "Add txt record error."
+      return 1
     fi
-    _err "Update error"
-    return 1
   fi
 }
 
@@ -122,23 +104,24 @@ dns_unoeuro_rm() {
   if ! _contains "$response" "$_sub_domain"; then
     _info "Don't need to remove."
   else
-    record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1)
-    record_line_number=$(_math "$record_line_number" - 1)
-    record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
-    _debug "record_id" "$record_id"
-
-    if [ -z "$record_id" ]; then
-      _err "Can not get record id to remove."
-      return 1
-    fi
+    for record_line_number in $(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1); do
+      record_line_number=$(_math "$record_line_number" - 1)
+      _debug "record_line_number" "$record_line_number"
+      record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
+      _debug "record_id" "$record_id"
+
+      if [ -z "$record_id" ]; then
+        _err "Can not get record id to remove."
+        return 1
+      fi
 
-    if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then
-      _err "Delete record error."
-      return 1
-    fi
-    _contains "$response" "\"status\": 200"
+      if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then
+        _err "Delete record error."
+        return 1
+      fi
+      _contains "$response" "\"status\": 200"
+    done
   fi
-
 }
 
 ####################  Private functions below ##################################