Browse Source

Merge branch 'dev' into multideploy-yaml

pull/6241/head
tomo 2 months ago
committed by GitHub
parent
commit
1bb5dd8416
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      .github/workflows/pr_dns.yml
  2. 62
      .github/workflows/wiki-monitor.yml
  3. 38
      acme.sh
  4. 98
      deploy/kemplm.sh
  5. 77
      deploy/truenas_ws.sh
  6. 98
      deploy/vault.sh
  7. 500
      deploy/zyxel_gs1900.sh
  8. 4
      dnsapi/dns_1984hosting.sh
  9. 160
      dnsapi/dns_active24.sh
  10. 13
      dnsapi/dns_azure.sh
  11. 2
      dnsapi/dns_beget.sh
  12. 2
      dnsapi/dns_bookmyname.sh
  13. 5
      dnsapi/dns_cloudns.sh
  14. 5
      dnsapi/dns_constellix.sh
  15. 2
      dnsapi/dns_ddnss.sh
  16. 2
      dnsapi/dns_dnshome.sh
  17. 2
      dnsapi/dns_duckdns.sh
  18. 2
      dnsapi/dns_dyn.sh
  19. 2
      dnsapi/dns_dynv6.sh
  20. 2
      dnsapi/dns_easydns.sh
  21. 163
      dnsapi/dns_edgecenter.sh
  22. 2
      dnsapi/dns_fornex.sh
  23. 2
      dnsapi/dns_freedns.sh
  24. 4
      dnsapi/dns_freemyip.sh
  25. 1
      dnsapi/dns_he_ddns.sh
  26. 2
      dnsapi/dns_joker.sh
  27. 96
      dnsapi/dns_la.sh
  28. 9
      dnsapi/dns_mijnhost.sh
  29. 2
      dnsapi/dns_mydnsjp.sh
  30. 2
      dnsapi/dns_namecom.sh
  31. 2
      dnsapi/dns_namesilo.sh
  32. 186
      dnsapi/dns_openprovider_rest.sh
  33. 2
      dnsapi/dns_pleskxml.sh
  34. 18
      dnsapi/dns_rage4.sh
  35. 2
      dnsapi/dns_schlundtech.sh
  36. 38
      dnsapi/dns_selectel.sh
  37. 212
      dnsapi/dns_spaceship.sh
  38. 2
      dnsapi/dns_tele3.sh
  39. 2
      dnsapi/dns_tencent.sh
  40. 2
      dnsapi/dns_timeweb.sh
  41. 4
      dnsapi/dns_transip.sh
  42. 2
      dnsapi/dns_udr.sh
  43. 2
      dnsapi/dns_variomedia.sh
  44. 2
      dnsapi/dns_vscale.sh
  45. 1
      dnsapi/dns_vultr.sh
  46. 2
      dnsapi/dns_websupport.sh
  47. 2
      dnsapi/dns_world4you.sh

6
.github/workflows/pr_dns.yml

@ -20,11 +20,13 @@ jobs:
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: `**Welcome** body: `**Welcome**
READ ME !!!!!
Read me !!!!!!
First thing: don't send PR to the master branch, please send to the dev branch instead. First thing: don't send PR to the master branch, please send to the dev branch instead.
Please make sure you've read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test).
Please read the [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide).
You MUST pass the [DNS-API-Test](../wiki/DNS-API-Test).
Then reply on this message, otherwise, your code will not be reviewed or merged. Then reply on this message, otherwise, your code will not be reviewed or merged.
Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2 Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2
We look forward to reviewing your Pull request shortly ✨
注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试. 注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.
` `
}) })

62
.github/workflows/wiki-monitor.yml

@ -0,0 +1,62 @@
name: Notify via Issue on Wiki Edit
on:
gollum:
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Checkout wiki repository
uses: actions/checkout@v4
with:
repository: ${{ github.repository }}.wiki
path: wiki
fetch-depth: 0
- name: Generate wiki change message
run: |
actor="${{ github.actor }}"
sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH")
page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH")
page_sha=$(jq -r '.pages[0].sha' "$GITHUB_EVENT_PATH")
page_url=$(jq -r '.pages[0].html_url' "$GITHUB_EVENT_PATH")
page_action=$(jq -r '.pages[0].action' "$GITHUB_EVENT_PATH")
now="$(date '+%Y-%m-%d %H:%M:%S')"
cd wiki
prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1)
if [ -n "$prev_sha" ]; then
git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff
else
echo "(no diff)" > ../wiki.diff
fi
cd ..
{
echo "Wiki edited"
echo -n "User: "
echo "[$actor]($sender_url)"
echo "Time: $now"
echo "Page: [$page_name]($page_url) (Action: $page_action)"
echo ""
echo "----"
echo "### diff:"
echo '```diff'
cat wiki.diff
echo '```'
} > wiki-change-msg.txt
- name: Create issue to notify Neilpang
uses: peter-evans/create-issue-from-file@v5
with:
title: "Wiki edited"
content-filepath: ./wiki-change-msg.txt
assignees: Neilpang
env:
TZ: Asia/Shanghai

38
acme.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh #!/usr/bin/env sh
VER=3.1.1
VER=3.1.2
PROJECT_NAME="acme.sh" PROJECT_NAME="acme.sh"
@ -1401,6 +1401,12 @@ _ss() {
return 0 return 0
fi fi
if [ "$(uname)" = "AIX" ]; then
_debug "Using: AIX netstat"
netstat -an | grep "^tcp" | grep "LISTEN" | grep "\.$_port "
return 0
fi
if _exists "netstat"; then if _exists "netstat"; then
_debug "Using: netstat" _debug "Using: netstat"
if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then
@ -1805,6 +1811,10 @@ _time() {
# 2022-04-01 08:10:33 to 1648800633 # 2022-04-01 08:10:33 to 1648800633
#or 2022-04-01T08:10:33Z to 1648800633 #or 2022-04-01T08:10:33Z to 1648800633
_date2time() { _date2time() {
#Mac/BSD
if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
return
fi
#Linux #Linux
if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
return return
@ -1814,10 +1824,6 @@ _date2time() {
if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
return return
fi fi
#Mac/BSD
if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
return
fi
#Omnios #Omnios
if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then
return return
@ -2532,15 +2538,17 @@ _startserver() {
_NC="socat" _NC="socat"
if [ "$Le_Listen_V6" ]; then if [ "$Le_Listen_V6" ]; then
_NC="$_NC -6" _NC="$_NC -6"
SOCAT_OPTIONS=TCP6-LISTEN
else else
_NC="$_NC -4" _NC="$_NC -4"
SOCAT_OPTIONS=TCP4-LISTEN
fi fi
if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
_NC="$_NC -d -d -v" _NC="$_NC -d -d -v"
fi fi
SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork
SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork
#Adding bind to local-address #Adding bind to local-address
if [ "$ncaddr" ]; then if [ "$ncaddr" ]; then
@ -2761,7 +2769,7 @@ _initAPI() {
_request_retry_times=0 _request_retry_times=0
while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do
_request_retry_times=$(_math "$_request_retry_times" + 1) _request_retry_times=$(_math "$_request_retry_times" + 1)
response=$(_get "$_api_server")
response=$(_get "$_api_server" "" 10)
if [ "$?" != "0" ]; then if [ "$?" != "0" ]; then
_debug2 "response" "$response" _debug2 "response" "$response"
_info "Cannot init API for: $_api_server." _info "Cannot init API for: $_api_server."
@ -3507,7 +3515,7 @@ _on_before_issue() {
_debug _chk_alt_domains "$_chk_alt_domains" _debug _chk_alt_domains "$_chk_alt_domains"
#run pre hook #run pre hook
if [ "$_chk_pre_hook" ]; then if [ "$_chk_pre_hook" ]; then
_info "Runing pre hook:'$_chk_pre_hook'"
_info "Running pre hook:'$_chk_pre_hook'"
if ! ( if ! (
export Le_Domain="$_chk_main_domain" export Le_Domain="$_chk_main_domain"
export Le_Alt="$_chk_alt_domains" export Le_Alt="$_chk_alt_domains"
@ -4496,6 +4504,7 @@ issue() {
if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then
_err "_on_before_issue." _err "_on_before_issue."
_on_issue_err "$_post_hook"
return 1 return 1
fi fi
@ -4755,7 +4764,8 @@ $_authorizations_map"
_debug keyauthorization "$keyauthorization" _debug keyauthorization "$keyauthorization"
fi fi
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
# Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
_debug entry "$entry" _debug entry "$entry"
if [ -z "$keyauthorization" -a -z "$entry" ]; then if [ -z "$keyauthorization" -a -z "$entry" ]; then
@ -5504,6 +5514,13 @@ renew() {
if [ -z "$Le_Keylength" ]; then if [ -z "$Le_Keylength" ]; then
Le_Keylength=2048 Le_Keylength=2048
fi fi
if [ "$CA_LETSENCRYPT_V2" = "$Le_API" ]; then
#letsencrypt doesn't support ocsp anymore
if [ "$Le_OCSP_Staple" ]; then
export Le_OCSP_Staple=""
_cleardomainconf Le_OCSP_Staple
fi
fi
issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To"
res="$?" res="$?"
if [ "$res" != "0" ]; then if [ "$res" != "0" ]; then
@ -6337,7 +6354,8 @@ _deactivate() {
fi fi
_debug "Trigger validation." _debug "Trigger validation."
vtype="$(_getIdType "$_d_domain")" vtype="$(_getIdType "$_d_domain")"
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
# Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
_debug entry "$entry" _debug entry "$entry"
if [ -z "$entry" ]; then if [ -z "$entry" ]; then
_err "$d: Cannot get domain token" _err "$d: Cannot get domain token"

98
deploy/kemplm.sh

@ -0,0 +1,98 @@
#!/usr/bin/env sh
#Here is a script to deploy cert to a Kemp Loadmaster.
#returns 0 means success, otherwise error.
#DEPLOY_KEMP_TOKEN="token"
#DEPLOY_KEMP_URL="https://kemplm.example.com"
######## Public functions #####################
#domain keyfile certfile cafile fullchain
kemplm_deploy() {
_domain="$1"
_key_file="$2"
_cert_file="$3"
_ca_file="$4"
_fullchain_file="$5"
_debug _domain "$_domain"
_debug _key_file "$_key_file"
_debug _cert_file "$_cert_file"
_debug _ca_file "$_ca_file"
_debug _fullchain_file "$_fullchain_file"
if ! _exists jq; then
_err "jq not found"
return 1
fi
# Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename
_kemp_domain=$(echo "${_domain}" | sed 's/\*\.//')
_debug _kemp_domain "$_kemp_domain"
# Read config from saved values or env
_getdeployconf DEPLOY_KEMP_TOKEN
_getdeployconf DEPLOY_KEMP_URL
_debug DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
_secure_debug DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
if [ -z "$DEPLOY_KEMP_TOKEN" ]; then
_err "Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN."
return 1
fi
if [ -z "$DEPLOY_KEMP_URL" ]; then
_err "Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL."
return 1
fi
# Save current values
_savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
_savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
# Check if certificate is already installed
_info "Check if certificate is already present"
_list_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}"
_debug3 _list_request "${_list_request}"
_kemp_cert_count=$(HTTPS_INSECURE=1 _post "${_list_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "${_kemp_domain}")
_debug2 _kemp_cert_count "${_kemp_cert_count}"
_kemp_replace_cert=1
if [ "${_kemp_cert_count}" -eq 0 ]; then
_kemp_replace_cert=0
_info "Certificate does not exist on Kemp Loadmaster"
else
_info "Certificate already exists on Kemp Loadmaster"
fi
_debug _kemp_replace_cert "${_kemp_replace_cert}"
# Upload new certificate to Kemp Loadmaster
_kemp_upload_cert=$(_mktemp)
cat "${_fullchain_file}" "${_key_file}" | base64 | tr -d '\n' >"${_kemp_upload_cert}"
_info "Uploading certificate to Kemp Loadmaster"
_add_data=$(cat "${_kemp_upload_cert}")
_add_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_add_data}\"}"
_debug3 _add_request "${_add_request}"
_kemp_post_result=$(HTTPS_INSECURE=1 _post "${_add_request}" "${DEPLOY_KEMP_URL}/accessv2")
_retval=$?
_debug2 _kemp_post_result "${_kemp_post_result}"
if [ "${_retval}" -eq 0 ]; then
_kemp_post_status=$(echo "${_kemp_post_result}" | jq -r '.status')
_kemp_post_message=$(echo "${_kemp_post_result}" | jq -r '.message')
if [ "${_kemp_post_status}" = "ok" ]; then
_info "Upload successful"
else
_err "Upload failed: ${_kemp_post_message}"
fi
else
_err "Upload failed"
_retval=1
fi
rm "${_kemp_upload_cert}"
return $_retval
}

77
deploy/truenas_ws.sh

@ -52,6 +52,39 @@ _ws_call() {
return 0 return 0
} }
# Upload certificate with webclient api
_ws_upload_cert() {
/usr/bin/env python - <<EOF
import sys
from truenas_api_client import Client
with Client() as c:
### Login with API key
print("I:Trying to upload new certificate...")
ret = c.call("auth.login_with_api_key", "${DEPLOY_TRUENAS_APIKEY}")
if ret:
### upload certificate
with open('$1', 'r') as file:
fullchain = file.read()
with open('$2', 'r') as file:
privatekey = file.read()
ret = c.call("certificate.create", {"name": "$3", "create_type": "CERTIFICATE_CREATE_IMPORTED", "certificate": fullchain, "privatekey": privatekey, "passphrase": ""}, job=True)
print("R:" + str(ret["id"]))
sys.exit(0)
else:
print("R:0")
print("E:_ws_upload_cert error!")
sys.exit(7)
EOF
return $?
}
# Check argument is a number # Check argument is a number
# Usage: # Usage:
# #
@ -129,7 +162,6 @@ _ws_get_job_result() {
# 5: WebUI cert error # 5: WebUI cert error
# 6: Job error # 6: Job error
# 7: WS call error # 7: WS call error
# 10: No CORE or SCALE detected
# #
truenas_ws_deploy() { truenas_ws_deploy() {
_domain="$1" _domain="$1"
@ -179,14 +211,8 @@ truenas_ws_deploy() {
_info "Gather system info..." _info "Gather system info..."
_ws_response=$(_ws_call "system.info") _ws_response=$(_ws_call "system.info")
_truenas_system=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 2 | tr '[:lower:]' '[:upper:]')
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 3)
_info "TrueNAS system: $_truenas_system"
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"')
_info "TrueNAS version: $_truenas_version" _info "TrueNAS version: $_truenas_version"
if [ "$_truenas_system" != "SCALE" ] && [ "$_truenas_system" != "CORE" ]; then
_err "Cannot gather TrueNAS system. Nor CORE oder SCALE detected."
return 10
fi
########## Gather current certificate ########## Gather current certificate
@ -203,19 +229,26 @@ truenas_ws_deploy() {
_certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')" _certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')"
_info "New WebUI certificate name: $_certname" _info "New WebUI certificate name: $_certname"
_debug _certname "$_certname" _debug _certname "$_certname"
_ws_jobid=$(_ws_call "certificate.create" "{\"name\": \"${_certname}\", \"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"certificate\": \"$(_json_encode <"$_file_fullchain")\", \"privatekey\": \"$(_json_encode <"$_file_key")\", \"passphrase\": \"\"}")
_debug "_ws_jobid" "$_ws_jobid"
if ! _ws_check_jobid "$_ws_jobid"; then
_err "No JobID returned from websocket method."
return 3
fi
_ws_result=$(_ws_get_job_result "$_ws_jobid")
_ws_ret=$?
if [ $_ws_ret -gt 0 ]; then
return $_ws_ret
fi
_debug "_ws_result" "$_ws_result"
_new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"')
_ws_out=$(_ws_upload_cert "$_file_fullchain" "$_file_key" "$_certname")
echo "$_ws_out" | while IFS= read -r LINE; do
case "$LINE" in
I:*)
_info "${LINE#I:}"
;;
D:*)
_debug "${LINE#D:}"
;;
E*)
_err "${LINE#E:}"
;;
*) ;;
esac
done
_new_certid=$(echo "$_ws_out" | grep 'R:' | cut -d ':' -f 2)
_info "New certificate ID: $_new_certid" _info "New certificate ID: $_new_certid"
########## FTP ########## FTP
@ -231,7 +264,6 @@ truenas_ws_deploy() {
########## ix Apps (SCALE only) ########## ix Apps (SCALE only)
if [ "$_truenas_system" = "SCALE" ]; then
_info "Replace app certificates..." _info "Replace app certificates..."
_ws_response=$(_ws_call "app.query") _ws_response=$(_ws_call "app.query")
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do
@ -257,7 +289,6 @@ truenas_ws_deploy() {
_info "App has no certificate option, skipping..." _info "App has no certificate option, skipping..."
fi fi
done done
fi
########## WebUI ########## WebUI

98
deploy/vault.sh

@ -80,10 +80,15 @@ vault_deploy() {
if [ -n "$VAULT_RENEW_TOKEN" ]; then if [ -n "$VAULT_RENEW_TOKEN" ]; then
URL="$VAULT_ADDR/v1/auth/token/renew-self" URL="$VAULT_ADDR/v1/auth/token/renew-self"
_info "Renew the Vault token to default TTL" _info "Renew the Vault token to default TTL"
if ! _post "" "$URL" >/dev/null; then
_response=$(_post "" "$URL")
if [ "$?" != "0" ]; then
_err "Failed to renew the Vault token" _err "Failed to renew the Vault token"
return 1 return 1
fi fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Failed to renew the Vault token: $_response"
return 1
fi
fi fi
URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain" URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain"
@ -91,29 +96,85 @@ vault_deploy() {
if [ -n "$VAULT_FABIO_MODE" ]; then if [ -n "$VAULT_FABIO_MODE" ]; then
_info "Writing certificate and key to $URL in Fabio mode" _info "Writing certificate and key to $URL in Fabio mode"
if [ -n "$VAULT_KV_V2" ]; then if [ -n "$VAULT_KV_V2" ]; then
_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL" >/dev/null || return 1
_response=$(_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error: $_response"
return 1
fi
else else
_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" >/dev/null || return 1
_response=$(_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error: $_response"
return 1
fi
fi fi
else else
if [ -n "$VAULT_KV_V2" ]; then if [ -n "$VAULT_KV_V2" ]; then
_info "Writing certificate to $URL/cert.pem" _info "Writing certificate to $URL/cert.pem"
_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem" >/dev/null || return 1
_response=$(_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing cert.pem: $_response"
return 1
fi
_info "Writing key to $URL/cert.key" _info "Writing key to $URL/cert.key"
_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key" >/dev/null || return 1
_response=$(_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing cert.key: $_response"
return 1
fi
_info "Writing CA certificate to $URL/ca.pem" _info "Writing CA certificate to $URL/ca.pem"
_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem" >/dev/null || return 1
_response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing ca.pem: $_response"
return 1
fi
_info "Writing full-chain certificate to $URL/fullchain.pem" _info "Writing full-chain certificate to $URL/fullchain.pem"
_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem" >/dev/null || return 1
_response=$(_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing fullchain.pem: $_response"
return 1
fi
else else
_info "Writing certificate to $URL/cert.pem" _info "Writing certificate to $URL/cert.pem"
_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem" >/dev/null || return 1
_response=$(_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing cert.pem: $_response"
return 1
fi
_info "Writing key to $URL/cert.key" _info "Writing key to $URL/cert.key"
_post "{\"value\": \"$_ckey\"}" "$URL/cert.key" >/dev/null || return 1
_response=$(_post "{\"value\": \"$_ckey\"}" "$URL/cert.key")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing cert.key: $_response"
return 1
fi
_info "Writing CA certificate to $URL/ca.pem" _info "Writing CA certificate to $URL/ca.pem"
_post "{\"value\": \"$_cca\"}" "$URL/ca.pem" >/dev/null || return 1
_response=$(_post "{\"value\": \"$_cca\"}" "$URL/ca.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing ca.pem: $_response"
return 1
fi
_info "Writing full-chain certificate to $URL/fullchain.pem" _info "Writing full-chain certificate to $URL/fullchain.pem"
_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem" >/dev/null || return 1
_response=$(_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing fullchain.pem: $_response"
return 1
fi
fi fi
# To make it compatible with the wrong ca path `chain.pem` which was used in former versions # To make it compatible with the wrong ca path `chain.pem` which was used in former versions
@ -121,11 +182,20 @@ vault_deploy() {
_err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning" _err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning"
_info "Updating CA certificate to $URL/chain.pem for backward compatibility" _info "Updating CA certificate to $URL/chain.pem for backward compatibility"
if [ -n "$VAULT_KV_V2" ]; then if [ -n "$VAULT_KV_V2" ]; then
_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem" >/dev/null || return 1
_response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing chain.pem: $_response"
return 1
fi
else else
_post "{\"value\": \"$_cca\"}" "$URL/chain.pem" >/dev/null || return 1
_response=$(_post "{\"value\": \"$_cca\"}" "$URL/chain.pem")
if [ "$?" != "0" ]; then return 1; fi
if echo "$_response" | grep -q '"errors":\['; then
_err "Vault error writing chain.pem: $_response"
return 1
fi
fi fi
fi fi
fi fi
} }

500
deploy/zyxel_gs1900.sh

@ -0,0 +1,500 @@
#!/usr/bin/env sh
# Deploy certificates to Zyxel GS1900 series switches
#
# This script uses the https web administration interface in order
# to upload updated certificates to Zyxel GS1900 series switches.
# Only a few models have been tested but untested switches from the
# same model line may work as well. If you test and confirm a switch
# as working please submit a pull request updating this compatibility
# list!
#
# Known Issues:
# 1. This is a consumer grade switch and is a bit underpowered
# the longer the RSA key size the slower your switch web UI
# will be. RSA 2048 will work, RSA 4096 will work but you may
# experience performance problems.
# 2. You must use RSA certificates. The switch will reject EC-256
# and EC-384 certificates in firmware 2.80
# See: https://community.zyxel.com/en/discussion/21506/bug-cannot-import-ssl-cert-on-gs1900-8-and-gs1900-24e-firmware-v2-80/
#
# Current GS1900 Switch Compatibility:
# GS1900-8 - Working as of firmware V2.80
# GS1900-8HP - Untested
# GS1900-10HP - Untested
# GS1900-16 - Untested
# GS1900-24 - Untested
# GS1900-24E - Working as of firmware V2.80
# GS1900-24EP - Untested
# GS1900-24HP - Untested
# GS1900-48 - Untested
# GS1900-48HP - Untested
#
# Prerequisite Setup Steps:
# 1. Install at least firmware V2.80 on your switch
# 2. Enable HTTPS web management on your switch
#
# Usage:
# 1. Ensure the switch has firmware V2.80 or later.
# 2. Ensure the switch has HTTPS management enabled.
# 3. Set the appropriate environment variables for your environment.
#
# DEPLOY_ZYXEL_SWITCH - The switch hostname. (Default: _cdomain)
# DEPLOY_ZYXEL_SWITCH_USER - The webadmin user. (Default: admin)
# DEPLOY_ZYXEL_SWITCH_PASSWORD - The webadmin password for the switch.
# DEPLOY_ZYXEL_SWITCH_REBOOT - If "1" reboot after update. (Default: "0")
#
# 4. Run the deployment plugin:
# acme.sh --deploy --deploy-hook zyxel_gs1900 -d example.com
#
# returns 0 means success, otherwise error.
#domain keyfile certfile cafile fullchain
zyxel_gs1900_deploy() {
_zyxel_gs1900_minimum_firmware_version="v2.80"
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug2 _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
_getdeployconf DEPLOY_ZYXEL_SWITCH
_getdeployconf DEPLOY_ZYXEL_SWITCH_USER
_getdeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD
_getdeployconf DEPLOY_ZYXEL_SWITCH_REBOOT
if [ -z "$DEPLOY_ZYXEL_SWITCH" ]; then
DEPLOY_ZYXEL_SWITCH="$_cdomain"
fi
if [ -z "$DEPLOY_ZYXEL_SWITCH_USER" ]; then
DEPLOY_ZYXEL_SWITCH_USER="admin"
fi
if [ -z "$DEPLOY_ZYXEL_SWITCH_PASSWORD" ]; then
DEPLOY_ZYXEL_SWITCH_PASSWORD="1234"
fi
if [ -z "$DEPLOY_ZYXEL_SWITCH_REBOOT" ]; then
DEPLOY_ZYXEL_SWITCH_REBOOT="0"
fi
_savedeployconf DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
_savedeployconf DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
_savedeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
_savedeployconf DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
_debug DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
_debug DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
_secure_debug DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
_debug DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
_zyxel_switch_base_uri="https://${DEPLOY_ZYXEL_SWITCH}"
_info "Beginning to deploy to a Zyxel GS1900 series switch at ${_zyxel_switch_base_uri}."
_zyxel_gs1900_deployment_precheck || return $?
_zyxel_gs1900_should_update
if [ "$?" != "0" ]; then
_info "The switch already has our certificate installed. No update required."
return 0
else
_info "The switch does not yet have our certificate installed."
fi
_info "Logging into the switch web interface."
_zyxel_gs1900_login || return $?
_info "Validating the switch is compatible with this deployment process."
_zyxel_gs1900_validate_device_compatibility || return $?
_info "Uploading the certificate."
_zyxel_gs1900_upload_certificate || return $?
if [ "$DEPLOY_ZYXEL_SWITCH_REBOOT" = "1" ]; then
_info "Rebooting the switch."
_zyxel_gs1900_trigger_reboot || return $?
fi
return 0
}
_zyxel_gs1900_deployment_precheck() {
# Initialize the keylength if it isn't already
if [ -z "$Le_Keylength" ]; then
Le_Keylength=""
fi
if _isEccKey "$Le_Keylength"; then
_info "Warning: Zyxel GS1900 switches are not currently known to work with ECC keys!"
_info "You can continue, but your switch may reject your key."
elif [ -n "$Le_Keylength" ] && [ "$Le_Keylength" -gt "2048" ]; then
_info "Warning: Your RSA key length is greater than 2048!"
_info "You can continue, but you may experience performance issues in the web administration interface."
fi
# Check the server for some common failure modes prior to authentication and certificate upload in order to avoid
# sending a certificate when we may not want to.
test_login_response=$(_post "username=test&password=test&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" 2>&1)
test_login_page_exitcode="$?"
_debug3 "Test Login Response: ${test_login_response}"
if [ "$test_login_page_exitcode" -ne "0" ]; then
if { [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "60" ]; } || { [ "${ACME_USE_WGET:-0}" = "1" ] && [ "$test_login_page_exitcode" = "5" ]; }; then
_err "The SSL certificate at $_zyxel_switch_base_uri could not be validated."
_err "Please double check your hostname, port, and that you are actually connecting to your switch."
_err "If the problem persists then please ensure that the certificate is not self-signed, has not"
_err "expired, and matches the switch hostname. If you expect validation to fail then you can disable"
_err "certificate validation by running with --insecure."
return 1
elif [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "56" ]; then
_debug3 "Intentionally ignore curl exit code 56 in our precheck"
else
_err "Failed to submit the initial login attempt to $_zyxel_switch_base_uri."
return 1
fi
fi
}
_zyxel_gs1900_login() {
# Login to the switch and set the appropriate auth cookie in _H1
username_encoded=$(printf "%s" "$DEPLOY_ZYXEL_SWITCH_USER" | _url_encode)
password_encoded=$(_zyxel_gs1900_password_obfuscate "$DEPLOY_ZYXEL_SWITCH_PASSWORD" | _url_encode)
login_response=$(_post "username=${username_encoded}&password=${password_encoded}&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
auth_response=$(_post "authId=${login_response}&login_chk=true" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
if [ "$auth_response" != "OK" ]; then
_err "Login failed due to invalid credentials."
_err "Please double check the configured username and password and try again."
return 1
fi
sessionid=$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'HTTPS_XSSID=[^;]*;' | tr -d ';')
_secure_debug2 "sessionid" "$sessionid"
export _H1="Cookie: $sessionid"
_secure_debug2 "_H1" "$_H1"
return 0
}
_zyxel_gs1900_validate_device_compatibility() {
# Check the switches model and firmware version and throw errors
# if this script isn't compatible.
device_info_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=12" | tr -d '\n')
model_name=$(_zyxel_gs1900_get_model "$device_info_html")
_debug2 "model_name" "$model_name"
if [ -z "$model_name" ]; then
_err "Could not find the switch model name."
_err "Please re-run with --debug and report a bug."
return $?
fi
if ! expr "$model_name" : "GS1900-" >/dev/null; then
_err "Switch is an unsupported model: $model_name"
return 1
fi
firmware_version=$(_zyxel_gs1900_get_firmware_version "$device_info_html")
_debug2 "firmware_version" "$firmware_version"
if [ -z "$firmware_version" ]; then
_err "Could not find the switch firmware version."
_err "Please re-run with --debug and report a bug."
return $?
fi
_debug2 "_zyxel_gs1900_minimum_firmware_version" "$_zyxel_gs1900_minimum_firmware_version"
minimum_major_version=$(_zyxel_gs1900_parse_major_version "$_zyxel_gs1900_minimum_firmware_version")
_debug2 "minimum_major_version" "$minimum_major_version"
minimum_minor_version=$(_zyxel_gs1900_parse_minor_version "$_zyxel_gs1900_minimum_firmware_version")
_debug2 "minimum_minor_version" "$minimum_minor_version"
_debug2 "firmware_version" "$firmware_version"
firmware_major_version=$(_zyxel_gs1900_parse_major_version "$firmware_version")
_debug2 "firmware_major_version" "$firmware_major_version"
firmware_minor_version=$(_zyxel_gs1900_parse_minor_version "$firmware_version")
_debug2 "firmware_minor_version" "$firmware_minor_version"
_ret=0
if [ "$firmware_major_version" -lt "$minimum_major_version" ]; then
_ret=1
elif [ "$firmware_major_version" -eq "$minimum_major_version" ] && [ "$firmware_minor_version" -lt "$minimum_minor_version" ]; then
_ret=1
fi
if [ "$_ret" != "0" ]; then
_err "Unsupported firmware version $firmware_version. Please upgrade to at least version $_zyxel_gs1900_minimum_firmware_version."
fi
return $?
}
_zyxel_gs1900_should_update() {
# Get the remote certificate serial number
_remote_cert=$(${ACME_OPENSSL_BIN:-openssl} s_client -showcerts -connect "${DEPLOY_ZYXEL_SWITCH}:443" 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
_debug3 "_remote_cert" "$_remote_cert"
_remote_cert_serial=$(printf "%s" "${_remote_cert}" | ${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial)
_debug2 "_remote_cert_serial" "$_remote_cert_serial"
# Get our certificate serial number
_our_cert_serial=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial <"${_ccert}")
_debug2 "_our_cert_serial" "$_our_cert_serial"
[ "${_remote_cert_serial}" != "${_our_cert_serial}" ]
}
_zyxel_gs1900_upload_certificate() {
# Generate a PKCS12 certificate with a temporary password since the web interface
# requires a password be present. Then upload that certificate.
temp_cert_password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 64)
_secure_debug2 "temp_cert_password" "$temp_cert_password"
temp_pkcs12="$(_mktemp)"
_debug2 "temp_pkcs12" "$temp_pkcs12"
_toPkcs "$temp_pkcs12" "$_ckey" "$_ccert" "$_cca" "$temp_cert_password"
if [ "$?" != "0" ]; then
_err "Failed to generate a pkcs12 certificate."
_err "Please re-run with --debug and report a bug."
# ensure the temporary certificate file is cleaned up
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
return $?
fi
# Load the upload page
upload_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5914" | tr -d '\n')
# Get the first instance of XSSID from the upload page
form_xss_value=$(printf "%s" "$upload_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g' | head -n 1)
_secure_debug2 "form_xss_value" "$form_xss_value"
_info "Generating the certificate upload request"
upload_post_request="$(_mktemp)"
upload_post_boundary="---------------------------$(date +%Y%m%d%H%M%S)"
{
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"XSSID\"\r\n\r\n%s\r\n" "${form_xss_value}"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"http_file\"; filename=\"temp_pkcs12.pfx\"\r\n"
printf "Content-Type: application/pkcs12\r\n\r\n"
cat "${temp_pkcs12}"
printf "\r\n"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"pwd\"\r\n\r\n%s\r\n" "${temp_cert_password}"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"cmd\"\r\n\r\n%s\r\n" "31"
printf -- "--%s\r\n" "${upload_post_boundary}"
printf "Content-Disposition: form-data; name=\"sysSubmit\"\r\n\r\n%s\r\n" "Import"
printf -- "--%s--\r\n" "${upload_post_boundary}"
} >"${upload_post_request}"
_info "Upload certificate to the switch"
# Unfortunately we cannot rely upon the switch response across switch models
# to return a consistent body return - so we cannot inspect the result of this
# upload to determine success.
upload_response=$(_zyxel_upload_pkcs12 "${upload_post_request}" "${upload_post_boundary}" 2>&1)
_debug3 "Upload response: ${upload_response}"
rm "${upload_post_request}"
# Pause for a few seconds to give the switch a chance to process the certificate
# For some reason I've found this to be necessary on my GS1900-24E
_debug2 "Waiting 4 seconds for the switch to process the newly uploaded certificate."
sleep "4"
# Check to see whether or not our update was successful
_ret=0
_zyxel_gs1900_should_update
if [ "$?" != "0" ]; then
_info "The certificate was updated successfully"
else
_ret=1
_err "The certificate upload does not appear to have worked."
_err "The remote certificate does not match the certificate we tried to upload."
_err "Please re-run with --debug 2 and review for unexpected errors. If none can be found please submit a bug."
fi
# ensure the temporary files are cleaned up
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
return $_ret
}
# make the certificate upload request using either
# --data binary with @ for file access in CURL
# or using --post-file for wget to ensure we upload
# the pkcs12 without getting tripped up on null bytes
#
# Usage _zyxel_upload_pkcs12 [body file name] [post boundary marker]
_zyxel_upload_pkcs12() {
bodyfilename="$1"
multipartformmarker="$2"
_post_url="${_zyxel_switch_base_uri}/cgi-bin/httpuploadcert.cgi"
httpmethod="POST"
_postContentType="multipart/form-data; boundary=${multipartformmarker}"
if [ -z "$httpmethod" ]; then
httpmethod="POST"
fi
_debug $httpmethod
_debug "_post_url" "$_post_url"
_debug2 "bodyfilename" "$bodyfilename"
_debug2 "_postContentType" "$_postContentType"
_inithttp
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
_CURL="$_ACME_CURL"
if [ "$HTTPS_INSECURE" ]; then
_CURL="$_CURL --insecure "
fi
if [ "$httpmethod" = "HEAD" ]; then
_CURL="$_CURL -I "
fi
_debug "_CURL" "$_CURL"
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data-binary "@${bodyfilename}" "$_post_url")"
_ret="$?"
if [ "$_ret" != "0" ]; then
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
_err "Here is the curl dump log:"
_err "$(cat "$_CURL_DUMP")"
fi
fi
elif [ "$_ACME_WGET" ]; then
_WGET="$_ACME_WGET"
if [ "$HTTPS_INSECURE" ]; then
_WGET="$_WGET --no-check-certificate "
fi
_debug "_WGET" "$_WGET"
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-file="${bodyfilename}" "$_post_url" 2>"$HTTP_HEADER")"
_ret="$?"
if [ "$_ret" = "8" ]; then
_ret=0
_debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
fi
if [ "$_ret" != "0" ]; then
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
fi
if _contains "$_WGET" " -d "; then
# Demultiplex wget debug output
cat "$HTTP_HEADER" >&2
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
fi
# remove leading whitespaces from header to match curl format
_sed_i 's/^ //g' "$HTTP_HEADER"
else
_ret="$?"
_err "Neither curl nor wget have been found, cannot make $httpmethod request."
fi
_debug "_ret" "$_ret"
printf "%s" "$response"
return $_ret
}
_zyxel_gs1900_trigger_reboot() {
# Trigger a reboot via the management reboot page in the web ui
reboot_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5888" | tr -d '\n')
reboot_xss_value=$(printf "%s" "$reboot_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g')
_secure_debug2 "reboot_xss_value" "$reboot_xss_value"
reboot_response_html=$(_post "XSSID=${reboot_xss_value}&cmd=5889&sysSubmit=Reboot" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi" '' "POST" "application/x-www-form-urlencoded")
reboot_message=$(printf "%s" "$reboot_response_html" | tr -d '\t\r\n\v\f' | _egrep_o "Rebooting now...")
if [ -z "$reboot_message" ]; then
_err "Failed to trigger switch reboot!"
return 1
fi
return 0
}
# password
_zyxel_gs1900_password_obfuscate() {
# Return the password obfuscated via the same method used by the
# switch's web UI login process
echo "$1" | awk '{
encoded = "";
password = $1;
allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
len = length($1);
pwi = length($1);
for (i=1; i <= (321 - pwi); i++)
{
if (0 == i % 5 && pwi > 0)
{
encoded = (encoded)(substr(password, pwi--, 1));
}
else if (i == 123)
{
if (len < 10)
{
encoded = (encoded)(0);
}
else
{
encoded = (encoded)(int(len / 10));
}
}
else if (i == 289)
{
encoded = (encoded)(len % 10)
}
else
{
encoded = (encoded)(substr(allowed, int(rand() * length(allowed)), 1))
}
}
printf("%s", encoded);
}'
}
# html label
_zyxel_html_table_lookup() {
# Look up a value in the html representing the status page of the switch
# when provided with the html of the page and the label (i.e. "Model Name:")
html="$1"
label=$(printf "%s" "$2" | tr -d ' ')
lookup_result=$(printf "%s" "$html" | tr -d "\t\r\n\v\f" | sed 's/<tr>/\n<tr>/g' | sed 's/<td[^>]*>/<td>/g' | tr -d ' ' | grep -i "$label" | sed "s/<tr><td>$label<\/td><td>\([^<]\{1,\}\)<\/td><\/tr>/\1/i")
printf "%s" "$lookup_result"
return 0
}
# html
_zyxel_gs1900_get_model() {
html="$1"
model_name=$(_zyxel_html_table_lookup "$html" "Model Name:")
printf "%s" "$model_name"
}
# html
_zyxel_gs1900_get_firmware_version() {
html="$1"
firmware_version=$(_zyxel_html_table_lookup "$html" "Firmware Version:" | _egrep_o "V[^.]+.[^(]+")
printf "%s" "$firmware_version"
}
# version_number
_zyxel_gs1900_parse_major_version() {
printf "%s" "$1" | sed 's/^V\([0-9]\{1,\}\).\{1,\}$/\1/gi'
}
# version_number
_zyxel_gs1900_parse_minor_version() {
printf "%s" "$1" | sed 's/^.\{1,\}\.\([0-9]\{1,\}\)$/\1/gi'
}

4
dnsapi/dns_1984hosting.sh

@ -128,7 +128,7 @@ _1984hosting_login() {
_get "https://1984.hosting/accounts/login/" | grep "csrfmiddlewaretoken" _get "https://1984.hosting/accounts/login/" | grep "csrfmiddlewaretoken"
csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')" csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | tr -d ';')"
if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then
_err "One or more cookies are empty: '$csrftoken', '$sessionid'." _err "One or more cookies are empty: '$csrftoken', '$sessionid'."
@ -145,7 +145,7 @@ _1984hosting_login() {
_debug2 response "$response" _debug2 response "$response"
if _contains "$response" '"loggedin": true'; then if _contains "$response" '"loggedin": true'; then
One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | tr -d ';')"
One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')" One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
export One984HOSTING_SESSIONID_COOKIE export One984HOSTING_SESSIONID_COOKIE
export One984HOSTING_CSRFTOKEN_COOKIE export One984HOSTING_CSRFTOKEN_COOKIE

160
dnsapi/dns_active24.sh

@ -1,17 +1,17 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# shellcheck disable=SC2034 # shellcheck disable=SC2034
dns_active24_info='Active24.com
Site: Active24.com
dns_active24_info='Active24.cz
Site: Active24.cz
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24 Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24
Options: Options:
ACTIVE24_Token API Token
Active24_ApiKey API Key. Called "Identifier" in the Active24 Admin
Active24_ApiSecret API Secret. Called "Secret key" in the Active24 Admin
Issues: github.com/acmesh-official/acme.sh/issues/2059 Issues: github.com/acmesh-official/acme.sh/issues/2059
Author: Milan Pála
' '
ACTIVE24_Api="https://api.active24.com"
######## Public functions #####################
Active24_Api="https://rest.active24.cz"
# export Active24_ApiKey=ak48l3h7-ak5d-qn4t-p8gc-b6fs8c3l
# export Active24_ApiSecret=ajvkeo3y82ndsu2smvxy3o36496dcascksldncsq
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record # Used to add txt record
@ -22,8 +22,8 @@ dns_active24_add() {
_active24_init _active24_init
_info "Adding txt record" _info "Adding txt record"
if _active24_rest POST "dns/$_domain/txt/v1" "{\"name\":\"$_sub_domain\",\"text\":\"$txtvalue\",\"ttl\":0}"; then
if _contains "$response" "errors"; then
if _active24_rest POST "/v2/service/$_service_id/dns/record" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":300}"; then
if _contains "$response" "error"; then
_err "Add txt record error." _err "Add txt record error."
return 1 return 1
else else
@ -31,6 +31,7 @@ dns_active24_add() {
return 0 return 0
fi fi
fi fi
_err "Add txt record error." _err "Add txt record error."
return 1 return 1
} }
@ -44,19 +45,25 @@ dns_active24_rm() {
_active24_init _active24_init
_debug "Getting txt records" _debug "Getting txt records"
_active24_rest GET "dns/$_domain/records/v1"
# The API needs to send data in body in order the filter to work
# TODO: web can also add content $txtvalue to filter and then get the id from response
_active24_rest GET "/v2/service/$_service_id/dns/record" "{\"page\":1,\"descending\":true,\"sortBy\":\"name\",\"rowsPerPage\":100,\"totalRecords\":0,\"filters\":{\"type\":[\"TXT\"],\"name\":\"${_sub_domain}\"}}"
#_active24_rest GET "/v2/service/$_service_id/dns/record?rowsPerPage=100"
if _contains "$response" "errors"; then
if _contains "$response" "error"; then
_err "Error" _err "Error"
return 1 return 1
fi fi
hash_ids=$(echo "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o "hashId\":\"[^\"]+" | cut -c10-)
# Note: it might never be more than one record actually, NEEDS more INVESTIGATION
record_ids=$(printf "%s" "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2)
_debug2 record_ids "$record_ids"
for hash_id in $hash_ids; do
_debug "Removing hash_id" "$hash_id"
if _active24_rest DELETE "dns/$_domain/$hash_id/v1" ""; then
if _contains "$response" "errors"; then
for redord_id in $record_ids; do
_debug "Removing record_id" "$redord_id"
_debug "txtvalue" "$txtvalue"
if _active24_rest DELETE "/v2/service/$_service_id/dns/record/$redord_id" ""; then
if _contains "$response" "error"; then
_err "Unable to remove txt record." _err "Unable to remove txt record."
return 1 return 1
else else
@ -70,21 +77,15 @@ dns_active24_rm() {
return 1 return 1
} }
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() { _get_root() {
domain=$1 domain=$1
i=1
p=1
if ! _active24_rest GET "dns/domains/v1"; then
if ! _active24_rest GET "/v1/user/self/service"; then
return 1 return 1
fi fi
i=1
p=1
while true; do while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug "h" "$h" _debug "h" "$h"
@ -104,45 +105,102 @@ _get_root() {
return 1 return 1
} }
_active24_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
export _H1="Authorization: Bearer $ACTIVE24_Token"
if [ "$m" != "GET" ]; then
_debug "data" "$data"
response="$(_post "$data" "$ACTIVE24_Api/$ep" "" "$m" "application/json")"
else
response="$(_get "$ACTIVE24_Api/$ep")"
_active24_init() {
Active24_ApiKey="${Active24_ApiKey:-$(_readaccountconf_mutable Active24_ApiKey)}"
Active24_ApiSecret="${Active24_ApiSecret:-$(_readaccountconf_mutable Active24_ApiSecret)}"
#Active24_ServiceId="${Active24_ServiceId:-$(_readaccountconf_mutable Active24_ServiceId)}"
if [ -z "$Active24_ApiKey" ] || [ -z "$Active24_ApiSecret" ]; then
Active24_ApiKey=""
Active24_ApiSecret=""
_err "You don't specify Active24 api key and ApiSecret yet."
_err "Please create your key and try again."
return 1
fi fi
if [ "$?" != "0" ]; then
_err "error $ep"
#save the credentials to the account conf file.
_saveaccountconf_mutable Active24_ApiKey "$Active24_ApiKey"
_saveaccountconf_mutable Active24_ApiSecret "$Active24_ApiSecret"
_debug "A24 API CHECK"
if ! _active24_rest GET "/v2/check"; then
_err "A24 API check failed with: $response"
return 1 return 1
fi fi
_debug2 response "$response"
return 0
}
_active24_init() {
ACTIVE24_Token="${ACTIVE24_Token:-$(_readaccountconf_mutable ACTIVE24_Token)}"
if [ -z "$ACTIVE24_Token" ]; then
ACTIVE24_Token=""
_err "You didn't specify a Active24 api token yet."
_err "Please create the token and try again."
if ! echo "$response" | tr -d " " | grep \"verified\":true >/dev/null; then
_err "A24 API check failed with: $response"
return 1 return 1
fi fi
_saveaccountconf_mutable ACTIVE24_Token "$ACTIVE24_Token"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "invalid domain" _err "invalid domain"
return 1 return 1
fi fi
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_domain" _debug _domain "$_domain"
_active24_get_service_id "$_domain"
_debug _service_id "$_service_id"
}
_active24_get_service_id() {
_d=$1
if ! _active24_rest GET "/v1/user/self/zone/${_d}"; then
return 1
else
response=$(echo "$response" | _json_decode)
_service_id=$(echo "$response" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2)
fi
}
_active24_rest() {
m=$1
ep_qs=$2 # with query string
# ep=$2
ep=$(printf "%s" "$ep_qs" | cut -d '?' -f1) # no query string
data="$3"
_debug "A24 $ep"
_debug "A24 $Active24_ApiKey"
_debug "A24 $Active24_ApiSecret"
timestamp=$(_time)
datez=$(date -u +"%Y%m%dT%H%M%SZ")
canonicalRequest="${m} ${ep} ${timestamp}"
signature=$(printf "%s" "$canonicalRequest" | _hmac sha1 "$(printf "%s" "$Active24_ApiSecret" | _hex_dump | tr -d " ")" hex)
authorization64="$(printf "%s:%s" "$Active24_ApiKey" "$signature" | _base64)"
export _H1="Date: ${datez}"
export _H2="Accept: application/json"
export _H3="Content-Type: application/json"
export _H4="Authorization: Basic ${authorization64}"
_debug2 H1 "$_H1"
_debug2 H2 "$_H2"
_debug2 H3 "$_H3"
_debug2 H4 "$_H4"
# _sleep 1
if [ "$m" != "GET" ]; then
_debug2 "${m} $Active24_Api${ep_qs}"
_debug "data" "$data"
response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")"
else
if [ -z "$data" ]; then
_debug2 "GET $Active24_Api${ep_qs}"
response="$(_get "$Active24_Api${ep_qs}")"
else
_debug2 "GET $Active24_Api${ep_qs} with data: ${data}"
response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")"
fi
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
} }

13
dnsapi/dns_azure.sh

@ -340,8 +340,17 @@ _azure_getaccess_token() {
if [ "$managedIdentity" = true ]; then if [ "$managedIdentity" = true ]; then
# https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http # https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
export _H1="Metadata: true"
response="$(_get http://169.254.169.254/metadata/identity/oauth2/token\?api-version=2018-02-01\&resource=https://management.azure.com/)"
if [ -n "$IDENTITY_ENDPOINT" ]; then
# Some Azure environments may set IDENTITY_ENDPOINT (formerly MSI_ENDPOINT) to have an alternative metadata endpoint
url="$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/"
headers="X-IDENTITY-HEADER: $IDENTITY_HEADER"
else
url="http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
headers="Metadata: true"
fi
export _H1="$headers"
response="$(_get "$url")"
response="$(echo "$response" | _normalizeJson)" response="$(echo "$response" | _normalizeJson)"
accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")

2
dnsapi/dns_beget.sh

@ -7,7 +7,7 @@ Options:
BEGET_User API user BEGET_User API user
BEGET_Password API password BEGET_Password API password
Issues: github.com/acmesh-official/acme.sh/issues/6200 Issues: github.com/acmesh-official/acme.sh/issues/6200
Author: ARNik arnik@arnik.ru
Author: ARNik <arnik@arnik.ru>
' '
Beget_Api="https://api.beget.com/api" Beget_Api="https://api.beget.com/api"

2
dnsapi/dns_bookmyname.sh

@ -7,7 +7,7 @@ Options:
BOOKMYNAME_USERNAME Username BOOKMYNAME_USERNAME Username
BOOKMYNAME_PASSWORD Password BOOKMYNAME_PASSWORD Password
Issues: github.com/acmesh-official/acme.sh/issues/3209 Issues: github.com/acmesh-official/acme.sh/issues/3209
Author: Neilpang
Author: @Neilpang
' '
######## Public functions ##################### ######## Public functions #####################

5
dnsapi/dns_cloudns.sh

@ -197,10 +197,11 @@ _dns_cloudns_http_api_call() {
auth_user="auth-id=$CLOUDNS_AUTH_ID" auth_user="auth-id=$CLOUDNS_AUTH_ID"
fi fi
encoded_password=$(echo "$CLOUDNS_AUTH_PASSWORD" | tr -d "\n\r" | _url_encode)
if [ -z "$2" ]; then if [ -z "$2" ]; then
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD"
data="$auth_user&auth-password=$encoded_password"
else else
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2"
data="$auth_user&auth-password=$encoded_password&$2"
fi fi
response="$(_get "$CLOUDNS_API/$method?$data")" response="$(_get "$CLOUDNS_API/$method?$data")"

5
dnsapi/dns_constellix.sh

@ -117,7 +117,7 @@ dns_constellix_rm() {
#################### Private functions below ################################## #################### Private functions below ##################################
_get_root() { _get_root() {
domain=$1
domain=$(echo "$1" | _lower_case)
i=2 i=2
p=1 p=1
_debug "Detecting root zone" _debug "Detecting root zone"
@ -156,6 +156,9 @@ _constellix_rest() {
data="$3" data="$3"
_debug "$ep" _debug "$ep"
# Prevent rate limit
_sleep 2
rdate=$(date +"%s")"000" rdate=$(date +"%s")"000"
hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64) hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64)

2
dnsapi/dns_ddnss.sh

@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss
Options: Options:
DDNSS_Token API Token DDNSS_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2230 Issues: github.com/acmesh-official/acme.sh/issues/2230
Author: RaidenII, helbgd, mod242
Author: @helbgd, @mod242
' '
DDNSS_DNS_API="https://ddnss.de/upd.php" DDNSS_DNS_API="https://ddnss.de/upd.php"

2
dnsapi/dns_dnshome.sh

@ -7,7 +7,7 @@ Options:
DNSHOME_Subdomain Subdomain DNSHOME_Subdomain Subdomain
DNSHOME_SubdomainPassword Subdomain Password DNSHOME_SubdomainPassword Subdomain Password
Issues: github.com/acmesh-official/acme.sh/issues/3819 Issues: github.com/acmesh-official/acme.sh/issues/3819
Author: dnsHome.de https://github.com/dnsHome-de
Author: @dnsHome-de
' '
# Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"

2
dnsapi/dns_duckdns.sh

@ -5,7 +5,7 @@ Site: www.DuckDNS.org
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns
Options: Options:
DuckDNS_Token API Token DuckDNS_Token API Token
Author: RaidenII
Author: @RaidenII
' '
DuckDNS_API="https://www.duckdns.org/update" DuckDNS_API="https://www.duckdns.org/update"

2
dnsapi/dns_dyn.sh

@ -8,7 +8,7 @@ Options:
DYN_Customer Customer DYN_Customer Customer
DYN_Username API Username DYN_Username API Username
DYN_Password Secret DYN_Password Secret
Author: Gerd Naschenweng <https://github.com/magicdude4eva>
Author: Gerd Naschenweng <@magicdude4eva>
' '
# Dyn Managed DNS API # Dyn Managed DNS API

2
dnsapi/dns_dynv6.sh

@ -8,7 +8,7 @@ Options:
OptionsAlt: OptionsAlt:
KEY Path to SSH private key file. E.g. "/root/.ssh/dynv6" KEY Path to SSH private key file. E.g. "/root/.ssh/dynv6"
Issues: github.com/acmesh-official/acme.sh/issues/2702 Issues: github.com/acmesh-official/acme.sh/issues/2702
Author: StefanAbl
Author: @StefanAbl
' '
dynv6_api="https://dynv6.com/api/v2" dynv6_api="https://dynv6.com/api/v2"

2
dnsapi/dns_easydns.sh

@ -7,7 +7,7 @@ Options:
EASYDNS_Token API Token EASYDNS_Token API Token
EASYDNS_Key API Key EASYDNS_Key API Key
Issues: github.com/acmesh-official/acme.sh/issues/2647 Issues: github.com/acmesh-official/acme.sh/issues/2647
Author: Neilpang, wurzelpanzer <wurzelpanzer@maximolider.net>
Author: @Neilpang, wurzelpanzer <wurzelpanzer@maximolider.net>
' '
# API Documentation: https://sandbox.rest.easydns.net:3001/ # API Documentation: https://sandbox.rest.easydns.net:3001/

163
dnsapi/dns_edgecenter.sh

@ -0,0 +1,163 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_edgecenter_info='EdgeCenter.ru
Site: EdgeCenter.ru
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgecenter
Options:
EDGECENTER_API_KEY API Key
Issues: github.com/acmesh-official/acme.sh/issues/6313
Author: Konstantin Ruchev <konstantin.ruchev@edgecenter.ru>
'
EDGECENTER_API="https://api.edgecenter.ru"
DOMAIN_TYPE=
DOMAIN_MASTER=
######## Public functions #####################
#Usage: dns_edgecenter_add _acme-challenge.www.domain.com "TXT_RECORD_VALUE"
dns_edgecenter_add() {
fulldomain="$1"
txtvalue="$2"
_info "Using EdgeCenter DNS API"
if ! _dns_edgecenter_init_check; then
return 1
fi
_debug "Detecting root zone for $fulldomain"
if ! _get_root "$fulldomain"; then
return 1
fi
subdomain="${fulldomain%."$_zone"}"
subdomain=${subdomain%.}
_debug "Zone: $_zone"
_debug "Subdomain: $subdomain"
_debug "TXT value: $txtvalue"
payload='{"resource_records": [ { "content": ["'"$txtvalue"'"] } ], "ttl": 60 }'
_dns_edgecenter_http_api_call "post" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$payload"
if _contains "$response" '"error":"rrset is already exists"'; then
_debug "RRSet exists, merging values"
_dns_edgecenter_http_api_call "get" "dns/v2/zones/$_zone/$subdomain.$_zone/txt"
current="$response"
newlist=""
for v in $(echo "$current" | sed -n 's/.*"content":\["\([^"]*\)"\].*/\1/p'); do
newlist="$newlist {\"content\":[\"$v\"]},"
done
newlist="$newlist{\"content\":[\"$txtvalue\"]}"
putdata="{\"resource_records\":[${newlist}]}
"
_dns_edgecenter_http_api_call "put" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$putdata"
_info "Updated existing RRSet with new TXT value."
return 0
fi
if _contains "$response" '"exception":'; then
_err "Record cannot be added."
return 1
fi
_info "TXT record added successfully."
return 0
}
#Usage: dns_edgecenter_rm _acme-challenge.www.domain.com "TXT_RECORD_VALUE"
dns_edgecenter_rm() {
fulldomain="$1"
txtvalue="$2"
_info "Removing TXT record for $fulldomain"
if ! _dns_edgecenter_init_check; then
return 1
fi
if ! _get_root "$fulldomain"; then
return 1
fi
subdomain="${fulldomain%."$_zone"}"
subdomain=${subdomain%.}
_dns_edgecenter_http_api_call "delete" "dns/v2/zones/$_zone/$subdomain.$_zone/txt"
if [ -z "$response" ]; then
_info "TXT record deleted successfully."
else
_info "TXT record may not have been deleted: $response"
fi
return 0
}
#################### Private functions below ##################################
_dns_edgecenter_init_check() {
EDGECENTER_API_KEY="${EDGECENTER_API_KEY:-$(_readaccountconf_mutable EDGECENTER_API_KEY)}"
if [ -z "$EDGECENTER_API_KEY" ]; then
_err "EDGECENTER_API_KEY was not exported."
return 1
fi
_saveaccountconf_mutable EDGECENTER_API_KEY "$EDGECENTER_API_KEY"
export _H1="Authorization: APIKey $EDGECENTER_API_KEY"
_dns_edgecenter_http_api_call "get" "dns/v2/clients/me/features"
if ! _contains "$response" '"id":'; then
_err "Invalid API key."
return 1
fi
return 0
}
_get_root() {
domain="$1"
i=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-)
if [ -z "$h" ]; then
return 1
fi
_dns_edgecenter_http_api_call "get" "dns/v2/zones/$h"
if ! _contains "$response" 'zone is not found'; then
_zone="$h"
return 0
fi
i=$((i + 1))
done
return 1
}
_dns_edgecenter_http_api_call() {
mtd="$1"
endpoint="$2"
data="$3"
export _H1="Authorization: APIKey $EDGECENTER_API_KEY"
case "$mtd" in
get)
response="$(_get "$EDGECENTER_API/$endpoint")"
;;
post)
response="$(_post "$data" "$EDGECENTER_API/$endpoint")"
;;
delete)
response="$(_post "" "$EDGECENTER_API/$endpoint" "" "DELETE")"
;;
put)
response="$(_post "$data" "$EDGECENTER_API/$endpoint" "" "PUT")"
;;
*)
_err "Unknown HTTP method $mtd"
return 1
;;
esac
_debug "HTTP $mtd response: $response"
return 0
}

2
dnsapi/dns_fornex.sh

@ -95,7 +95,7 @@ _get_root() {
return 1 return 1
fi fi
if ! _rest GET "dns/domain/"; then
if ! _rest GET "dns/domain/?q=$h"; then
return 1 return 1
fi fi

2
dnsapi/dns_freedns.sh

@ -7,7 +7,7 @@ Options:
FREEDNS_User Username FREEDNS_User Username
FREEDNS_Password Password FREEDNS_Password Password
Issues: github.com/acmesh-official/acme.sh/issues/2305 Issues: github.com/acmesh-official/acme.sh/issues/2305
Author: David Kerr <https://github.com/dkerr64>
Author: David Kerr <@dkerr64>
' '
######## Public functions ##################### ######## Public functions #####################

4
dnsapi/dns_freemyip.sh

@ -1,11 +1,11 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# shellcheck disable=SC2034 # shellcheck disable=SC2034
dns_freemyip_info='FreeMyIP.com dns_freemyip_info='FreeMyIP.com
Site: freemyip.com
Site: FreeMyIP.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip
Options: Options:
FREEMYIP_Token API Token FREEMYIP_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/{XXXX}
Issues: github.com/acmesh-official/acme.sh/issues/6247
Author: Recolic Keghart <root@recolic.net>, @Giova96 Author: Recolic Keghart <root@recolic.net>, @Giova96
' '

1
dnsapi/dns_he_ddns.sh

@ -5,6 +5,7 @@ Site: dns.he.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns
Options: Options:
HE_DDNS_KEY The DDNS key HE_DDNS_KEY The DDNS key
Issues: https://github.com/acmesh-official/acme.sh/issues/5238
Author: Markku Leiniö Author: Markku Leiniö
' '

2
dnsapi/dns_joker.sh

@ -7,7 +7,7 @@ Options:
JOKER_USERNAME Username JOKER_USERNAME Username
JOKER_PASSWORD Password JOKER_PASSWORD Password
Issues: github.com/acmesh-official/acme.sh/issues/2840 Issues: github.com/acmesh-official/acme.sh/issues/2840
Author: <https://github.com/aattww/>
Author: @aattww
' '
JOKER_API="https://svc.joker.com/nic/replace" JOKER_API="https://svc.joker.com/nic/replace"

96
dnsapi/dns_la.sh

@ -1,14 +1,17 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# LA_Id="123"
# LA_Sk="456"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
dns_la_info='dns.la dns_la_info='dns.la
Site: dns.la Site: dns.la
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la
Options: Options:
LA_Id API ID
LA_Key API key
LA_Id APIID
LA_Sk APISecret
LA_Token 用冒号连接 APIID APISecret 再base64生成
Issues: github.com/acmesh-official/acme.sh/issues/4257 Issues: github.com/acmesh-official/acme.sh/issues/4257
' '
LA_Api="https://api.dns.la/api" LA_Api="https://api.dns.la/api"
######## Public functions ##################### ######## Public functions #####################
@ -19,18 +22,23 @@ dns_la_add() {
txtvalue=$2 txtvalue=$2
LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}"
LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}"
LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}"
_log "LA_Id=$LA_Id"
_log "LA_Sk=$LA_Sk"
if [ -z "$LA_Id" ] || [ -z "$LA_Key" ]; then
if [ -z "$LA_Id" ] || [ -z "$LA_Sk" ]; then
LA_Id="" LA_Id=""
LA_Key=""
LA_Sk=""
_err "You didn't specify a dnsla api id and key yet." _err "You didn't specify a dnsla api id and key yet."
return 1 return 1
fi fi
#save the api key and email to the account conf file. #save the api key and email to the account conf file.
_saveaccountconf_mutable LA_Id "$LA_Id" _saveaccountconf_mutable LA_Id "$LA_Id"
_saveaccountconf_mutable LA_Key "$LA_Key"
_saveaccountconf_mutable LA_Sk "$LA_Sk"
# generate dnsla token
_la_token
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -42,11 +50,13 @@ dns_la_add() {
_debug _domain "$_domain" _debug _domain "$_domain"
_info "Adding record" _info "Adding record"
if _la_rest "record.ashx?cmd=create&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue&recordline="; then
if _contains "$response" '"resultid":'; then
# record type is enum in new api, 16 for TXT
if _la_post "{\"domainId\":\"$_domain_id\",\"type\":16,\"host\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"ttl\":600}" "record"; then
if _contains "$response" '"id":'; then
_info "Added, OK" _info "Added, OK"
return 0 return 0
elif _contains "$response" '"code":532'; then
elif _contains "$response" '"msg":"与已有记录冲突"'; then
_info "Already exists, OK" _info "Already exists, OK"
return 0 return 0
else else
@ -54,7 +64,7 @@ dns_la_add() {
return 1 return 1
fi fi
fi fi
_err "Add txt record error."
_err "Add txt record failed."
return 1 return 1
} }
@ -65,7 +75,9 @@ dns_la_rm() {
txtvalue=$2 txtvalue=$2
LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}"
LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}"
LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}"
_la_token
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -77,27 +89,29 @@ dns_la_rm() {
_debug _domain "$_domain" _debug _domain "$_domain"
_debug "Getting txt records" _debug "Getting txt records"
if ! _la_rest "record.ashx?cmd=listn&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue"; then
# record type is enum in new api, 16 for TXT
if ! _la_get "recordList?pageIndex=1&pageSize=10&domainId=$_domain_id&host=$_sub_domain&type=16&data=$txtvalue"; then
_err "Error" _err "Error"
return 1 return 1
fi fi
if ! _contains "$response" '"recordid":'; then
if ! _contains "$response" '"id":'; then
_info "Don't need to remove." _info "Don't need to remove."
return 0 return 0
fi fi
record_id=$(printf "%s" "$response" | grep '"recordid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n')
record_id=$(printf "%s" "$response" | grep '"id":' | _head_n 1 | sed 's/.*"id": *"\([^"]*\)".*/\1/')
_debug "record_id" "$record_id" _debug "record_id" "$record_id"
if [ -z "$record_id" ]; then if [ -z "$record_id" ]; then
_err "Can not get record id to remove." _err "Can not get record id to remove."
return 1 return 1
fi fi
if ! _la_rest "record.ashx?cmd=remove&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&recordid=$record_id"; then
# remove record in new api is RESTful
if ! _la_post "" "record?id=$record_id" "DELETE"; then
_err "Delete record error." _err "Delete record error."
return 1 return 1
fi fi
_contains "$response" '"code":300'
_contains "$response" '"code":200'
} }
@ -119,12 +133,13 @@ _get_root() {
return 1 return 1
fi fi
if ! _la_rest "domain.ashx?cmd=get&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domain=$h"; then
if ! _la_get "domain?domain=$h"; then
return 1 return 1
fi fi
if _contains "$response" '"domainid":'; then
_domain_id=$(printf "%s" "$response" | grep '"domainid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n')
if _contains "$response" '"domain":'; then
_domain_id=$(echo "$response" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p')
_log "_domain_id" "$_domain_id"
if [ "$_domain_id" ]; then if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h" _domain="$h"
@ -143,6 +158,21 @@ _la_rest() {
url="$LA_Api/$1" url="$LA_Api/$1"
_debug "$url" _debug "$url"
if ! response="$(_get "$url" "Authorization: Basic $LA_Token" | tr -d ' ' | tr "}" ",")"; then
_err "Error: $url"
return 1
fi
_debug2 response "$response"
return 0
}
_la_get() {
url="$LA_Api/$1"
_debug "$url"
export _H1="Authorization: Basic $LA_Token"
if ! response="$(_get "$url" | tr -d ' ' | tr "}" ",")"; then if ! response="$(_get "$url" | tr -d ' ' | tr "}" ",")"; then
_err "Error: $url" _err "Error: $url"
return 1 return 1
@ -151,3 +181,29 @@ _la_rest() {
_debug2 response "$response" _debug2 response "$response"
return 0 return 0
} }
# Usage: _la_post body url [POST|PUT|DELETE]
_la_post() {
body=$1
url="$LA_Api/$2"
http_method=$3
_debug "$body"
_debug "$url"
export _H1="Authorization: Basic $LA_Token"
if ! response="$(_post "$body" "$url" "" "$http_method")"; then
_err "Error: $url"
return 1
fi
_debug2 response "$response"
return 0
}
_la_token() {
LA_Token=$(printf "%s:%s" "$LA_Id" "$LA_Sk" | _base64)
_debug "$LA_Token"
return 0
}

9
dnsapi/dns_mijnhost.sh

@ -1,16 +1,15 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# shellcheck disable=SC2034 # shellcheck disable=SC2034
dns_mijnhost_info='mijn.host dns_mijnhost_info='mijn.host
Domains: mijn.host
Site: mijn.host Site: mijn.host
Docs: https://mijn.host/api/doc/
Issues: https://github.com/acmesh-official/acme.sh/issues/6177
Author: peterv99
Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mijnhost
Options: Options:
MIJNHOST_API_KEY API Key MIJNHOST_API_KEY API Key
Issues: github.com/acmesh-official/acme.sh/issues/6177
Author: @peterv99
' '
######## Public functions ###################### Constants for your mijn-host API
######## Public functions ######################
MIJNHOST_API="https://mijn.host/api/v2" MIJNHOST_API="https://mijn.host/api/v2"
# Add TXT record for domain verification # Add TXT record for domain verification

2
dnsapi/dns_mydnsjp.sh

@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydnsjp
Options: Options:
MYDNSJP_MasterID Master ID MYDNSJP_MasterID Master ID
MYDNSJP_Password Password MYDNSJP_Password Password
Author: epgdatacapbon
Author: @tkmsst
' '
######## Public functions ##################### ######## Public functions #####################

2
dnsapi/dns_namecom.sh

@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecom
Options: Options:
Namecom_Username Username Namecom_Username Username
Namecom_Token API Token Namecom_Token API Token
Author: RaidenII
Author: @RaidenII
' '
######## Public functions ##################### ######## Public functions #####################

2
dnsapi/dns_namesilo.sh

@ -5,7 +5,7 @@ Site: NameSilo.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namesilo Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namesilo
Options: Options:
Namesilo_Key API Key Namesilo_Key API Key
Author: meowthink
Author: @meowthink
' '
#Utilize API to finish dns-01 verifications. #Utilize API to finish dns-01 verifications.

186
dnsapi/dns_openprovider_rest.sh

@ -0,0 +1,186 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_openprovider_rest_info='OpenProvider (REST)
Domains: OpenProvider.com
Site: OpenProvider.eu
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openprovider_rest
Options:
OPENPROVIDER_REST_USERNAME Openprovider Account Username
OPENPROVIDER_REST_PASSWORD Openprovider Account Password
Issues: github.com/acmesh-official/acme.sh/issues/6122
Author: Lambiek12
'
OPENPROVIDER_API_URL="https://api.openprovider.eu/v1beta"
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_openprovider_rest_add() {
fulldomain=$1
txtvalue=$2
_openprovider_prepare_credentials || return 1
_debug "Try fetch OpenProvider DNS zone details"
if ! _get_dns_zone "$fulldomain"; then
_err "DNS zone not found within configured OpenProvider account."
return 1
fi
if [ -n "$_domain_id" ]; then
addzonerecordrequestparameters="dns/zones/$_domain_name"
addzonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"add\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"$txtvalue\"}]}}"
if _openprovider_rest PUT "$addzonerecordrequestparameters" "$addzonerecordrequestbody"; then
if _contains "$response" "\"success\":true"; then
return 0
elif _contains "$response" "\"Duplicate record\""; then
_debug "Record already existed"
return 0
else
_err "Adding TXT record failed due to errors."
return 1
fi
fi
fi
_err "Adding TXT record failed due to errors."
return 1
}
# Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to remove the txt record after validation
dns_openprovider_rest_rm() {
fulldomain=$1
txtvalue=$2
_openprovider_prepare_credentials || return 1
_debug "Try fetch OpenProvider DNS zone details"
if ! _get_dns_zone "$fulldomain"; then
_err "DNS zone not found within configured OpenProvider account."
return 1
fi
if [ -n "$_domain_id" ]; then
removezonerecordrequestparameters="dns/zones/$_domain_name"
removezonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"remove\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"\\\"$txtvalue\\\"\"}]}}"
if _openprovider_rest PUT "$removezonerecordrequestparameters" "$removezonerecordrequestbody"; then
if _contains "$response" "\"success\":true"; then
return 0
else
_err "Removing TXT record failed due to errors."
return 1
fi
fi
fi
_err "Removing TXT record failed due to errors."
return 1
}
#################### OpenProvider API common functions ####################
_openprovider_prepare_credentials() {
OPENPROVIDER_REST_USERNAME="${OPENPROVIDER_REST_USERNAME:-$(_readaccountconf_mutable OPENPROVIDER_REST_USERNAME)}"
OPENPROVIDER_REST_PASSWORD="${OPENPROVIDER_REST_PASSWORD:-$(_readaccountconf_mutable OPENPROVIDER_REST_PASSWORD)}"
if [ -z "$OPENPROVIDER_REST_USERNAME" ] || [ -z "$OPENPROVIDER_REST_PASSWORD" ]; then
OPENPROVIDER_REST_USERNAME=""
OPENPROVIDER_REST_PASSWORD=""
_err "You didn't specify the Openprovider username or password yet."
return 1
fi
#save the credentials to the account conf file.
_saveaccountconf_mutable OPENPROVIDER_REST_USERNAME "$OPENPROVIDER_REST_USERNAME"
_saveaccountconf_mutable OPENPROVIDER_REST_PASSWORD "$OPENPROVIDER_REST_PASSWORD"
}
_openprovider_rest() {
httpmethod=$1
queryparameters=$2
requestbody=$3
_openprovider_rest_login
if [ -z "$openproviderauthtoken" ]; then
_err "Unable to fetch authentication token from Openprovider API."
return 1
fi
export _H1="Content-Type: application/json"
export _H2="Accept: application/json"
export _H3="Authorization: Bearer $openproviderauthtoken"
if [ "$httpmethod" != "GET" ]; then
response="$(_post "$requestbody" "$OPENPROVIDER_API_URL/$queryparameters" "" "$httpmethod")"
else
response="$(_get "$OPENPROVIDER_API_URL/$queryparameters")"
fi
if [ "$?" != "0" ]; then
_err "No valid parameters supplied for Openprovider API: Error $queryparameters"
return 1
fi
_debug2 response "$response"
return 0
}
_openprovider_rest_login() {
export _H1="Content-Type: application/json"
export _H2="Accept: application/json"
loginrequesturl="$OPENPROVIDER_API_URL/auth/login"
loginrequestbody="{\"ip\":\"0.0.0.0\",\"password\":\"$OPENPROVIDER_REST_PASSWORD\",\"username\":\"$OPENPROVIDER_REST_USERNAME\"}"
loginresponse="$(_post "$loginrequestbody" "$loginrequesturl" "" "POST")"
openproviderauthtoken="$(printf "%s\n" "$loginresponse" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')"
export openproviderauthtoken
}
#################### Private functions ##################################
# Usage: _get_dns_zone _acme-challenge.www.domain.com
# Returns:
# _domain_id=123456789
# _domain_name=domain.com
# _sub_domain=_acme-challenge.www
_get_dns_zone() {
domain=$1
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
# Empty value not allowed
return 1
fi
if ! _openprovider_rest GET "dns/zones/$h" ""; then
return 1
fi
if _contains "$response" "\"name\":\"$h\""; then
_domain_id="$(printf "%s\n" "$response" | _egrep_o '"id" *: *[^,]*' | _head_n 1 | sed 's#^"id" *: *##')"
_debug _domain_id "$_domain_id"
_domain_name="$h"
_debug _domain_name "$_domain_name"
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_debug _sub_domain "$_sub_domain"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}

2
dnsapi/dns_pleskxml.sh

@ -8,7 +8,7 @@ Options:
pleskxml_user Username pleskxml_user Username
pleskxml_pass Password pleskxml_pass Password
Issues: github.com/acmesh-official/acme.sh/issues/2577 Issues: github.com/acmesh-official/acme.sh/issues/2577
Author: Stilez, <https://github.com/romanlum>
Author: @Stilez, @romanlum
' '
## Plesk XML API described at: ## Plesk XML API described at:

18
dnsapi/dns_rage4.sh

@ -42,6 +42,14 @@ dns_rage4_add() {
_debug _domain_id "$_domain_id" _debug _domain_id "$_domain_id"
_rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1" _rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1"
# Response after adding a TXT record should be something like this:
# {"status":true,"id":28160443,"error":null}
if ! _contains "$response" '"error":null' >/dev/null; then
_err "Error while adding TXT record: '$response'"
return 1
fi
return 0 return 0
} }
@ -63,7 +71,12 @@ dns_rage4_rm() {
_debug "Getting txt records" _debug "Getting txt records"
_rage4_rest "getrecords/?id=${_domain_id}" _rage4_rest "getrecords/?id=${_domain_id}"
_record_id=$(echo "$response" | sed -rn 's/.*"id":([[:digit:]]+)[^\}]*'"$txtvalue"'.*/\1/p')
_record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -rn 's/.*"id":([[:digit:]]+),.*/\1/p')
if [ -z "$_record_id" ]; then
_err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'."
return 1
fi
_rage4_rest "deleterecord/?id=${_record_id}" _rage4_rest "deleterecord/?id=${_record_id}"
return 0 return 0
} }
@ -105,8 +118,7 @@ _rage4_rest() {
token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"') token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"')
auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64) auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64)
export _H1="Content-Type: application/json"
export _H2="Authorization: Basic $auth"
export _H1="Authorization: Basic $auth"
response="$(_get "$RAGE4_Api$ep")" response="$(_get "$RAGE4_Api$ep")"

2
dnsapi/dns_schlundtech.sh

@ -7,7 +7,7 @@ Options:
SCHLUNDTECH_USER Username SCHLUNDTECH_USER Username
SCHLUNDTECH_PASSWORD Password SCHLUNDTECH_PASSWORD Password
Issues: github.com/acmesh-official/acme.sh/issues/2246 Issues: github.com/acmesh-official/acme.sh/issues/2246
Author: <https://github.com/mod242>
Author: @mod242
' '
SCHLUNDTECH_API="https://gateway.schlundtech.de" SCHLUNDTECH_API="https://gateway.schlundtech.de"

38
dnsapi/dns_selectel.sh

@ -1,27 +1,21 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# shellcheck disable=SC2034 # shellcheck disable=SC2034
# dns_selectel_info='Selectel.com
# Domains: Selectel.ru
# Site: Selectel.com
# Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel
# Options:
# Variables that must be defined before running
# SL_Ver can take one of the values 'v1' or 'v2', default is 'v1'
# SL_Ver='v1', when using version API legacy (v1)
# SL_Ver='v2', when using version API actual (v2)
# when using API version v1, i.e. SL_Ver is 'v1' or not defined:
# SL_Key - API Key, required
# when using API version v2:
# SL_Ver - required as 'v2'
# SL_Login_ID - account ID, required
# SL_Project_Name - name project, required
# SL_Login_Name - service user name, required
# SL_Pswd - service user password, required
# SL_Expire - token lifetime in minutes (0-1440), default 1400 minutes
#
# Issues: github.com/acmesh-official/acme.sh/issues/5126
#
dns_selectel_info='Selectel.com
Domains: Selectel.ru
Site: Selectel.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel
Options: For old API version v1 (deprecated)
SL_Ver API version. Use "v1".
SL_Key API Key
OptionsAlt: For the current API version v2
SL_Ver API version. Use "v2".
SL_Login_ID Account ID
SL_Project_Name Project name
SL_Login_Name Service user name
SL_Pswd Service user password
SL_Expire Token lifetime. In minutes (0-1440). Default "1400"
Issues: github.com/acmesh-official/acme.sh/issues/5126
'
SL_Api="https://api.selectel.ru/domains" SL_Api="https://api.selectel.ru/domains"
auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens" auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens"

212
dnsapi/dns_spaceship.sh

@ -0,0 +1,212 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_spaceship_info='Spaceship.com
Site: Spaceship.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_spaceship
Options:
SPACESHIP_API_KEY API Key
SPACESHIP_API_SECRET API Secret
SPACESHIP_ROOT_DOMAIN Root domain. Manually specify the root domain if auto-detection fails. Optional.
Issues: github.com/acmesh-official/acme.sh/issues/6304
Author: Meow <@Meo597>
'
# Spaceship API
# https://docs.spaceship.dev/
######## Public functions #####################
SPACESHIP_API_BASE="https://spaceship.dev/api/v1"
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_spaceship_add() {
fulldomain="$1"
txtvalue="$2"
_info "Adding TXT record for $fulldomain with value $txtvalue"
# Initialize API credentials and headers
if ! _spaceship_init; then
return 1
fi
# Detect root zone
if ! _get_root "$fulldomain"; then
return 1
fi
# Extract subdomain part relative to root domain
subdomain=$(echo "$fulldomain" | sed "s/\.$_domain$//")
if [ "$subdomain" = "$fulldomain" ]; then
_err "Failed to extract subdomain from $fulldomain relative to root domain $_domain"
return 1
fi
_debug "Extracted subdomain: $subdomain for root domain: $_domain"
# Escape txtvalue to prevent JSON injection (e.g., quotes in txtvalue)
escaped_txtvalue=$(echo "$txtvalue" | sed 's/"/\\"/g')
# Prepare payload and URL for adding TXT record
# Note: 'name' in payload uses subdomain (e.g., _acme-challenge.sub) as required by Spaceship API
payload="{\"force\": true, \"items\": [{\"type\": \"TXT\", \"name\": \"$subdomain\", \"value\": \"$escaped_txtvalue\", \"ttl\": 600}]}"
url="$SPACESHIP_API_BASE/dns/records/$_domain"
# Send API request
if _spaceship_api_request "PUT" "$url" "$payload"; then
_info "Successfully added TXT record for $fulldomain"
return 0
else
_err "Failed to add TXT record. If the domain $_domain is incorrect, set SPACESHIP_ROOT_DOMAIN to the correct root domain."
return 1
fi
}
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
dns_spaceship_rm() {
fulldomain="$1"
txtvalue="$2"
_info "Removing TXT record for $fulldomain with value $txtvalue"
# Initialize API credentials and headers
if ! _spaceship_init; then
return 1
fi
# Detect root zone
if ! _get_root "$fulldomain"; then
return 1
fi
# Extract subdomain part relative to root domain
subdomain=$(echo "$fulldomain" | sed "s/\.$_domain$//")
if [ "$subdomain" = "$fulldomain" ]; then
_err "Failed to extract subdomain from $fulldomain relative to root domain $_domain"
return 1
fi
_debug "Extracted subdomain: $subdomain for root domain: $_domain"
# Escape txtvalue to prevent JSON injection
escaped_txtvalue=$(echo "$txtvalue" | sed 's/"/\\"/g')
# Prepare payload and URL for deleting TXT record
# Note: 'name' in payload uses subdomain (e.g., _acme-challenge.sub) as required by Spaceship API
payload="[{\"type\": \"TXT\", \"name\": \"$subdomain\", \"value\": \"$escaped_txtvalue\"}]"
url="$SPACESHIP_API_BASE/dns/records/$_domain"
# Send API request
if _spaceship_api_request "DELETE" "$url" "$payload"; then
_info "Successfully deleted TXT record for $fulldomain"
return 0
else
_err "Failed to delete TXT record. If the domain $_domain is incorrect, set SPACESHIP_ROOT_DOMAIN to the correct root domain."
return 1
fi
}
#################### Private functions below ##################################
_spaceship_init() {
SPACESHIP_API_KEY="${SPACESHIP_API_KEY:-$(_readaccountconf_mutable SPACESHIP_API_KEY)}"
SPACESHIP_API_SECRET="${SPACESHIP_API_SECRET:-$(_readaccountconf_mutable SPACESHIP_API_SECRET)}"
if [ -z "$SPACESHIP_API_KEY" ] || [ -z "$SPACESHIP_API_SECRET" ]; then
_err "Spaceship API credentials are not set. Please set SPACESHIP_API_KEY and SPACESHIP_API_SECRET."
_err "Ensure \"$LE_CONFIG_HOME\" directory has restricted permissions (chmod 700 \"$LE_CONFIG_HOME\") to protect credentials."
return 1
fi
# Save credentials to account config for future renewals
_saveaccountconf_mutable SPACESHIP_API_KEY "$SPACESHIP_API_KEY"
_saveaccountconf_mutable SPACESHIP_API_SECRET "$SPACESHIP_API_SECRET"
# Set common headers for API requests
export _H1="X-API-Key: $SPACESHIP_API_KEY"
export _H2="X-API-Secret: $SPACESHIP_API_SECRET"
export _H3="Content-Type: application/json"
return 0
}
_get_root() {
domain="$1"
# Check manual override
SPACESHIP_ROOT_DOMAIN="${SPACESHIP_ROOT_DOMAIN:-$(_readdomainconf SPACESHIP_ROOT_DOMAIN)}"
if [ -n "$SPACESHIP_ROOT_DOMAIN" ]; then
_domain="$SPACESHIP_ROOT_DOMAIN"
_debug "Using manually specified or saved root domain: $_domain"
_savedomainconf SPACESHIP_ROOT_DOMAIN "$SPACESHIP_ROOT_DOMAIN"
return 0
fi
_debug "Detecting root zone for '$domain'"
i=1
p=1
while true; do
_cutdomain=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug "Attempt i=$i: Checking if '$_cutdomain' is root zone (cut ret=$?)"
if [ -z "$_cutdomain" ]; then
_debug "Cut resulted in empty string, root zone not found."
break
fi
# Call the API to check if this _cutdomain is a manageable zone
if _spaceship_api_request "GET" "$SPACESHIP_API_BASE/dns/records/$_cutdomain?take=1&skip=0"; then
# API call succeeded (HTTP 200 OK for GET /dns/records)
_domain="$_cutdomain"
_debug "Root zone found: '$_domain'"
# Save the detected root domain
_savedomainconf SPACESHIP_ROOT_DOMAIN "$_domain"
_info "Root domain '$_domain' saved to configuration for future use."
return 0
fi
_debug "API check failed for '$_cutdomain'. Continuing search."
p=$i
i=$((i + 1))
done
_err "Could not detect root zone for '$domain'. Please set SPACESHIP_ROOT_DOMAIN manually."
return 1
}
_spaceship_api_request() {
method="$1"
url="$2"
payload="$3"
_debug2 "Sending $method request to $url with payload $payload"
if [ "$method" = "GET" ]; then
response="$(_get "$url")"
else
response="$(_post "$payload" "$url" "" "$method")"
fi
if [ "$?" != "0" ]; then
_err "API request failed. Response: $response"
return 1
fi
_debug2 "API response body: $response"
if [ "$method" = "GET" ]; then
if _contains "$(_head_n 1 <"$HTTP_HEADER")" '200'; then
return 0
fi
else
if _contains "$(_head_n 1 <"$HTTP_HEADER")" '204'; then
return 0
fi
fi
_debug2 "API response header: $HTTP_HEADER"
return 1
}

2
dnsapi/dns_tele3.sh

@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#tele3
Options: Options:
TELE3_Key API Key TELE3_Key API Key
TELE3_Secret API Secret TELE3_Secret API Secret
Author: Roman Blizik <https://github.com/par-pa>
Author: Roman Blizik <@par-pa>
' '
TELE3_API="https://www.tele3.cz/acme/" TELE3_API="https://www.tele3.cz/acme/"

2
dnsapi/dns_tencent.sh

@ -2,7 +2,7 @@
# shellcheck disable=SC2034 # shellcheck disable=SC2034
dns_tencent_info='Tencent.com dns_tencent_info='Tencent.com
Site: cloud.Tencent.com Site: cloud.Tencent.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_tencent
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_tencent
Options: Options:
Tencent_SecretId Secret ID Tencent_SecretId Secret ID
Tencent_SecretKey Secret Key Tencent_SecretKey Secret Key

2
dnsapi/dns_timeweb.sh

@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_timeweb
Options: Options:
TW_Token API JWT token. Get it from the control panel at https://timeweb.cloud/my/api-keys TW_Token API JWT token. Get it from the control panel at https://timeweb.cloud/my/api-keys
Issues: github.com/acmesh-official/acme.sh/issues/5140 Issues: github.com/acmesh-official/acme.sh/issues/5140
Author: Nikolay Pronchev <https://github.com/nikolaypronchev>
Author: Nikolay Pronchev <@nikolaypronchev>
' '
TW_Api="https://api.timeweb.cloud/api/v1" TW_Api="https://api.timeweb.cloud/api/v1"

4
dnsapi/dns_transip.sh

@ -24,7 +24,7 @@ dns_transip_add() {
_debug txtvalue="$txtvalue" _debug txtvalue="$txtvalue"
_transip_setup "$fulldomain" || return 1 _transip_setup "$fulldomain" || return 1
_info "Creating TXT record." _info "Creating TXT record."
if ! _transip_rest POST "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then
if ! _transip_rest POST "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":60}}"; then
_err "Could not add TXT record." _err "Could not add TXT record."
return 1 return 1
fi fi
@ -38,7 +38,7 @@ dns_transip_rm() {
_debug txtvalue="$txtvalue" _debug txtvalue="$txtvalue"
_transip_setup "$fulldomain" || return 1 _transip_setup "$fulldomain" || return 1
_info "Removing TXT record." _info "Removing TXT record."
if ! _transip_rest DELETE "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then
if ! _transip_rest DELETE "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":60}}"; then
_err "Could not remove TXT record $_sub_domain for $domain" _err "Could not remove TXT record $_sub_domain for $domain"
return 1 return 1
fi fi

2
dnsapi/dns_udr.sh

@ -7,7 +7,7 @@ Options:
UDR_USER Username UDR_USER Username
UDR_PASS Password UDR_PASS Password
Issues: github.com/acmesh-official/acme.sh/issues/3923 Issues: github.com/acmesh-official/acme.sh/issues/3923
Author: Andreas Scherer <https://github.com/andischerer>
Author: Andreas Scherer <@andischerer>
' '
UDR_API="https://api.domainreselling.de/api/call.cgi" UDR_API="https://api.domainreselling.de/api/call.cgi"

2
dnsapi/dns_variomedia.sh

@ -74,7 +74,7 @@ dns_variomedia_rm() {
return 1 return 1
fi fi
_record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
_record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f3 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep -i "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
_debug _record_id "$_record_id" _debug _record_id "$_record_id"
if [ "$_record_id" ]; then if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge." _info "Successfully retrieved the record id for ACME challenge."

2
dnsapi/dns_vscale.sh

@ -5,7 +5,7 @@ Site: vscale.io
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_vscale Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_vscale
Options: Options:
VSCALE_API_KEY API Key VSCALE_API_KEY API Key
Author: Alex Loban <https://github.com/LAV45>
Author: Alex Loban <@LAV45>
' '
VSCALE_API_URL="https://api.vscale.io/v1" VSCALE_API_URL="https://api.vscale.io/v1"

1
dnsapi/dns_vultr.sh

@ -6,7 +6,6 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vultr
Options: Options:
VULTR_API_KEY API Key VULTR_API_KEY API Key
Issues: github.com/acmesh-official/acme.sh/issues/2374 Issues: github.com/acmesh-official/acme.sh/issues/2374
Author:
' '
VULTR_Api="https://api.vultr.com/v2" VULTR_Api="https://api.vultr.com/v2"

2
dnsapi/dns_websupport.sh

@ -7,7 +7,7 @@ Options:
WS_ApiKey API Key. Called "Identifier" in the WS Admin WS_ApiKey API Key. Called "Identifier" in the WS Admin
WS_ApiSecret API Secret. Called "Secret key" in the WS Admin WS_ApiSecret API Secret. Called "Secret key" in the WS Admin
Issues: github.com/acmesh-official/acme.sh/issues/3486 Issues: github.com/acmesh-official/acme.sh/issues/3486
Author: trgo.sk <https://github.com/trgosk>, akulumbeg <https://github.com/akulumbeg>
Author: trgo.sk <@trgosk>, @akulumbeg
' '
# Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey # Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey

2
dnsapi/dns_world4you.sh

@ -7,7 +7,7 @@ Options:
WORLD4YOU_USERNAME Username WORLD4YOU_USERNAME Username
WORLD4YOU_PASSWORD Password WORLD4YOU_PASSWORD Password
Issues: github.com/acmesh-official/acme.sh/issues/3269 Issues: github.com/acmesh-official/acme.sh/issues/3269
Author: Lorenz Stechauner <https://www.github.com/NerLOR>
Author: Lorenz Stechauner <@NerLOR>
' '
WORLD4YOU_API="https://my.world4you.com/en" WORLD4YOU_API="https://my.world4you.com/en"

Loading…
Cancel
Save