committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 1688 additions and 214 deletions
-
14.github/workflows/pr_dns.yml
-
62.github/workflows/wiki-monitor.yml
-
38acme.sh
-
98deploy/kemplm.sh
-
125deploy/truenas_ws.sh
-
98deploy/vault.sh
-
500deploy/zyxel_gs1900.sh
-
4dnsapi/dns_1984hosting.sh
-
160dnsapi/dns_active24.sh
-
13dnsapi/dns_azure.sh
-
2dnsapi/dns_beget.sh
-
2dnsapi/dns_bookmyname.sh
-
5dnsapi/dns_cloudns.sh
-
5dnsapi/dns_constellix.sh
-
2dnsapi/dns_ddnss.sh
-
2dnsapi/dns_dnshome.sh
-
2dnsapi/dns_duckdns.sh
-
2dnsapi/dns_dyn.sh
-
2dnsapi/dns_dynv6.sh
-
2dnsapi/dns_easydns.sh
-
163dnsapi/dns_edgecenter.sh
-
2dnsapi/dns_fornex.sh
-
2dnsapi/dns_freedns.sh
-
4dnsapi/dns_freemyip.sh
-
1dnsapi/dns_he_ddns.sh
-
2dnsapi/dns_joker.sh
-
96dnsapi/dns_la.sh
-
9dnsapi/dns_mijnhost.sh
-
2dnsapi/dns_mydnsjp.sh
-
2dnsapi/dns_namecom.sh
-
2dnsapi/dns_namesilo.sh
-
186dnsapi/dns_openprovider_rest.sh
-
2dnsapi/dns_pleskxml.sh
-
18dnsapi/dns_rage4.sh
-
2dnsapi/dns_schlundtech.sh
-
38dnsapi/dns_selectel.sh
-
212dnsapi/dns_spaceship.sh
-
2dnsapi/dns_tele3.sh
-
2dnsapi/dns_tencent.sh
-
2dnsapi/dns_timeweb.sh
-
4dnsapi/dns_transip.sh
-
2dnsapi/dns_udr.sh
-
2dnsapi/dns_variomedia.sh
-
2dnsapi/dns_vscale.sh
-
1dnsapi/dns_vultr.sh
-
2dnsapi/dns_websupport.sh
-
2dnsapi/dns_world4you.sh
@ -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 |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
@ -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 |
|||
} |
|||
@ -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' |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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 |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue