Browse Source

Merge branch 'dev' of https://github.com/Neilpang/acme.sh into dev

neilpang 6 years ago
parent
commit
f1c0f3d45f
7 changed files with 719 additions and 39 deletions
  1. 2 2
      deploy/gcore_cdn.sh
  2. 249 35
      deploy/haproxy.sh
  3. 3 1
      dnsapi/README.md
  4. 1 1
      dnsapi/dns_ddnss.sh
  5. 64 0
      dnsapi/dns_nsd.sh
  6. 139 0
      dnsapi/dns_one.sh
  7. 261 0
      dnsapi/dns_schlundtech.sh

+ 2 - 2
deploy/gcore_cdn.sh

@@ -27,8 +27,8 @@ gcore_cdn_deploy() {
   _debug _cca "$_cca"
   _debug _cfullchain "$_cfullchain"
 
-  _fullchain=$(tr '\n\r' '@#' <"$_cfullchain" | sed 's/@/\\n/g;s/#/\\r/g')
-  _key=$(tr '\n\r' '@#' <"$_ckey" | sed 's/@/\\n/g;s/#/\\r/g')
+  _fullchain=$(tr '\r\n' '*#' <"$_cfullchain" | sed 's/*#/#/g;s/##/#/g;s/#/\\n/g')
+  _key=$(tr '\r\n' '*#' <"$_ckey" | sed 's/*#/#/g;s/#/\\n/g')
 
   _debug _fullchain "$_fullchain"
   _debug _key "$_key"

+ 249 - 35
deploy/haproxy.sh

@@ -1,8 +1,41 @@
 #!/usr/bin/env sh
 
-#Here is a script to deploy cert to haproxy server.
-
-#returns 0 means success, otherwise error.
+# Script for acme.sh to deploy certificates to haproxy
+#
+# The following variables can be exported:
+#
+# export DEPLOY_HAPROXY_PEM_NAME="${domain}.pem"
+#
+# Defines the name of the PEM file.
+# Defaults to "<domain>.pem"
+#
+# export DEPLOY_HAPROXY_PEM_PATH="/etc/haproxy"
+#
+# Defines location of PEM file for HAProxy.
+# Defaults to /etc/haproxy
+#
+# export DEPLOY_HAPROXY_RELOAD="systemctl reload haproxy"
+#
+# OPTIONAL: Reload command used post deploy
+# This defaults to be a no-op (ie "true").
+# It is strongly recommended to set this something that makes sense
+# for your distro.
+#
+# export DEPLOY_HAPROXY_ISSUER="no"
+#
+# OPTIONAL: Places CA file as "${DEPLOY_HAPROXY_PEM}.issuer"
+# Note: Required for OCSP stapling to work
+#
+# export DEPLOY_HAPROXY_BUNDLE="no"
+#
+# OPTIONAL: Deploy this certificate as part of a multi-cert bundle
+# This adds a suffix to the certificate based on the certificate type
+# eg RSA certificates will have .rsa as a suffix to the file name
+# HAProxy will load all certificates and provide one or the other
+# depending on client capabilities
+# Note: This functionality requires HAProxy was compiled against
+# a version of OpenSSL that supports this.
+#
 
 ########  Public functions #####################
 
@@ -14,45 +47,226 @@ haproxy_deploy() {
   _cca="$4"
   _cfullchain="$5"
 
-  _debug _cdomain "$_cdomain"
-  _debug _ckey "$_ckey"
-  _debug _ccert "$_ccert"
-  _debug _cca "$_cca"
-  _debug _cfullchain "$_cfullchain"
-
-  # handle reload preference
-  DEFAULT_HAPROXY_RELOAD="/usr/sbin/service haproxy restart"
-  if [ -z "${DEPLOY_HAPROXY_RELOAD}" ]; then
-    _reload="${DEFAULT_HAPROXY_RELOAD}"
-    _cleardomainconf DEPLOY_HAPROXY_RELOAD
-  else
-    _reload="${DEPLOY_HAPROXY_RELOAD}"
-    _savedomainconf DEPLOY_HAPROXY_RELOAD "$DEPLOY_HAPROXY_RELOAD"
+  # Some defaults
+  DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy"
+  DEPLOY_HAPROXY_PEM_NAME_DEFAULT="${_cdomain}.pem"
+  DEPLOY_HAPROXY_BUNDLE_DEFAULT="no"
+  DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
+  DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
+
+  if [ -f "${DOMAIN_CONF}" ]; then
+    # shellcheck disable=SC1090
+    . "${DOMAIN_CONF}"
+  fi
+
+  _debug _cdomain "${_cdomain}"
+  _debug _ckey "${_ckey}"
+  _debug _ccert "${_ccert}"
+  _debug _cca "${_cca}"
+  _debug _cfullchain "${_cfullchain}"
+
+  # PEM_PATH is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
+  if [ -n "${DEPLOY_HAPROXY_PEM_PATH}" ]; then
+    Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
+    _savedomainconf Le_Deploy_haproxy_pem_path "${Le_Deploy_haproxy_pem_path}"
+  elif [ -z "${Le_Deploy_haproxy_pem_path}" ]; then
+    Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
   fi
-  _savedomainconf DEPLOY_HAPROXY_PEM_PATH "$DEPLOY_HAPROXY_PEM_PATH"
 
-  # work out the path where the PEM file should go
-  _pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
-  if [ -z "$_pem_path" ]; then
-    _err "Path to save PEM file not found. Please define DEPLOY_HAPROXY_PEM_PATH."
+  # Ensure PEM_PATH exists
+  if [ -d "${Le_Deploy_haproxy_pem_path}" ]; then
+    _debug "PEM_PATH ${Le_Deploy_haproxy_pem_path} exists"
+  else
+    _err "PEM_PATH ${Le_Deploy_haproxy_pem_path} does not exist"
     return 1
   fi
-  _pem_full_path="$_pem_path/$_cdomain.pem"
-  _info "Full path to PEM $_pem_full_path"
 
-  # combine the key and fullchain into a single pem and install
-  cat "$_cfullchain" "$_ckey" >"$_pem_full_path"
-  chmod 600 "$_pem_full_path"
-  _info "Certificate successfully deployed"
+  # PEM_NAME is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
+  if [ -n "${DEPLOY_HAPROXY_PEM_NAME}" ]; then
+    Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME}"
+    _savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
+  elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then
+    Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
+  fi
+
+  # BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
+  if [ -n "${DEPLOY_HAPROXY_BUNDLE}" ]; then
+    Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE}"
+    _savedomainconf Le_Deploy_haproxy_bundle "${Le_Deploy_haproxy_bundle}"
+  elif [ -z "${Le_Deploy_haproxy_bundle}" ]; then
+    Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
+  fi
 
-  # restart HAProxy
-  _info "Run reload: $_reload"
-  if eval "$_reload"; then
-    _info "Reload success!"
-    return 0
+  # ISSUER is optional. If not provided then assume "${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
+  if [ -n "${DEPLOY_HAPROXY_ISSUER}" ]; then
+    Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER}"
+    _savedomainconf Le_Deploy_haproxy_issuer "${Le_Deploy_haproxy_issuer}"
+  elif [ -z "${Le_Deploy_haproxy_issuer}" ]; then
+    Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
+  fi
+
+  # RELOAD is optional. If not provided then assume "${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
+  if [ -n "${DEPLOY_HAPROXY_RELOAD}" ]; then
+    Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD}"
+    _savedomainconf Le_Deploy_haproxy_reload "${Le_Deploy_haproxy_reload}"
+  elif [ -z "${Le_Deploy_haproxy_reload}" ]; then
+    Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
+  fi
+
+  # Set the suffix depending if we are creating a bundle or not
+  if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then
+    _info "Bundle creation requested"
+    # Initialise $Le_Keylength if its not already set
+    if [ -z "${Le_Keylength}" ]; then
+      Le_Keylength=""
+    fi
+    if _isEccKey "${Le_Keylength}"; then
+      _info "ECC key type detected"
+      _suffix=".ecdsa"
+    else
+      _info "RSA key type detected"
+      _suffix=".rsa"
+    fi
   else
