2020-12-24 10:03:25 +00:00
#!/usr/bin/env sh
2024-04-18 10:39:35 +00:00
# Supports IONOS DNS API v1.0.1 and IONOS Cloud DNS API v1.15.4
2020-12-24 10:03:25 +00:00
#
# Usage:
2024-04-18 10:39:35 +00:00
# Export IONOS_PREFIX and IONOS_SECRET or IONOS_TOKEN before calling acme.sh:
2020-12-24 10:03:25 +00:00
#
# $ export IONOS_PREFIX="..."
# $ export IONOS_SECRET="..."
2024-04-18 10:39:35 +00:00
# or
# $ export IONOS_TOKEN="..."
2020-12-24 10:03:25 +00:00
#
# $ acme.sh --issue --dns dns_ionos ...
2024-04-18 10:39:35 +00:00
#
# if IONOS_PREFIX and IONOS_SECRET are set, the script will use IONOS DNS API
# if IONOS_TOKEN is set, the script will use the IONOS Cloud DNS API
2020-12-24 10:03:25 +00:00
IONOS_API = "https://api.hosting.ionos.com/dns"
2024-04-18 10:39:35 +00:00
IONOS_CLOUD_API = "https://dns.de-fra.ionos.com"
2020-12-24 10:03:25 +00:00
IONOS_ROUTE_ZONES = "/v1/zones"
2024-04-18 10:39:35 +00:00
IONOS_CLOUD_ROUTE_ZONES = "/zones"
2020-12-24 10:03:25 +00:00
2020-12-24 12:06:07 +00:00
IONOS_TXT_TTL = 60 # minimum accepted by API
2020-12-24 10:03:25 +00:00
IONOS_TXT_PRIO = 10
dns_ionos_add( ) {
fulldomain = $1
txtvalue = $2
2021-01-18 13:35:08 +00:00
if ! _ionos_init; then
return 1
fi
2024-04-30 15:04:24 +00:00
if [ " $_context " = "core" ] ; then
2024-04-18 10:39:35 +00:00
_body = " [{\"name\":\" $_sub_domain . $_domain \",\"type\":\"TXT\",\"content\":\" $txtvalue \",\"ttl\": $IONOS_TXT_TTL ,\"prio\": $IONOS_TXT_PRIO ,\"disabled\":false}] "
2021-01-18 13:35:08 +00:00
2024-04-18 10:39:35 +00:00
if _ionos_rest POST " $IONOS_ROUTE_ZONES / $_zone_id /records " " $_body " && [ " $_code " = "201" ] ; then
_info "TXT record has been created successfully."
return 0
fi
else
2024-04-24 10:11:48 +00:00
_record_name = $( printf "%s" " $fulldomain " | cut -d . -f 1)
_body = " {\"properties\":{\"name\":\" $_record_name \", \"type\":\"TXT\", \"content\":\" $txtvalue \"}} "
2024-04-18 10:39:35 +00:00
if _ionos_cloud_rest POST " $IONOS_CLOUD_ROUTE_ZONES / $_zone_id /records " " $_body " && [ " $_code " = "202" ] ; then
_info "TXT record has been created successfully."
return 0
fi
2020-12-24 10:03:25 +00:00
fi
return 1
}
dns_ionos_rm( ) {
fulldomain = $1
txtvalue = $2
2021-01-18 13:35:08 +00:00
if ! _ionos_init; then
return 1
fi
2020-12-24 10:03:25 +00:00
2024-04-30 15:04:24 +00:00
if [ " $_context " = "core" ] ; then
2024-04-22 13:48:01 +00:00
if ! _ionos_get_record " $fulldomain " " $_zone_id " " $txtvalue " ; then
_err "Could not find _acme-challenge TXT record."
return 1
fi
if _ionos_rest DELETE " $IONOS_ROUTE_ZONES / $_zone_id /records/ $_record_id " && [ " $_code " = "200" ] ; then
_info "TXT record has been deleted successfully."
return 0
fi
else
2024-04-30 10:38:02 +00:00
if ! _ionos_cloud_get_record " $_zone_id " " $txtvalue " " $fulldomain " ; then
2024-04-22 13:48:01 +00:00
_err "Could not find _acme-challenge TXT record."
return 1
fi
2024-04-30 10:38:02 +00:00
if _ionos_cloud_rest DELETE " $IONOS_CLOUD_ROUTE_ZONES / $_zone_id /records/ $_record_id " && [ " $_code " = "202" ] ; then
2024-04-22 13:48:01 +00:00
_info "TXT record has been deleted successfully."
return 0
fi
2020-12-24 10:03:25 +00:00
fi
return 1
}
_ionos_init( ) {
IONOS_PREFIX = " ${ IONOS_PREFIX :- $( _readaccountconf_mutable IONOS_PREFIX) } "
IONOS_SECRET = " ${ IONOS_SECRET :- $( _readaccountconf_mutable IONOS_SECRET) } "
2024-04-18 10:39:35 +00:00
IONOS_TOKEN = " ${ IONOS_TOKEN :- $( _readaccountconf_mutable IONOS_TOKEN) } "
2024-04-22 13:48:01 +00:00
if [ -n " $IONOS_PREFIX " ] && [ -n " $IONOS_SECRET " ] ; then
2024-04-18 10:39:35 +00:00
_info "You have specified an IONOS api prefix and secret."
_info " The script will use the IONOS DNS API: $IONOS_API "
_saveaccountconf_mutable IONOS_PREFIX " $IONOS_PREFIX "
_saveaccountconf_mutable IONOS_SECRET " $IONOS_SECRET "
if ! _get_root " $fulldomain " ; then
_err "Cannot find this domain in your IONOS account."
return 1
fi
2024-04-25 15:38:13 +00:00
_context = "core"
2024-04-22 14:16:03 +00:00
elif [ -n " $IONOS_TOKEN " ] ; then
2024-04-18 10:39:35 +00:00
_info "You have specified an IONOS token."
_info " The script will use the IONOS Cloud DNS API: $IONOS_CLOUD_API "
_saveaccountconf_mutable IONOS_TOKEN " $IONOS_TOKEN "
2020-12-24 12:06:07 +00:00
2024-04-18 10:39:35 +00:00
if ! _get_cloud_zone " $fulldomain " ; then
2024-04-24 09:58:38 +00:00
_err " Cannot find zone $zone in your IONOS account. "
2024-04-18 10:39:35 +00:00
return 1
fi
2024-04-25 15:38:13 +00:00
_context = "cloud"
2024-04-18 10:39:35 +00:00
else
2024-04-22 13:48:01 +00:00
_err "You didn't specify any IONOS credentials yet."
2024-04-18 10:39:35 +00:00
_err "If you are using the IONOS DNS API, Read https://beta.developer.hosting.ionos.de/docs/getstarted to learn how to get a prefix and secret."
_err "If you are using the IONOS Cloud DNS API, Read https://api.ionos.com/docs/authentication/v1/#tag/tokens/operation/tokensGenerate to learn how to get a token."
2020-12-24 10:03:25 +00:00
_err ""
_err "Then set them before calling acme.sh:"
_err "\$ export IONOS_PREFIX=\"...\""
_err "\$ export IONOS_SECRET=\"...\""
2024-04-18 10:39:35 +00:00
_err "#or"
_err "\$ export IONOS_TOKEN=\"...\""
2020-12-24 10:03:25 +00:00
_err "\$ acme.sh --issue -d ... --dns dns_ionos"
return 1
fi
2024-04-18 10:39:35 +00:00
return 0
}
2020-12-24 10:03:25 +00:00
_get_root( ) {
domain = $1
2021-01-21 15:10:10 +00:00
i = 1
2020-12-24 10:03:25 +00:00
p = 1
if _ionos_rest GET " $IONOS_ROUTE_ZONES " ; then
2022-05-22 11:24:18 +00:00
_response = " $( echo " $_response " | tr -d "\n" ) "
2020-12-24 10:03:25 +00:00
while true; do
h = $( printf "%s" " $domain " | cut -d . -f $i -100)
if [ -z " $h " ] ; then
return 1
fi
2022-05-22 11:24:18 +00:00
_zone = " $( echo " $_response " | _egrep_o " \"name\":\" $h \".*\} " ) "
2020-12-24 10:03:25 +00:00
if [ " $_zone " ] ; then
2021-01-20 20:08:58 +00:00
_zone_id = $( printf "%s\n" " $_zone " | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"' )
2020-12-24 10:03:25 +00:00
if [ " $_zone_id " ] ; then
_sub_domain = $( printf "%s" " $domain " | cut -d . -f 1-$p )
_domain = $h
return 0
fi
return 1
fi
p = $i
i = $( _math " $i " + 1)
done
fi
return 1
}
2024-04-22 13:48:01 +00:00
_get_cloud_zone( ) {
2024-04-24 09:58:38 +00:00
domain = $1
zone = $( printf "%s" " $domain " | cut -d . -f 2-)
2024-04-22 13:48:01 +00:00
if _ionos_cloud_rest GET " $IONOS_CLOUD_ROUTE_ZONES ?filter.zoneName= $zone " ; then
_response = " $( echo " $_response " | tr -d "\n" ) "
2024-04-24 09:58:38 +00:00
_zone_list_items = $( echo " $_response " | _egrep_o "\"items\":.*" )
_zone_id = $( printf "%s\n" " $_zone_list_items " | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"' )
if [ " $_zone_id " ] ; then
return 0
2024-04-22 13:48:01 +00:00
fi
fi
return 1
}
2020-12-24 10:03:25 +00:00
_ionos_get_record( ) {
fulldomain = $1
zone_id = $2
2021-01-18 13:35:08 +00:00
txtrecord = $3
2020-12-24 10:03:25 +00:00
if _ionos_rest GET " $IONOS_ROUTE_ZONES / $zone_id ?recordName= $fulldomain &recordType=TXT " ; then
2022-05-22 11:24:18 +00:00
_response = " $( echo " $_response " | tr -d "\n" ) "
2020-12-24 10:03:25 +00:00
2022-05-22 11:24:18 +00:00
_record = " $( echo " $_response " | _egrep_o " \"name\":\" $fulldomain \"[^\}]*\"type\":\"TXT\"[^\}]*\"content\":\"\\\\\" $txtrecord \\\\\"\".*\} " ) "
2020-12-24 10:03:25 +00:00
if [ " $_record " ] ; then
2021-01-20 20:08:58 +00:00
_record_id = $( printf "%s\n" " $_record " | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"' )
2020-12-24 12:06:07 +00:00
return 0
2020-12-24 10:03:25 +00:00
fi
fi
return 1
}
2024-04-22 13:48:01 +00:00
_ionos_cloud_get_record( ) {
2024-04-30 10:38:02 +00:00
zone_id = $1
txtrecord = $2
2024-05-02 16:48:39 +00:00
# this is to transform the domain to lower case
fulldomain = $( printf "%s" " $3 " | tr "[:upper:]" "[:lower:]" )
# this is to transform record name to lower case
2024-04-30 14:20:07 +00:00
# IONOS Cloud API transforms all record names to lower case
2024-05-02 16:48:39 +00:00
_record_name = $( printf "%s" " $fulldomain " | cut -d . -f 1 | tr "[:upper:]" "[:lower:]" )
2024-04-22 13:48:01 +00:00
2024-04-30 13:44:39 +00:00
_info " grepping with the following args: zone_id= $zone_id txtrecord= $txtrecord fulldomain= $fulldomain _record_name= $_record_name "
2024-04-30 10:38:02 +00:00
if _ionos_cloud_rest GET " $IONOS_CLOUD_ROUTE_ZONES / $zone_id /records " ; then
2024-04-22 13:48:01 +00:00
_response = " $( echo " $_response " | tr -d "\n" ) "
2024-04-30 10:38:02 +00:00
pattern = " {\"id\":\"[a-fA-F0-9\-]*\",\"type\":\"record\",\"href\":\"/zones/ $zone_id /records/[a-fA-F0-9\-]*\",\"metadata\":{\"createdDate\":\"[A-Z0-9\:\.\-]*\",\"lastModifiedDate\":\"[A-Z0-9\:\.\-]*\",\"fqdn\":\" $fulldomain \",\"state\":\"AVAILABLE\",\"zoneId\":\" $zone_id \"},\"properties\":{\"content\":\" $txtrecord \",\"enabled\":true,\"name\":\" $_record_name \",\"priority\":[0-9]*,\"ttl\":[0-9]*,\"type\":\"TXT\"}} "
2024-04-30 15:04:24 +00:00
_record = " $( echo " $_response " | _egrep_o " $pattern " ) "
2024-04-30 13:34:49 +00:00
_info " the found record after grep: $_record "
2024-04-22 13:48:01 +00:00
if [ " $_record " ] ; then
_record_id = $( printf "%s\n" " $_record " | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"' )
2024-04-30 13:34:49 +00:00
_info " the record id after the search is: $_record_id "
2024-04-22 13:48:01 +00:00
return 0
fi
fi
return 1
}
2020-12-24 10:03:25 +00:00
_ionos_rest( ) {
method = " $1 "
route = " $2 "
data = " $3 "
IONOS_API_KEY = " $( printf "%s.%s" " $IONOS_PREFIX " " $IONOS_SECRET " ) "
export _H1 = " X-API-Key: $IONOS_API_KEY "
2022-05-22 11:24:18 +00:00
# clear headers
: >" $HTTP_HEADER "
2020-12-24 10:03:25 +00:00
if [ " $method " != "GET" ] ; then
export _H2 = "Accept: application/json"
export _H3 = "Content-Type: application/json"
2022-05-22 11:24:18 +00:00
_response = " $( _post " $data " " $IONOS_API $route " "" " $method " "application/json" ) "
2020-12-24 10:03:25 +00:00
else
export _H2 = "Accept: */*"
2021-05-25 13:57:15 +00:00
export _H3 =
2022-05-22 11:24:18 +00:00
_response = " $( _get " $IONOS_API $route " ) "
2020-12-24 10:03:25 +00:00
fi
2022-05-22 11:24:18 +00:00
_code = " $( grep "^HTTP" " $HTTP_HEADER " | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n" ) "
2020-12-24 10:03:25 +00:00
if [ " $? " != "0" ] ; then
2022-05-22 11:24:18 +00:00
_err " Error $route : $_response "
2020-12-24 10:03:25 +00:00
return 1
fi
2022-05-22 11:24:18 +00:00
_debug2 "_response" " $_response "
_debug2 "_code" " $_code "
2020-12-24 10:03:25 +00:00
return 0
}
2024-04-18 10:39:35 +00:00
_ionos_cloud_rest( ) {
method = " $1 "
route = " $2 "
data = " $3 "
export _H1 = " Authorization: Bearer $IONOS_TOKEN "
# clear headers
: >" $HTTP_HEADER "
if [ " $method " != "GET" ] ; then
_response = " $( _post " $data " " $IONOS_CLOUD_API $route " "" " $method " "application/json" ) "
else
_response = " $( _get " $IONOS_CLOUD_API $route " ) "
fi
_code = " $( grep "^HTTP" " $HTTP_HEADER " | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n" ) "
if [ " $? " != "0" ] ; then
_err " Error $route : $_response "
return 1
fi
_debug2 "_response" " $_response "
_debug2 "_code" " $_code "
return 0
}