Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fornex.com API v.2.2.0 support #5162

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
174 changes: 104 additions & 70 deletions dnsapi/dns_fornex.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_fornex_info='Fornex.com
Site: Fornex.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_fornex
Options:
FORNEX_API_KEY API Key
Issues: github.com/acmesh-official/acme.sh/issues/3998
Author: Timur Umarov <[email protected]>
'

FORNEX_API_URL="https://fornex.com/api/dns/v0.1"
# Author: @SBohomolov <[email protected]>
# Site: Fornex.com
# Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_fornex
# Bugs: https://github.com/acmesh-official/acme.sh/issues/5161

######## Public functions #####################

#Usage: dns_fornex_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
## install jq ##

# Check the operating system
if [ "$(uname)" = "Darwin" ]; then
# macOS - install jq using Homebrew
if ! command -v brew >/dev/null 2>&1; then
echo "Error: Homebrew is not installed. Please install Homebrew first." >&2
exit 1
fi
brew install jq
elif [ -f "/etc/redhat-release" ] || [ -f "/etc/centos-release" ] || [ -f "/etc/fedora-release" ]; then
# RedHat/CentOS/Fedora - install jq using yum or dnf
if command -v dnf >/dev/null 2>&1; then
dnf install -y jq
elif command -v yum >/dev/null 2>&1; then
yum install -y jq
else
echo "Error: Neither yum nor dnf package manager found." >&2
exit 1
fi
elif [ -f "/etc/lsb-release" ] || [ -f "/etc/debian_version" ]; then
# Debian/Ubuntu - install jq using apt
if command -v apt >/dev/null 2>&1; then
apt update
apt install -y jq
else
echo "Error: apt package manager not found." >&2
exit 1
fi
else
echo "Error: Unsupported operating system." >&2
exit 1
fi

# jq installed successfully
echo "jq installed successfully."

#######################################################

FORNEX_API_URL="https://fornex.com/api/dns/domain"

######## Public functions ###########################

# Usage: dns_fornex_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_fornex_add() {
fulldomain=$1
txtvalue=$2
Expand All @@ -22,95 +58,90 @@ dns_fornex_add() {
return 1
fi

if ! _get_root "$fulldomain"; then
_err "Unable to determine root domain"
domain=$(echo "$fulldomain" | sed 's/^\*\.//')

if ! _get_domain_id "$domain"; then
_err "Unable to determine domain ID"
return 1
else
_debug _domain "$_domain"
_debug _domain_id "$_domain_id"
fi

_info "Adding record"
if _rest POST "$_domain/entry_set/add/" "host=$fulldomain&type=TXT&value=$txtvalue&apikey=$FORNEX_API_KEY"; then
_debug _response "$response"
if _contains "$response" '"ok": true' || _contains "$response" 'Такая запись уже существует.'; then
_info "Added, OK"
return 0
fi
_info "Adding TXT record for $fulldomain"
# Add the TXT record
if ! _rest POST "$domain/entry_set/" "type=TXT&host=_acme-challenge&value=$txtvalue"; then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To work correctly with subdomains TXT record "host" value must be: _acme-challenge[.subdomain].
I.e. in the case when domain api.thedomain.com doesn't exist, but there is thedomain.com domain with "host": "api" record:

{
    "name": "thedomain.com",
    "created": "...",
    "updated": "...",
    "tags": [],
    "entry_set": [
...
      {
        "id": <xxx>,
        "host": "api",
        "type": "A",
        "prio": null,
        "value": "xx.xx.xx.xx",
        "ttl": null
      },
...

Script must create a TXT record with ".host" == "_acme-challenge.api" value, for example.
Script needs to be modified to account for this.

_err "Failed to add TXT record"
return 1
fi
_err "Add txt record error."
return 1

_info "TXT record added successfully"
return 0
}

#Usage: dns_fornex_rm _acme-challenge.www.domain.com
dns_fornex_rm() {
fulldomain=$1
txtvalue=$2

if ! _Fornex_API; then
return 1
fi

if ! _get_root "$fulldomain"; then
_err "Unable to determine root domain"
domain=$(echo "$fulldomain" | sed 's/^\*\.//')

if ! _get_domain_id "$domain"; then
_err "Unable to determine domain ID"
return 1
else
_debug _domain "$_domain"
_debug _domain_id "$_domain_id"
fi

_debug "Getting txt records"
_rest GET "$_domain/entry_set.json?apikey=$FORNEX_API_KEY"
_info "Removing TXT records for domain: _acme-challenge.$domain"

if ! _contains "$response" "$txtvalue"; then
_err "Txt record not found"
return 1
fi
response=$(curl -X GET -H "Authorization: Api-Key $FORNEX_API_KEY" "https://fornex.com/api/dns/domain/$domain/entry_set/")

# Extract TXT record IDs using jq
txt_ids=$(echo "$response" | jq -r '.[] | select(.type == "TXT") | .id')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a total disaster! dns_fornex_rm() selects and removes ALL TXT records from a domain, regardless of what they contain and who created them. Bye-bye all marketing google-site-verification, yandex-verification and other such records!

Previous version of the script checked TXT record value and only deleted TXT records with the value that script previously added.

I would suggest at least check here that TXT record .host value starts with "_acme-challenge". I.e. use something like this:

  txt_ids=$(echo "$response" | jq -r '.[] | select(.type == "TXT") | select(.host | startswith("_acme-challenge")) | .id')


_record_id="$(echo "$response" | _egrep_o "{[^{]*\"value\"*:*\"$txtvalue\"[^}]*}" | sed -n -e 's#.*"id": \([0-9]*\).*#\1#p')"
_debug "_record_id" "$_record_id"
if [ -z "$_record_id" ]; then
_err "can not find _record_id"
return 1
if [ -z "$txt_ids" ]; then
_info "No TXT records found for domain: _acme-challenge.$domain"
return 0
fi

if ! _rest POST "$_domain/entry_set/$_record_id/delete/" "apikey=$FORNEX_API_KEY"; then
_err "Delete record error."
return 1
fi
for txt_id in $txt_ids; do
_info "Removing TXT record with ID: $txt_id"
if ! curl -X DELETE -H "Authorization: Api-Key $FORNEX_API_KEY" "https://fornex.com/api/dns/domain/$domain/entry_set/$txt_id/"; then
_err "Failed to remove TXT record with ID: $txt_id"
else
_info "TXT record with ID $txt_id removed successfully"
fi
done

return 0
}

#################### Private functions below ##################################

#_acme-challenge.www.domain.com
#returns
# _acme-challenge.www.domain.com
# returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
_get_domain_id() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This updated script doesn't work correctly with subdomains. For example, if I have thedomain.com domain with "host": "api" record:

{
    "name": "thedomain.com",
    "created": "...",
    "updated": "...",
    "tags": [],
    "entry_set": [
...
      {
        "id": <xxx>,
        "host": "api",
        "type": "A",
        "prio": null,
        "value": "xx.xx.xx.xx",
        "ttl": null
      },
...

and I want to renew certificate for api.thedomain.com, script fails, since _get_domain_id() incorrectly returns api.thedomain.com as _domain. And subsequent calls to DNS API return error that there is no such domain:

Getting domain ID for _acme-challenge.api.thedomain.com
api.thedomain.com/entry_set/
response='{"detail":"No Domain matches the given query."}'
Domain ID for api.thedomain.com is {"detail":"No Domain matches the given query."}
_domain_id='{"detail":"No Domain matches the given query."}'

Notice how previous version of the script uses while true; do loop to find a correct "root" domain to use.
New script must do the same.

domain=$1

i=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
_debug "Getting domain ID for $domain"

if ! _rest GET "domain_list.json?q=$h&apikey=$FORNEX_API_KEY"; then
return 1
fi
if echo "$domain" | grep -q "_acme-challenge"; then
# If yes, remove "_acme-challenge" from the domain name
domain=$(echo "$domain" | sed 's/_acme-challenge\.//')
fi

if _contains "$response" "\"$h\"" >/dev/null; then
_domain=$h
return 0
else
_debug "$h not found"
fi
i=$(_math "$i" + 1)
done
if ! _rest GET "$domain/entry_set/"; then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling is somehow incorrect here. In the case outlined above when a domain api.thedomain.com doesn't exist, but there is thedomain.com domain with "host": "api" record

{
    "name": "thedomain.com",
    "created": "...",
    "updated": "...",
    "tags": [],
    "entry_set": [
...
      {
        "id": <xxx>,
        "host": "api",
        "type": "A",
        "prio": null,
        "value": "xx.xx.xx.xx",
        "ttl": null
      },
...

this piece of code calls /api.thedomain.com/entry_set/, fornex DNS API 2.2.0 return error string:

No Domain matches the given query.

But script ignores this error, and goes on as if nothing happened:

Getting domain ID for _acme-challenge.api.thedomain.com
thedomain.com/entry_set/
response='{"detail":"No Domain matches the given query."}'
Domain ID for thedomain.com is {"detail":"No Domain matches the given query."}
_domain_id='{"detail":"No Domain matches the given query."}'

The script should handle this and other similar errors correctly, and terminate processing.

_err "Failed to get domain ID for $domain"
return 1
fi

return 1
_domain_id="$response"
_debug "Domain ID for $domain is $_domain_id"
return 0
}

_Fornex_API() {
Expand All @@ -127,20 +158,23 @@ _Fornex_API() {
_saveaccountconf_mutable FORNEX_API_KEY "$FORNEX_API_KEY"
}

#method method action data
# method method action data
_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"

export _H1="Accept: application/json"
export _H2="Authorization: Api-Key $FORNEX_API_KEY"

if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$FORNEX_API_URL/$ep" "" "$m")"
url="$FORNEX_API_URL/$ep"
response=$(curl -X "$m" -H "Authorization: Api-Key $FORNEX_API_KEY" -d "$data" "$url")
else
response="$(_get "$FORNEX_API_URL/$ep" | _normalizeJson)"
url="$FORNEX_API_URL/$ep"
response=$(curl -X GET -H "Authorization: Api-Key $FORNEX_API_KEY" "$url")
fi

_ret="$?"
Expand Down
Loading