-    _err "Reload error"
-    return 1
+    _suffix=""
+  fi
+  _debug _suffix "${_suffix}"
+
+  # Set variables for later
+  _pem="${Le_Deploy_haproxy_pem_path}/${Le_Deploy_haproxy_pem_name}${_suffix}"
+  _issuer="${_pem}.issuer"
+  _ocsp="${_pem}.ocsp"
+  _reload="${Le_Deploy_haproxy_reload}"
+
+  _info "Deploying PEM file"
+  # Create a temporary PEM file
+  _temppem="$(_mktemp)"
+  _debug _temppem "${_temppem}"
+  cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}"
+  _ret="$?"
+
+  # Check that we could create the temporary file
+  if [ "${_ret}" != "0" ]; then
+    _err "Error code ${_ret} returned during PEM file creation"
+    [ -f "${_temppem}" ] && rm -f "${_temppem}"
+    return ${_ret}
+  fi
+
+  # Move PEM file into place
+  _info "Moving new certificate into place"
+  _debug _pem "${_pem}"
+  cat "${_temppem}" >"${_pem}"
+  _ret=$?
+
+  # Clean up temp file
+  [ -f "${_temppem}" ] && rm -f "${_temppem}"
+
+  # Deal with any failure of moving PEM file into place
+  if [ "${_ret}" != "0" ]; then
+    _err "Error code ${_ret} returned while moving new certificate into place"
+    return ${_ret}
+  fi
+
+  # Update .issuer file if requested
+  if [ "${Le_Deploy_haproxy_issuer}" = "yes" ]; then
+    _info "Updating .issuer file"
+    _debug _issuer "${_issuer}"
+    cat "${_cca}" >"${_issuer}"
+    _ret="$?"
+
+    if [ "${_ret}" != "0" ]; then
+      _err "Error code ${_ret} returned while copying issuer/CA certificate into place"
+      return ${_ret}
+    fi
+  else
+    [ -f "${_issuer}" ] _err "Issuer file update not requested but .issuer file exists"
+  fi
+
+  # Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option
+  if [ -z "${Le_OCSP_Staple}" ]; then
+    Le_OCSP_Staple="0"
+  fi
+  if [ "${Le_OCSP_Staple}" = "1" ]; then
+    _info "Updating OCSP stapling info"
+    _debug _ocsp "${_ocsp}"
+    _info "Extracting OCSP URL"
+    _ocsp_url=$(openssl x509 -noout -ocsp_uri -in "${_pem}")
+    _debug _ocsp_url "${_ocsp_url}"
+
+    # Only process OCSP if URL was present
+    if [ "${_ocsp_url}" != "" ]; then
+      # Extract the hostname from the OCSP URL
+      _info "Extracting OCSP URL"
+      _ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3)
+      _debug _ocsp_host "${_ocsp_host}"
+
+      # Only process the certificate if we have a .issuer file
+      if [ -r "${_issuer}" ]; then
+        # Check if issuer cert is also a root CA cert
+        _subjectdn=$(openssl x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
+        _debug _subjectdn "${_subjectdn}"
+        _issuerdn=$(openssl x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
+        _debug _issuerdn "${_issuerdn}"
+        _info "Requesting OCSP response"
+        # Request the OCSP response from the issuer and store it
+        if [ "${_subjectdn}" = "${_issuerdn}" ]; then
+          # If the issuer is a CA cert then our command line has "-CAfile" added
+          openssl ocsp \
+            -issuer "${_issuer}" \
+            -cert "${_pem}" \
+            -url "${_ocsp_url}" \
+            -header Host "${_ocsp_host}" \
+            -respout "${_ocsp}" \
+            -verify_other "${_issuer}" \
+            -no_nonce \
+            -CAfile "${_issuer}" \
+            | grep -q "${_pem}: good"
+          _ret=$?
+        else
+          # Issuer is not a root CA so no "-CAfile" option
+          openssl ocsp \
+            -issuer "${_issuer}" \
+            -cert "${_pem}" \
+            -url "${_ocsp_url}" \
+            -header Host "${_ocsp_host}" \
+            -respout "${_ocsp}" \
+            -verify_other "${_issuer}" \
+            -no_nonce \
+            | grep -q "${_pem}: good"
+          _ret=$?
+        fi
+      else
+        # Non fatal: No issuer file was present so no OCSP stapling file created
+        _err "OCSP stapling in use but no .issuer file was present"
+      fi
+    else
+      # Non fatal: No OCSP url was found int the certificate
+      _err "OCSP update requested but no OCSP URL was found in certificate"
+    fi
+
+    # Non fatal: Check return code of openssl command
+    if [ "${_ret}" != "0" ]; then
+      _err "Updating OCSP stapling failed with return code ${_ret}"
+    fi
+  else
+    # An OCSP file was already present but certificate did not have OCSP extension
+    if [ -f "${_ocsp}" ]; then
+      _err "OCSP was not requested but .ocsp file exists."
+      # Could remove the file at this step, although HAProxy just ignores it in this case
+      # rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file"
+    fi
+  fi
+
+  # Reload HAProxy
+  _debug _reload "${_reload}"
+  eval "${_reload}"
+  _ret=$?
+  if [ "${_ret}" != "0" ]; then
+    _err "Error code ${_ret} during reload"
+    return ${_ret}
+  else
+    _info "Reload successful"
   fi
 
+  return 0
 }

+ 3 - 1
dnsapi/README.md

@@ -1,4 +1,6 @@
 # How to use DNS API
 DNS api usage:
 
-https://github.com/Neilpang/acme.sh/wiki/dnsapi
+
+https://github.com/Neilpang/acme.sh/wiki/dnsapi
+

+ 1 - 1
dnsapi/dns_ddnss.sh

@@ -119,7 +119,7 @@ _ddnss_rest() {
 
   # DDNSS uses GET to update domain info
   if [ "$method" = "GET" ]; then
-    response="$(_get "$url" | sed 's/<[^>]*>//g;/</N;//ba' | _tail_n 1)"
+    response="$(_get "$url" | sed 's/<[a-zA-Z\/][^>]*>//g' | _tail_n 1)"
   else
     _err "Unsupported method"
     return 1

+ 64 - 0
dnsapi/dns_nsd.sh

@@ -0,0 +1,64 @@
+#!/usr/bin/env sh
+
+#Nsd_ZoneFile="/etc/nsd/zones/example.com.zone"
+#Nsd_Command="sudo nsd-control reload"
+
+# args: fulldomain txtvalue
+dns_nsd_add() {
+  fulldomain=$1
+  txtvalue=$2
+  ttlvalue=300
+
+  Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}"
+  Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}"
+
+  # Arg checks
+  if [ -z "$Nsd_ZoneFile" ] || [ -z "$Nsd_Command" ]; then
+    Nsd_ZoneFile=""
+    Nsd_Command=""
+    _err "Specify ENV vars Nsd_ZoneFile and Nsd_Command"
+    return 1
+  fi
+
+  if [ ! -f "$Nsd_ZoneFile" ]; then
+    Nsd_ZoneFile=""
+    Nsd_Command=""
+    _err "No such file: $Nsd_ZoneFile"
+    return 1
+  fi
+
+  _savedomainconf Nsd_ZoneFile "$Nsd_ZoneFile"
+  _savedomainconf Nsd_Command "$Nsd_Command"
+
+  echo "$fulldomain. $ttlvalue IN TXT \"$txtvalue\"" >>"$Nsd_ZoneFile"
+  _info "Added TXT record for $fulldomain"
+  _debug "Running $Nsd_Command"
+  if eval "$Nsd_Command"; then
+    _info "Successfully updated the zone"
+    return 0
+  else
+    _err "Problem updating the zone"
+    return 1
+  fi
+}
+
+# args: fulldomain txtvalue
+dns_nsd_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  ttlvalue=300
+
+  Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}"
+  Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}"
+
+  sed -i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
+  _info "Removed TXT record for $fulldomain"
+  _debug "Running $Nsd_Command"
+  if eval "$Nsd_Command"; then
+    _info "Successfully reloaded NSD "
+    return 0
+  else
+    _err "Problem reloading NSD"
+    return 1
+  fi
+}

+ 139 - 0
dnsapi/dns_one.sh

@@ -0,0 +1,139 @@
+#!/usr/bin/env sh
+# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
+
+# one.com ui wrapper for acme.sh
+# Author: github: @diseq
+# Created: 2019-02-17
+#
+#     export ONECOM_USER="username"
+#     export ONECOM_PASSWORD="password"
+#
+# Usage:
+#     acme.sh --issue --dns dns_one -d example.com
+#
+#     only single domain supported atm
+
+dns_one_add() {
+  mysubdomain=$(printf -- "%s" "$1" | rev | cut -d"." -f3- | rev)
+  mydomain=$(printf -- "%s" "$1" | rev | cut -d"." -f1-2 | rev)
+  txtvalue=$2
+
+  # get credentials
+  ONECOM_USER="${ONECOM_USER:-$(_readaccountconf_mutable ONECOM_USER)}"
+  ONECOM_PASSWORD="${ONECOM_PASSWORD:-$(_readaccountconf_mutable ONECOM_PASSWORD)}"
+  if [ -z "$ONECOM_USER" ] || [ -z "$ONECOM_PASSWORD" ]; then
+    ONECOM_USER=""
+    ONECOM_PASSWORD=""
+    _err "You didn't specify a one.com username and password yet."
+    _err "Please create the key and try again."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable ONECOM_USER "$ONECOM_USER"
+  _saveaccountconf_mutable ONECOM_PASSWORD "$ONECOM_PASSWORD"
+
+  # Login with user and password
+  postdata="loginDomain=true"
+  postdata="$postdata&displayUsername=$ONECOM_USER"
+  postdata="$postdata&username=$ONECOM_USER"
+  postdata="$postdata&targetDomain=$mydomain"
+  postdata="$postdata&password1=$ONECOM_PASSWORD"
+  postdata="$postdata&loginTarget="
+  #_debug postdata "$postdata"
+
+  response="$(_post "$postdata" "https://www.one.com/admin/login.do" "" "POST" "application/x-www-form-urlencoded")"
+  #_debug response "$response"
+
+  JSESSIONID="$(grep "JSESSIONID" "$HTTP_HEADER" | grep "^[Ss]et-[Cc]ookie:" | _tail_n 1 | _egrep_o 'JSESSIONID=[^;]*;' | tr -d ';')"
+  _debug jsessionid "$JSESSIONID"
+
+  export _H1="Cookie: ${JSESSIONID}"
+
+  # get entries
+  response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
+  _debug response "$response"
+
+  CSRF_G_TOKEN="$(grep "CSRF_G_TOKEN=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'CSRF_G_TOKEN=[^;]*;' | tr -d ';')"
+  export _H2="Cookie: ${CSRF_G_TOKEN}"
+
+  # Update the IP address for domain entry
+  postdata="{\"type\":\"dns_custom_records\",\"attributes\":{\"priority\":0,\"ttl\":600,\"type\":\"TXT\",\"prefix\":\"$mysubdomain\",\"content\":\"$txtvalue\"}}"
+  _debug postdata "$postdata"
+  response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records" "" "POST" "application/json")"
+  response="$(echo "$response" | _normalizeJson)"
+  _debug response "$response"
+
+  id=$(printf -- "%s" "$response" | sed -n "s/{\"result\":{\"data\":{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}}},\"metadata\":null}/\1/p")
+
+  if [ -z "$id" ]; then
+    _err "Add txt record error."
+    return 1
+  else
+    _info "Added, OK ($id)"
+    return 0
+  fi
+
+}
+
+dns_one_rm() {
+  mysubdomain=$(printf -- "%s" "$1" | rev | cut -d"." -f3- | rev)
+  mydomain=$(printf -- "%s" "$1" | rev | cut -d"." -f1-2 | rev)
+  txtvalue=$2
+
+  # get credentials
+  ONECOM_USER="${ONECOM_USER:-$(_readaccountconf_mutable ONECOM_USER)}"
+  ONECOM_PASSWORD="${ONECOM_PASSWORD:-$(_readaccountconf_mutable ONECOM_PASSWORD)}"
+  if [ -z "$ONECOM_USER" ] || [ -z "$ONECOM_PASSWORD" ]; then
+    ONECOM_USER=""
+    ONECOM_PASSWORD=""
+    _err "You didn't specify a one.com username and password yet."
+    _err "Please create the key and try again."
+    return 1
+  fi
+
+  # Login with user and password
+  postdata="loginDomain=true"
+  postdata="$postdata&displayUsername=$ONECOM_USER"
+  postdata="$postdata&username=$ONECOM_USER"
+  postdata="$postdata&targetDomain=$mydomain"
+  postdata="$postdata&password1=$ONECOM_PASSWORD"
+  postdata="$postdata&loginTarget="
+
+  response="$(_post "$postdata" "https://www.one.com/admin/login.do" "" "POST" "application/x-www-form-urlencoded")"
+  #_debug response "$response"
+
+  JSESSIONID="$(grep "JSESSIONID" "$HTTP_HEADER" | grep "^[Ss]et-[Cc]ookie:" | _tail_n 1 | _egrep_o 'JSESSIONID=[^;]*;' | tr -d ';')"
+  _debug jsessionid "$JSESSIONID"
+
+  export _H1="Cookie: ${JSESSIONID}"
+
+  # get entries
+  response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
+  response="$(echo "$response" | _normalizeJson)"
+  _debug response "$response"
+
+  CSRF_G_TOKEN="$(grep "CSRF_G_TOKEN=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'CSRF_G_TOKEN=[^;]*;' | tr -d ';')"
+  export _H2="Cookie: ${CSRF_G_TOKEN}"
+
+  id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}.*/\1/p")
+
+  if [ -z "$id" ]; then
+    _err "Txt record not found."
+    return 1
+  fi
+
+  # delete entry
+  response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records/$id" "" "DELETE" "application/json")"
+  response="$(echo "$response" | _normalizeJson)"
+  _debug response "$response"
+
+  if [ "$response" = '{"result":null,"metadata":null}' ]; then
+    _info "Removed, OK"
+    return 0
+  else
+    _err "Removing txt record error."
+    return 1
+  fi
+
+}

+ 261 - 0
dnsapi/dns_schlundtech.sh

@@ -0,0 +1,261 @@
+#!/usr/bin/env sh
+# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
+
+# Schlundtech DNS API
+# Author: mod242
+# Created: 2019-40-29
+# Completly based on the autoDNS xml api wrapper by auerswald@gmail.com
+#
+#     export SCHLUNDTECH_USER="username"
+#     export SCHLUNDTECH_PASSWORD="password"
+#
+# Usage:
+#     acme.sh --issue --dns dns_schlundtech -d example.com
+
+SCHLUNDTECH_API="https://gateway.schlundtech.de"
+
+# Arguments:
+#   txtdomain
+#   txt
+dns_schlundtech_add() {
+  fulldomain="$1"
+  txtvalue="$2"
+
+  SCHLUNDTECH_USER="${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}"
+  SCHLUNDTECH_PASSWORD="${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}"
+
+  if [ -z "$SCHLUNDTECH_USER" ] || [ -z "$SCHLUNDTECH_PASSWORD" ]; then
+    _err "You didn't specify schlundtech user and password."
+    return 1
+  fi
+
+  _saveaccountconf_mutable SCHLUNDTECH_USER "$SCHLUNDTECH_USER"
+  _saveaccountconf_mutable SCHLUNDTECH_PASSWORD "$SCHLUNDTECH_PASSWORD"
+
+  _debug "First detect the root zone"
+
+  if ! _get_autodns_zone "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _zone "$_zone"
+  _debug _system_ns "$_system_ns"
+
+  _info "Adding TXT record"
+
+  autodns_response="$(_autodns_zone_update "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")"
+
+  if [ "$?" -eq "0" ]; then
+    _info "Added, OK"
+    return 0
+  fi
+
+  return 1
+}
+
+# Arguments:
+#   txtdomain
+#   txt
+dns_schlundtech_rm() {
+  fulldomain="$1"
+  txtvalue="$2"
+
+  SCHLUNDTECH_USER="${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}"
+  SCHLUNDTECH_PASSWORD="${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}"
+
+  if [ -z "$SCHLUNDTECH_USER" ] || [ -z "$SCHLUNDTECH_PASSWORD" ]; then
+    _err "You didn't specify schlundtech user and password."
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+
+  if ! _get_autodns_zone "$fulldomain"; then
+    _err "zone not found"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _zone "$_zone"
+  _debug _system_ns "$_system_ns"
+
+  _info "Delete TXT record"
+
+  autodns_response="$(_autodns_zone_cleanup "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")"
+
+  if [ "$?" -eq "0" ]; then
+    _info "Deleted, OK"
+    return 0
+  fi
+
+  return 1
+}
+
+####################  Private functions below ##################################
+
+# Arguments:
+#   fulldomain
+# Returns:
+#   _sub_domain=_acme-challenge.www
+#   _zone=domain.com
+#   _system_ns
+_get_autodns_zone() {
+  domain="$1"
+
+  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
+
+    autodns_response="$(_autodns_zone_inquire "$h")"
+
+    if [ "$?" -ne "0" ]; then
+      _err "invalid domain"
+      return 1
+    fi
+
+    if _contains "$autodns_response" "<summary>1</summary>" >/dev/null; then
+      _zone="$(echo "$autodns_response" | _egrep_o '<name>[^<]*</name>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
+      _system_ns="$(echo "$autodns_response" | _egrep_o '<system_ns>[^<]*</system_ns>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      return 0
+    fi
+
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+
+  return 1
+}
+
+_build_request_auth_xml() {
+  printf "<auth>
+    <user>%s</user>
+    <password>%s</password>
+    <context>10</context>
+  </auth>" "$SCHLUNDTECH_USER" "$SCHLUNDTECH_PASSWORD"
+}
+
+# Arguments:
+#   zone
+_build_zone_inquire_xml() {
+  printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+  <request>
+    %s
+    <task>
+      <code>0205</code>
+      <view>
+        <children>1</children>
+        <limit>1</limit>
+      </view>
+      <where>
+        <key>name</key>
+        <operator>eq</operator>
+        <value>%s</value>
+      </where>
+    </task>
+  </request>" "$(_build_request_auth_xml)" "$1"
+}
+
+# Arguments:
+#   zone
+#   subdomain
+#   txtvalue
+#   system_ns
+_build_zone_update_xml() {
+  printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+  <request>
+    %s
+    <task>
+      <code>0202001</code>
+      <default>
+        <rr_add>
+          <name>%s</name>
+          <ttl>600</ttl>
+          <type>TXT</type>
+          <value>%s</value>
+        </rr_add>
+      </default>
+      <zone>
+        <name>%s</name>
+        <system_ns>%s</system_ns>
+      </zone>
+    </task>
+  </request>" "$(_build_request_auth_xml)" "$2" "$3" "$1" "$4"
+}
+
+# Arguments:
+#   zone
+_autodns_zone_inquire() {
+  request_data="$(_build_zone_inquire_xml "$1")"
+  autodns_response="$(_autodns_api_call "$request_data")"
+  ret="$?"
+
+  printf "%s" "$autodns_response"
+  return "$ret"
+}
+
+# Arguments:
+#   zone
+#   subdomain
+#   txtvalue
+#   system_ns
+_autodns_zone_update() {
+  request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")"
+  autodns_response="$(_autodns_api_call "$request_data")"
+  ret="$?"
+
+  printf "%s" "$autodns_response"
+  return "$ret"
+}
+
+# Arguments:
+#   zone
+#   subdomain
+#   txtvalue
+#   system_ns
+_autodns_zone_cleanup() {
+  request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")"
+  # replace 'rr_add>' with 'rr_rem>' in request_data
+  request_data="$(printf -- "%s" "$request_data" | sed 's/rr_add>/rr_rem>/g')"
+  autodns_response="$(_autodns_api_call "$request_data")"
+  ret="$?"
+
+  printf "%s" "$autodns_response"
+  return "$ret"
+}
+
+# Arguments:
+#   request_data
+_autodns_api_call() {
+  request_data="$1"
+
+  _debug request_data "$request_data"
+
+  autodns_response="$(_post "$request_data" "$SCHLUNDTECH_API")"
+  ret="$?"
+
+  _debug autodns_response "$autodns_response"
+
+  if [ "$ret" -ne "0" ]; then
+    _err "error"
+    return 1
+  fi
+
+  if _contains "$autodns_response" "<type>success</type>" >/dev/null; then
+    _info "success"
+    printf "%s" "$autodns_response"
+    return 0
+  fi
+
+  return 1
+}