From 192ede5e64a5b63752dcf7b4c750c616f920db37 Mon Sep 17 00:00:00 2001 From: sjau Date: Thu, 24 Nov 2016 16:00:32 +0100 Subject: [PATCH 1/3] Added ISPConfig DNS API --- README.md | 1 + dnsapi/README.md | 26 ++++++- dnsapi/dns_ispconfig.sh | 158 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100755 dnsapi/dns_ispconfig.sh diff --git a/README.md b/README.md index 724f6ee..859e8fe 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ You don't have to do anything manually! 1. DNSMadeEasy.com API 1. nsupdate API 1. aliyun.com(阿里云) API +1. ISPConfig 3.1 API **More APIs coming soon...** diff --git a/dnsapi/README.md b/dnsapi/README.md index ca9b08d..dbd27fc 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -218,7 +218,29 @@ acme.sh --issue --dns dns_ali -d example.com -d www.example.com The `Ali_Key` and `Ali_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -# 12. Use custom API +## 12. Use ISPConfig 3.1 API + +This only works for ISPConfig 3.1 (and newer). + +Create a Remote User in the ISPConfig Control Panel. The Remote User must have access to at least `DNS zone functions` and `DNS txt functions`. + +``` +export ISPC_User="xxx" +export ISPC_Password="xxx" +export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" +export ISPC_Api_Insecure=1 +``` +If you have installed ISPConfig on a different port, then alter the 8080 accordingly. +Leaver ISPC_Api_Insecure set to 1 if you have not a valid ssl cert for your installation. Change it to 0 if you have a valid ssl cert. + +To issue a cert: +``` +acme.sh --issue --dns dns_ispconfig -d example.com -d www.example.com +``` + +The `ISPC_User`, `ISPC_Password`, `ISPC_Api`and `ISPC_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +# 13. Use custom API If your API is not supported yet, you can write your own DNS API. @@ -235,6 +257,6 @@ acme.sh --issue --dns dns_myapi -d example.com -d www.example.com For more details, please check our sample script: [dns_myapi.sh](dns_myapi.sh) -## 13. Use lexicon DNS API +## 14. Use lexicon DNS API https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh new file mode 100755 index 0000000..eb55d35 --- /dev/null +++ b/dnsapi/dns_ispconfig.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env sh + +# ISPConfig 3.1 API +# User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to: +# - DNS zone Functions +# - DNS txt Functions + +# Report bugs to https://github.com/sjau/acme.sh + +# Values to export: +# export ISPC_User="remoteUser" +# export ISPC_Password="remotePasword" +# export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" +# export ISPC_Api_Insecure=1 # Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) + +######## Public functions ##################### + +#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_ispconfig_add() { + fulldomain="${1}" + txtvalue="${2}" + _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt +} + +#Usage: dns_myapi_rm _acme-challenge.www.domain.com +dns_ispconfig_rm() { + fulldomain="${1}" + _ISPC_credentials && _ISPC_login && _ISPC_rmTxt +} + +#################### Private functions bellow ################################## + +_ISPC_credentials() { + if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then + ISPC_User="" + ISPC_Password="" + ISPC_Api="" + ISPC_Api_Insecure="" + _err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again." + return 1 + else + _saveaccountconf ISPC_User "${ISPC_User}" + _saveaccountconf ISPC_Password "${ISPC_Password}" + _saveaccountconf ISPC_Api "${ISPC_Api}" + _saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}" + # Set whether curl should use secure or insecure mode + HTTPS_INSECURE="${ISPC_Api_Insecure}" + fi +} + +_ISPC_login() { + _info "Getting Session ID" + curData="{\"username\":\"${ISPC_User}\",\"password\":\"${ISPC_Password}\",\"client_login\":false}" + curResult="$(_post "${curData}" "${ISPC_Api}?login")" + if _contains "${curResult}" '"code":"ok"'; then + sessionID=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + _info "Retrieved Session ID." + else + _err "Couldn't retrieve the Session ID." + return 1 + fi +} + +_ISPC_getZoneInfo() { + _info "Getting Zoneinfo" + zoneEnd=false + curZone="${fulldomain}" + while [ "${zoneEnd}" = false ]; do + # we can strip the first part of the fulldomain, since it's just the _acme-challenge string + curZone="${curZone#*.}" + # suffix . needed for zone -> domain.tld. + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":[{\"origin\":\"${curZone}.\"}]}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")" + if _contains "${curResult}" '"id":"'; then + zoneFound=true + zoneEnd=true + _info "Retrieved zone data." + fi + if [ "${curZone#*.}" != "$curZone" ]; then + _debug2 "$curZone still contains a '.' - so we can check next higher level" + else + zoneEnd=true + _err "Couldn't retrieve zone data." + return 1 + fi + done + if [ "${zoneFound}" ]; then + server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + case "${server_id}" in + '' | *[!0-9]*) + _err "Server ID is not numeric." + return 1 + ;; + *) _info "Retrieved Server ID" ;; + esac + zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + case "${zone}" in + '' | *[!0-9]*) + _err "Zone ID is not numeric." + return 1 + ;; + *) _info "Retrieved Zone ID" ;; + esac + client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + case "${client_id}" in + '' | *[!0-9]*) + _err "Client ID is not numeric." + return 1 + ;; + *) _info "Retrieved Client ID." ;; + esac + zoneFound="" + zoneEnd="" + fi +} + +_ISPC_addTxt() { + curSerial="$(date +%s)" + curStamp="$(date +'%F %T')" + params="\"server_id\":\"${server_id}\",\"zone\":\"${zone}\",\"name\":\"${fulldomain}.\",\"type\":\"txt\",\"data\":\"${txtvalue}\",\"aux\":\"0\",\"ttl\":\"3600\",\"active\":\"y\",\"stamp\":\"${curStamp}\",\"serial\":\"${curSerial}\"" + curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}}}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")" + record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + case "${record_id}" in + '' | *[!0-9]*) + _err "Couldn't add ACME Challenge TXT record to zone." + return 1 + ;; + *) _info "Added ACME Challenge TXT record to zone." ;; + esac +} + +_ISPC_rmTxt() { + # Need to get the record ID. + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"name\":\"${fulldomain}.\",\"type\":\"TXT\"}}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_get")" + if _contains "${curResult}" '"code":"ok"'; then + record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + case "${record_id}" in + '' | *[!0-9]*) + _err "Record ID is not numeric." + return 1 + ;; + *) + unset IFS + _info "Retrieved Record ID." + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")" + if _contains "${curResult}" '"code":"ok"'; then + _info "Removed ACME Challenge TXT record from zone." + else + _err "Couldn't remove ACME Challenge TXT record from zone." + return 1 + fi + ;; + esac + fi +} From 983f1f28ca273d4b4461cd9fb5a4211335ed9904 Mon Sep 17 00:00:00 2001 From: sjau Date: Thu, 24 Nov 2016 18:02:42 +0100 Subject: [PATCH 2/3] Fixed wrong zone getting JSON and added lots of debug info --- dnsapi/dns_ispconfig.sh | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index eb55d35..315dd6e 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/dnsapi/dns_ispconfig.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash # ISPConfig 3.1 API # User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to: @@ -9,7 +9,7 @@ # Values to export: # export ISPC_User="remoteUser" -# export ISPC_Password="remotePasword" +# export ISPC_Password="remotePassword" # export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" # export ISPC_Api_Insecure=1 # Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) @@ -19,12 +19,14 @@ dns_ispconfig_add() { fulldomain="${1}" txtvalue="${2}" + _debug "Calling: dns_ispconfig_add() '${fulldomain}' '${txtvalue}'" _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt } #Usage: dns_myapi_rm _acme-challenge.www.domain.com dns_ispconfig_rm() { fulldomain="${1}" + _debug "Calling: dns_ispconfig_rm() '${fulldomain}'" _ISPC_credentials && _ISPC_login && _ISPC_rmTxt } @@ -52,9 +54,12 @@ _ISPC_login() { _info "Getting Session ID" curData="{\"username\":\"${ISPC_User}\",\"password\":\"${ISPC_Password}\",\"client_login\":false}" curResult="$(_post "${curData}" "${ISPC_Api}?login")" + _debug "Calling _ISPC_login: '${curData}' '${ISPC_Api}?login'" + _debug "Result of _ISPC_login: '$curResult'" if _contains "${curResult}" '"code":"ok"'; then sessionID=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _info "Retrieved Session ID." + _debug "Session ID: '${sessionID}'" else _err "Couldn't retrieve the Session ID." return 1 @@ -69,12 +74,15 @@ _ISPC_getZoneInfo() { # we can strip the first part of the fulldomain, since it's just the _acme-challenge string curZone="${curZone#*.}" # suffix . needed for zone -> domain.tld. - curData="{\"session_id\":\"${sessionID}\",\"primary_id\":[{\"origin\":\"${curZone}.\"}]}" + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}" curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")" + _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?login'" + _debug "Result of _ISPC_getZoneInfo: '$curResult'" if _contains "${curResult}" '"id":"'; then zoneFound=true zoneEnd=true _info "Retrieved zone data." + _debug "Zone data: '${curResult}'" fi if [ "${curZone#*.}" != "$curZone" ]; then _debug2 "$curZone still contains a '.' - so we can check next higher level" @@ -86,6 +94,7 @@ _ISPC_getZoneInfo() { done if [ "${zoneFound}" ]; then server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + _debug "Server ID: '${server_id}'" case "${server_id}" in '' | *[!0-9]*) _err "Server ID is not numeric." @@ -94,6 +103,7 @@ _ISPC_getZoneInfo() { *) _info "Retrieved Server ID" ;; esac zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + _debug "Zone: '${zone}'" case "${zone}" in '' | *[!0-9]*) _err "Zone ID is not numeric." @@ -102,6 +112,7 @@ _ISPC_getZoneInfo() { *) _info "Retrieved Zone ID" ;; esac client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + _debug "Client ID: '${client_id}'" case "${client_id}" in '' | *[!0-9]*) _err "Client ID is not numeric." @@ -120,7 +131,10 @@ _ISPC_addTxt() { params="\"server_id\":\"${server_id}\",\"zone\":\"${zone}\",\"name\":\"${fulldomain}.\",\"type\":\"txt\",\"data\":\"${txtvalue}\",\"aux\":\"0\",\"ttl\":\"3600\",\"active\":\"y\",\"stamp\":\"${curStamp}\",\"serial\":\"${curSerial}\"" curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}}}" curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")" + _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'" + _debug "Result of _ISPC_addTxt: '$curResult'" record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + _debug "Record ID: '${record_id}'" case "${record_id}" in '' | *[!0-9]*) _err "Couldn't add ACME Challenge TXT record to zone." @@ -134,8 +148,11 @@ _ISPC_rmTxt() { # Need to get the record ID. curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"name\":\"${fulldomain}.\",\"type\":\"TXT\"}}" curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_get")" + _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_get'" + _debug "Result of _ISPC_rmTxt: '$curResult'" if _contains "${curResult}" '"code":"ok"'; then record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + _debug "Record ID: '${record_id}'" case "${record_id}" in '' | *[!0-9]*) _err "Record ID is not numeric." @@ -146,6 +163,8 @@ _ISPC_rmTxt() { _info "Retrieved Record ID." curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}" curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")" + _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'" + _debug "Result of _ISPC_rmTxt: '$curResult'" if _contains "${curResult}" '"code":"ok"'; then _info "Removed ACME Challenge TXT record from zone." else From 5eed02f7e927742facd9d938a6e692a771271377 Mon Sep 17 00:00:00 2001 From: sjau Date: Fri, 25 Nov 2016 04:50:05 +0100 Subject: [PATCH 3/3] Changing shebang back to sh --- dnsapi/dns_ispconfig.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index 315dd6e..789b626 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/dnsapi/dns_ispconfig.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh # ISPConfig 3.1 API # User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to: