diff --git a/acme.sh b/acme.sh index 18456968..55fa4467 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=3.0.2 +VER=3.0.3 PROJECT_NAME="acme.sh" @@ -1141,13 +1141,19 @@ _createkey() { _debug "Use length $length" - if ! touch "$f" >/dev/null 2>&1; then - _f_path="$(dirname "$f")" - _debug _f_path "$_f_path" - if ! mkdir -p "$_f_path"; then - _err "Can not create path: $_f_path" + if ! [ -e "$f" ]; then + if ! touch "$f" >/dev/null 2>&1; then + _f_path="$(dirname "$f")" + _debug _f_path "$_f_path" + if ! mkdir -p "$_f_path"; then + _err "Can not create path: $_f_path" + return 1 + fi + fi + if ! touch "$f" >/dev/null 2>&1; then return 1 fi + chmod 600 "$f" fi if _isEccKey "$length"; then @@ -1495,7 +1501,6 @@ _create_account_key() { else #generate account key if _createkey "$length" "$ACCOUNT_KEY_PATH"; then - chmod 600 "$ACCOUNT_KEY_PATH" _info "Create account key ok." return 0 else @@ -5611,8 +5616,9 @@ _installcert() { if [ -f "$_real_key" ]; then cat "$CERT_KEY_PATH" >"$_real_key" || return 1 else - cat "$CERT_KEY_PATH" >"$_real_key" || return 1 + touch "$_real_key" || return 1 chmod 600 "$_real_key" + cat "$CERT_KEY_PATH" >"$_real_key" || return 1 fi fi diff --git a/deploy/routeros.sh b/deploy/routeros.sh index 9965d65c..b2b18c5e 100644 --- a/deploy/routeros.sh +++ b/deploy/routeros.sh @@ -23,6 +23,7 @@ # ```sh # export ROUTER_OS_USERNAME=certuser # export ROUTER_OS_HOST=router.example.com +# export ROUTER_OS_PORT=22 # # acme.sh --deploy -d ftp.example.com --deploy-hook routeros # ``` @@ -48,6 +49,16 @@ # One optional thing to do as well is to create a script that updates # all the required services and run that script in a single command. # +# To adopt parameters to `scp` and/or `ssh` set the optional +# `ROUTER_OS_SSH_CMD` and `ROUTER_OS_SCP_CMD` variables accordingly, +# see ssh(1) and scp(1) for parameters to those commands. +# +# Example: +# ```ssh +# export ROUTER_OS_SSH_CMD="ssh -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts" +# export ROUTER_OS_SCP_CMD="scp -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts" +# ```` +# # returns 0 means success, otherwise error. ######## Public functions ##################### @@ -80,6 +91,27 @@ routeros_deploy() { return 1 fi + _getdeployconf ROUTER_OS_PORT + + if [ -z "$ROUTER_OS_PORT" ]; then + _debug "Using default port 22 as ROUTER_OS_PORT, please set if not correct." + ROUTER_OS_PORT=22 + fi + + _getdeployconf ROUTER_OS_SSH_CMD + + if [ -z "$ROUTER_OS_SSH_CMD" ]; then + _debug "Use default ssh setup." + ROUTER_OS_SSH_CMD="ssh -p $ROUTER_OS_PORT" + fi + + _getdeployconf ROUTER_OS_SCP_CMD + + if [ -z "$ROUTER_OS_SCP_CMD" ]; then + _debug "USe default scp setup." + ROUTER_OS_SCP_CMD="scp -P $ROUTER_OS_PORT" + fi + _getdeployconf ROUTER_OS_ADDITIONAL_SERVICES if [ -z "$ROUTER_OS_ADDITIONAL_SERVICES" ]; then @@ -89,16 +121,20 @@ routeros_deploy() { _savedeployconf ROUTER_OS_HOST "$ROUTER_OS_HOST" _savedeployconf ROUTER_OS_USERNAME "$ROUTER_OS_USERNAME" + _savedeployconf ROUTER_OS_PORT "$ROUTER_OS_PORT" + _savedeployconf ROUTER_OS_SSH_CMD "$ROUTER_OS_SSH_CMD" + _savedeployconf ROUTER_OS_SCP_CMD "$ROUTER_OS_SCP_CMD" _savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES" _info "Trying to push key '$_ckey' to router" - scp "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key" + $ROUTER_OS_SCP_CMD "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key" _info "Trying to push cert '$_cfullchain' to router" - scp "$_cfullchain" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer" - DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=admin policy=ftp,read,write,password,sensitive \ -source=\"## generated by routeros deploy script in acme.sh;\ -\n/certificate remove [ find name=$_cdomain.cer_0 ];\ + $ROUTER_OS_SCP_CMD "$_cfullchain" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer" + DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=$ROUTER_OS_USER \ +comment=\"generated by routeros deploy script in acme.sh\" \ +source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\ \n/certificate remove [ find name=$_cdomain.cer_1 ];\ +\n/certificate remove [ find name=$_cdomain.cer_2 ];\ \ndelay 1;\ \n/certificate import file-name=$_cdomain.cer passphrase=\\\"\\\";\ \n/certificate import file-name=$_cdomain.key passphrase=\\\"\\\";\ @@ -111,11 +147,11 @@ source=\"## generated by routeros deploy script in acme.sh;\ \n\" " # shellcheck disable=SC2029 - ssh "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "$DEPLOY_SCRIPT_CMD" + $ROUTER_OS_SSH_CMD "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "$DEPLOY_SCRIPT_CMD" # shellcheck disable=SC2029 - ssh "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "/system script run \"LE Cert Deploy - $_cdomain\"" + $ROUTER_OS_SSH_CMD "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "/system script run \"LE Cert Deploy - $_cdomain\"" # shellcheck disable=SC2029 - ssh "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "/system script remove \"LE Cert Deploy - $_cdomain\"" + $ROUTER_OS_SSH_CMD "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "/system script remove \"LE Cert Deploy - $_cdomain\"" return 0 } diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index 66e28f93..f30f82c0 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -94,7 +94,12 @@ synology_dsm_deploy() { otp_code="" if [ -n "$SYNO_TOTP_SECRET" ]; then - otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)" + if _exists oathtool; then + otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)" + else + _err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET" + return 1 + fi fi if [ -n "$SYNO_DID" ]; then diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index 36799dcd..c2430086 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -25,9 +25,15 @@ dns_cf_add() { CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}" if [ "$CF_Token" ]; then - _saveaccountconf_mutable CF_Token "$CF_Token" - _saveaccountconf_mutable CF_Account_ID "$CF_Account_ID" - _saveaccountconf_mutable CF_Zone_ID "$CF_Zone_ID" + if [ "$CF_Zone_ID" ]; then + _savedomainconf CF_Token "$CF_Token" + _savedomainconf CF_Account_ID "$CF_Account_ID" + _savedomainconf CF_Zone_ID "$CF_Zone_ID" + else + _saveaccountconf_mutable CF_Token "$CF_Token" + _saveaccountconf_mutable CF_Account_ID "$CF_Account_ID" + _saveaccountconf_mutable CF_Zone_ID "$CF_Zone_ID" + fi else if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then CF_Key="" diff --git a/dnsapi/dns_curanet.sh b/dnsapi/dns_curanet.sh new file mode 100644 index 00000000..4b39f365 --- /dev/null +++ b/dnsapi/dns_curanet.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env sh + +#Script to use with curanet.dk, scannet.dk, wannafind.dk, dandomain.dk DNS management. +#Requires api credentials with scope: dns +#Author: Peter L. Hansen +#Version 1.0 + +CURANET_REST_URL="https://api.curanet.dk/dns/v1/Domains" +CURANET_AUTH_URL="https://apiauth.dk.team.blue/auth/realms/Curanet/protocol/openid-connect/token" +CURANET_ACCESS_TOKEN="" + +######## Public functions ##################### + +#Usage: dns_curanet_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_curanet_add() { + fulldomain=$1 + txtvalue=$2 + _info "Using curanet" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + CURANET_AUTHCLIENTID="${CURANET_AUTHCLIENTID:-$(_readaccountconf_mutable CURANET_AUTHCLIENTID)}" + CURANET_AUTHSECRET="${CURANET_AUTHSECRET:-$(_readaccountconf_mutable CURANET_AUTHSECRET)}" + if [ -z "$CURANET_AUTHCLIENTID" ] || [ -z "$CURANET_AUTHSECRET" ]; then + CURANET_AUTHCLIENTID="" + CURANET_AUTHSECRET="" + _err "You don't specify curanet api client and secret." + _err "Please create your auth info and try again." + return 1 + fi + + #save the credentials to the account conf file. + _saveaccountconf_mutable CURANET_AUTHCLIENTID "$CURANET_AUTHCLIENTID" + _saveaccountconf_mutable CURANET_AUTHSECRET "$CURANET_AUTHSECRET" + + if ! _get_token; then + _err "Unable to get token" + return 1 + fi + + if ! _get_root "$fulldomain"; then + _err "Invalid domain" + return 1 + fi + + export _H1="Content-Type: application/json-patch+json" + export _H2="Accept: application/json" + export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" + data="{\"name\": \"$fulldomain\",\"type\": \"TXT\",\"ttl\": 60,\"priority\": 0,\"data\": \"$txtvalue\"}" + response="$(_post "$data" "$CURANET_REST_URL/${_domain}/Records" "" "")" + + if _contains "$response" "$txtvalue"; then + _debug "TXT record added OK" + else + _err "Unable to add TXT record" + return 1 + fi + + return 0 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_curanet_rm() { + fulldomain=$1 + txtvalue=$2 + _info "Using curanet" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + CURANET_AUTHCLIENTID="${CURANET_AUTHCLIENTID:-$(_readaccountconf_mutable CURANET_AUTHCLIENTID)}" + CURANET_AUTHSECRET="${CURANET_AUTHSECRET:-$(_readaccountconf_mutable CURANET_AUTHSECRET)}" + + if ! _get_token; then + _err "Unable to get token" + return 1 + fi + + if ! _get_root "$fulldomain"; then + _err "Invalid domain" + return 1 + fi + + _debug "Getting current record list to identify TXT to delete" + + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" + + response="$(_get "$CURANET_REST_URL/${_domain}/Records" "" "")" + + if ! _contains "$response" "$txtvalue"; then + _err "Unable to delete record (does not contain $txtvalue )" + return 1 + fi + + recordid=$(echo "$response" | _egrep_o "{\"id\":[0-9]+,\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":60,\"priority\":0,\"data\":\"..$txtvalue" | _egrep_o "id\":[0-9]+" | cut -c 5-) + + if [ -z "$recordid" ]; then + _err "Unable to get recordid" + _debug "regex {\"id\":[0-9]+,\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":60,\"priority\":0,\"data\":\"..$txtvalue" + _debug "response $response" + return 1 + fi + + _debug "Deleting recordID $recordid" + response="$(_post "" "$CURANET_REST_URL/${_domain}/Records/$recordid" "" "DELETE")" + return 0 +} + +#################### Private functions below ################################## + +_get_token() { + response="$(_post "grant_type=client_credentials&client_id=$CURANET_AUTHCLIENTID&client_secret=$CURANET_AUTHSECRET&scope=dns" "$CURANET_AUTH_URL" "" "")" + if ! _contains "$response" "access_token"; then + _err "Unable get access token" + return 1 + fi + CURANET_ACCESS_TOKEN=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]+" | cut -c 17-) + + if [ -z "$CURANET_ACCESS_TOKEN" ]; then + _err "Unable to get token" + return 1 + fi + + return 0 + +} + +#_acme-challenge.www.domain.com +#returns +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + 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 + + export _H1="Content-Type: application/json" + export _H2="Accept: application/json" + export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" + response="$(_get "$CURANET_REST_URL/$h/Records" "" "")" + + if [ ! "$(echo "$response" | _egrep_o "Entity not found")" ]; then + _domain=$h + return 0 + fi + + i=$(_math "$i" + 1) + done + return 1 +} diff --git a/dnsapi/dns_geoscaling.sh b/dnsapi/dns_geoscaling.sh new file mode 100755 index 00000000..6d61312d --- /dev/null +++ b/dnsapi/dns_geoscaling.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env sh + +######################################################################## +# Geoscaling hook script for acme.sh +# +# Environment variables: +# +# - $GEOSCALING_Username (your Geoscaling username - this is usually NOT an amail address) +# - $GEOSCALING_Password (your Geoscaling password) + +#-- dns_geoscaling_add() - Add TXT record -------------------------------------- +# Usage: dns_geoscaling_add _acme-challenge.subdomain.domain.com "XyZ123..." + +dns_geoscaling_add() { + full_domain=$1 + txt_value=$2 + _info "Using DNS-01 Geoscaling DNS2 hook" + + GEOSCALING_Username="${GEOSCALING_Username:-$(_readaccountconf_mutable GEOSCALING_Username)}" + GEOSCALING_Password="${GEOSCALING_Password:-$(_readaccountconf_mutable GEOSCALING_Password)}" + if [ -z "$GEOSCALING_Username" ] || [ -z "$GEOSCALING_Password" ]; then + GEOSCALING_Username= + GEOSCALING_Password= + _err "No auth details provided. Please set user credentials using the \$GEOSCALING_Username and \$GEOSCALING_Password environment variables." + return 1 + fi + _saveaccountconf_mutable GEOSCALING_Username "${GEOSCALING_Username}" + _saveaccountconf_mutable GEOSCALING_Password "${GEOSCALING_Password}" + + # Fills in the $zone_id and $zone_name + find_zone "${full_domain}" || return 1 + _debug "Zone id '${zone_id}' will be used." + + # We're logged in here + + # we should add ${full_domain} minus the trailing ${zone_name} + + prefix=$(echo "${full_domain}" | sed "s|\\.${zone_name}\$||") + + body="id=${zone_id}&name=${prefix}&type=TXT&content=${txt_value}&ttl=300&prio=0" + + do_post "$body" "https://www.geoscaling.com/dns2/ajax/add_record.php" + exit_code="$?" + if [ "${exit_code}" -eq 0 ]; then + _info "TXT record added successfully." + else + _err "Couldn't add the TXT record." + fi + do_logout + return "${exit_code}" +} + +#-- dns_geoscaling_rm() - Remove TXT record ------------------------------------ +# Usage: dns_geoscaling_rm _acme-challenge.subdomain.domain.com "XyZ123..." + +dns_geoscaling_rm() { + full_domain=$1 + txt_value=$2 + _info "Cleaning up after DNS-01 Geoscaling DNS2 hook" + + # fills in the $zone_id + find_zone "${full_domain}" || return 1 + _debug "Zone id '${zone_id}' will be used." + + # Here we're logged in + # Find the record id to clean + + # get the domain + response=$(do_get "https://www.geoscaling.com/dns2/index.php?module=domain&id=${zone_id}") + _debug2 "response" "$response" + + table="$(echo "${response}" | tr -d '\n' | sed 's|.*
Basic Records
.*||')" + _debug2 table "${table}" + names=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|||; s|.*>||') + ids=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|\.name">.*||; s|id="||') + types=$(echo "${table}" | _egrep_o 'id="[0-9]+\.type">[^<]*' | sed 's|||; s|.*>||') + values=$(echo "${table}" | _egrep_o 'id="[0-9]+\.content">[^<]*' | sed 's|||; s|.*>||') + + _debug2 names "${names}" + _debug2 ids "${ids}" + _debug2 types "${types}" + _debug2 values "${values}" + + # look for line whose name is ${full_domain}, whose type is TXT, and whose value is ${txt_value} + line_num="$(echo "${values}" | grep -F -n -- "${txt_value}" | _head_n 1 | cut -d ':' -f 1)" + _debug2 line_num "${line_num}" + found_id= + if [ -n "$line_num" ]; then + type=$(echo "${types}" | sed -n "${line_num}p") + name=$(echo "${names}" | sed -n "${line_num}p") + id=$(echo "${ids}" | sed -n "${line_num}p") + + _debug2 type "$type" + _debug2 name "$name" + _debug2 id "$id" + _debug2 full_domain "$full_domain" + + if [ "${type}" = "TXT" ] && [ "${name}" = "${full_domain}" ]; then + found_id=${id} + fi + fi + + if [ "${found_id}" = "" ]; then + _err "Can not find record id." + return 0 + fi + + # Remove the record + body="id=${zone_id}&record_id=${found_id}" + response=$(do_post "$body" "https://www.geoscaling.com/dns2/ajax/delete_record.php") + exit_code="$?" + if [ "$exit_code" -eq 0 ]; then + _info "Record removed successfully." + else + _err "Could not clean (remove) up the record. Please go to Geoscaling administration interface and clean it by hand." + fi + do_logout + return "${exit_code}" +} + +########################## PRIVATE FUNCTIONS ########################### + +do_get() { + _url=$1 + export _H1="Cookie: $geoscaling_phpsessid_cookie" + _get "${_url}" +} + +do_post() { + _body=$1 + _url=$2 + export _H1="Cookie: $geoscaling_phpsessid_cookie" + _post "${_body}" "${_url}" +} + +do_login() { + + _info "Logging in..." + + username_encoded="$(printf "%s" "${GEOSCALING_Username}" | _url_encode)" + password_encoded="$(printf "%s" "${GEOSCALING_Password}" | _url_encode)" + body="username=${username_encoded}&password=${password_encoded}" + + response=$(_post "$body" "https://www.geoscaling.com/dns2/index.php?module=auth") + _debug2 response "${response}" + + #retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | _egrep_o '[0-9]+$') + retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | cut -d ' ' -f 2) + + if [ "$retcode" != "302" ]; then + _err "Geoscaling login failed for user ${GEOSCALING_Username}. Check ${HTTP_HEADER} file" + return 1 + fi + + geoscaling_phpsessid_cookie="$(grep -i '^set-cookie:' "${HTTP_HEADER}" | _egrep_o 'PHPSESSID=[^;]*;' | tr -d ';')" + return 0 + +} + +do_logout() { + _info "Logging out." + response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=auth")" + _debug2 response "$response" + return 0 +} + +find_zone() { + domain="$1" + + # do login + do_login || return 1 + + # get zones + response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=domains")" + + table="$(echo "${response}" | tr -d '\n' | sed 's|.*
Your domains
.*||')" + _debug2 table "${table}" + zone_names="$(echo "${table}" | _egrep_o '[^<]*' | sed 's|||;s|||')" + _debug2 _matches "${zone_names}" + # Zone names and zone IDs are in same order + zone_ids=$(echo "${table}" | _egrep_o '' | sed 's|.*id=||;s|. .*||') + + _debug2 "These are the zones on this Geoscaling account:" + _debug2 "zone_names" "${zone_names}" + _debug2 "And these are their respective IDs:" + _debug2 "zone_ids" "${zone_ids}" + if [ -z "${zone_names}" ] || [ -z "${zone_ids}" ]; then + _err "Can not get zone names or IDs." + return 1 + fi + # Walk through all possible zone names + strip_counter=1 + while true; do + attempted_zone=$(echo "${domain}" | cut -d . -f ${strip_counter}-) + + # All possible zone names have been tried + if [ -z "${attempted_zone}" ]; then + _err "No zone for domain '${domain}' found." + return 1 + fi + + _debug "Looking for zone '${attempted_zone}'" + + line_num="$(echo "${zone_names}" | grep -n "^${attempted_zone}\$" | _head_n 1 | cut -d : -f 1)" + _debug2 line_num "${line_num}" + if [ "$line_num" ]; then + zone_id=$(echo "${zone_ids}" | sed -n "${line_num}p") + zone_name=$(echo "${zone_names}" | sed -n "${line_num}p") + if [ -z "${zone_id}" ]; then + _err "Can not find zone id." + return 1 + fi + _debug "Found relevant zone '${attempted_zone}' with id '${zone_id}' - will be used for domain '${domain}'." + return 0 + fi + + _debug "Zone '${attempted_zone}' doesn't exist, let's try a less specific zone." + strip_counter=$(_math "${strip_counter}" + 1) + done +} +# vim: et:ts=2:sw=2: diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index 765e0eb5..e68ddd49 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/dnsapi/dns_ispconfig.sh @@ -32,7 +32,7 @@ dns_ispconfig_rm() { #################### Private functions below ################################## _ISPC_credentials() { - if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -n "${ISPC_Api_Insecure}" ]; then + if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then ISPC_User="" ISPC_Password="" ISPC_Api="" diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh index 7760b53e..e95d8999 100644 --- a/dnsapi/dns_loopia.sh +++ b/dnsapi/dns_loopia.sh @@ -32,8 +32,12 @@ dns_loopia_add() { _info "Adding record" - _loopia_add_sub_domain "$_domain" "$_sub_domain" - _loopia_add_record "$_domain" "$_sub_domain" "$txtvalue" + if ! _loopia_add_sub_domain "$_domain" "$_sub_domain"; then + return 1 + fi + if ! _loopia_add_record "$_domain" "$_sub_domain" "$txtvalue"; then + return 1 + fi } @@ -70,12 +74,13 @@ dns_loopia_rm() { %s - ' "$LOOPIA_User" "$LOOPIA_Password" "$_domain" "$_sub_domain") + ' "$LOOPIA_User" "$Encoded_Password" "$_domain" "$_sub_domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" "OK"; then - _err "Error could not get txt records" + err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+") + _err "Error could not get txt records: $err_response" return 1 fi } @@ -101,6 +106,12 @@ _loopia_load_config() { return 1 fi + if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then + _err "Password contains quoute or double quoute and this is not supported by dns_loopia.sh" + return 1 + fi + + Encoded_Password=$(_xml_encode "$LOOPIA_Password") return 0 } @@ -133,11 +144,12 @@ _loopia_get_records() { %s - ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain") + ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" ""; then - _err "Error" + err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+") + _err "Error: $err_response" return 1 fi return 0 @@ -162,7 +174,7 @@ _get_root() { %s - ' $LOOPIA_User $LOOPIA_Password) + ' "$LOOPIA_User" "$Encoded_Password") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" while true; do @@ -206,32 +218,35 @@ _loopia_add_record() { %s - - - type - TXT - - - priority - 0 - - - ttl - 300 - - - rdata - %s - - + + + + type + TXT + + + priority + 0 + + + ttl + 300 + + + rdata + %s + + + - ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain" "$txtval") + ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain" "$txtval") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" "OK"; then - _err "Error" + err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+") + _err "Error: $err_response" return 1 fi return 0 @@ -255,7 +270,7 @@ _sub_domain_exists() { %s - ' $LOOPIA_User $LOOPIA_Password "$domain") + ' "$LOOPIA_User" "$Encoded_Password" "$domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" @@ -290,13 +305,22 @@ _loopia_add_sub_domain() { %s - ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain") + ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" "OK"; then - _err "Error" + err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+") + _err "Error: $err_response" return 1 fi return 0 } + +_xml_encode() { + encoded_string=$1 + encoded_string=$(echo "$encoded_string" | sed 's/&/\&/') + encoded_string=$(echo "$encoded_string" | sed 's//\>/') + printf "%s" "$encoded_string" +} diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh index 26a422f8..eb95902f 100755 --- a/dnsapi/dns_opnsense.sh +++ b/dnsapi/dns_opnsense.sh @@ -150,8 +150,7 @@ _get_root() { return 1 fi _debug h "$h" - id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":{\"\":{[^}]*}}(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2) - + id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":{\"[^\"]*\":{[^}]*}},\"transferkeyalgo\":{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^}]*}},\"transferkey\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2) if [ -n "$id" ]; then _debug id "$id" _host=$(printf "%s" "$domain" | cut -d . -f 1-$p) diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh new file mode 100644 index 00000000..caada826 --- /dev/null +++ b/dnsapi/dns_udr.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env sh + +# united-domains Reselling (https://www.ud-reselling.com/) DNS API +# Author: Andreas Scherer (https://github.com/andischerer) +# Created: 2021-02-01 +# +# Set the environment variables as below: +# +# export UDR_USER="your_username_goes_here" +# export UDR_PASS="some_password_goes_here" +# + +UDR_API="https://api.domainreselling.de/api/call.cgi" +UDR_TTL="30" + +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt" +dns_udr_add() { + fulldomain=$1 + txtvalue=$2 + + UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}" + UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}" + if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then + UDR_USER="" + UDR_PASS="" + _err "You didn't specify an UD-Reselling username and password yet" + return 1 + fi + # save the username and password to the account conf file. + _saveaccountconf_mutable UDR_USER "$UDR_USER" + _saveaccountconf_mutable UDR_PASS "$UDR_PASS" + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _dnszone "${_dnszone}" + + _debug "Getting txt records" + if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then + return 1 + fi + + rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}" + _debug resource_record "${rr}" + if _contains "$response" "$rr" >/dev/null; then + _err "Error, it would appear that this record already exists. Please review existing TXT records for this domain." + return 1 + fi + + _info "Adding record" + if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&addrr0=${rr}"; then + _err "Adding the record did not succeed, please verify/check." + return 1 + fi + + _info "Added, OK" + return 0 +} + +dns_udr_rm() { + fulldomain=$1 + txtvalue=$2 + + UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}" + UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}" + if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then + UDR_USER="" + UDR_PASS="" + _err "You didn't specify an UD-Reselling username and password yet" + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _dnszone "${_dnszone}" + + _debug "Getting txt records" + if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then + return 1 + fi + + rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}" + _debug resource_record "${rr}" + if _contains "$response" "$rr" >/dev/null; then + if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&delrr0=${rr}"; then + _err "Deleting the record did not succeed, please verify/check." + return 1 + fi + _info "Removed, OK" + return 0 + else + _info "Text record is not present, will not delete anything." + return 0 + fi +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + i=1 + + if ! _udr_rest "QueryDNSZoneList" ""; then + return 1 + fi + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if _contains "${response}" "${h}." >/dev/null; then + _dnszone=$(echo "$response" | _egrep_o "${h}") + if [ "$_dnszone" ]; then + return 0 + fi + return 1 + fi + i=$(_math "$i" + 1) + done + return 1 +} + +_udr_rest() { + if [ -n "$2" ]; then + data="command=$1&$2" + else + data="command=$1" + fi + + _debug data "${data}" + response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")" + + _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + + _debug response_code "$_code" + _debug response_description "$_description" + + if [ ! "$_code" = "200" ]; then + _err "DNS-API-Error: $_description" + return 1 + fi + + return 0 +} diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh index 231c34b3..fd124754 100644 --- a/dnsapi/dns_world4you.sh +++ b/dnsapi/dns_world4you.sh @@ -24,7 +24,7 @@ dns_world4you_add() { fi export _H1="Cookie: W4YSESSID=$sessid" - form=$(_get "$WORLD4YOU_API/dashboard/paketuebersicht") + form=$(_get "$WORLD4YOU_API/") _get_paketnr "$fqdn" "$form" paketnr="$PAKETNR" if [ -z "$paketnr" ]; then @@ -87,7 +87,7 @@ dns_world4you_rm() { fi export _H1="Cookie: W4YSESSID=$sessid" - form=$(_get "$WORLD4YOU_API/dashboard/paketuebersicht") + form=$(_get "$WORLD4YOU_API/") _get_paketnr "$fqdn" "$form" paketnr="$PAKETNR" if [ -z "$paketnr" ]; then @@ -184,7 +184,7 @@ _get_paketnr() { fqdn="$1" form="$2" - domains=$(echo "$form" | grep '^ *[A-Za-z0-9_\.-]*\.[A-Za-z0-9_-]*$' | sed 's/^ *\(.*\)$/\1/') + domains=$(echo "$form" | grep 'header-paket-domain' | sed 's/<[^>]*>//g' | sed 's/^.*>\([^>]*\)$/\1/') domain='' for domain in $domains; do if _contains "$fqdn" "$domain\$"; then diff --git a/notify/weixin_work.sh b/notify/weixin_work.sh new file mode 100644 index 00000000..bf3e9ad6 --- /dev/null +++ b/notify/weixin_work.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env sh + +#Support weixin work webhooks api + +#WEIXIN_WORK_WEBHOOK="xxxx" + +#optional +#WEIXIN_WORK_KEYWORD="yyyy" + +#`WEIXIN_WORK_SIGNING_KEY`="SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx" + +# subject content statusCode +weixin_work_send() { + _subject="$1" + _content="$2" + _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped + _debug "_subject" "$_subject" + _debug "_content" "$_content" + _debug "_statusCode" "$_statusCode" + + WEIXIN_WORK_WEBHOOK="${WEIXIN_WORK_WEBHOOK:-$(_readaccountconf_mutable WEIXIN_WORK_WEBHOOK)}" + if [ -z "$WEIXIN_WORK_WEBHOOK" ]; then + WEIXIN_WORK_WEBHOOK="" + _err "You didn't specify a weixin_work webhooks WEIXIN_WORK_WEBHOOK yet." + _err "You can get yours from https://work.weixin.qq.com/api/doc/90000/90136/91770" + return 1 + fi + _saveaccountconf_mutable WEIXIN_WORK_WEBHOOK "$WEIXIN_WORK_WEBHOOK" + + WEIXIN_WORK_KEYWORD="${WEIXIN_WORK_KEYWORD:-$(_readaccountconf_mutable WEIXIN_WORK_KEYWORD)}" + if [ "$WEIXIN_WORK_KEYWORD" ]; then + _saveaccountconf_mutable WEIXIN_WORK_KEYWORD "$WEIXIN_WORK_KEYWORD" + fi + + _content=$(echo "$_content" | _json_encode) + _subject=$(echo "$_subject" | _json_encode) + _data="{\"msgtype\": \"text\", \"text\": {\"content\": \"[$WEIXIN_WORK_KEYWORD]\n$_subject\n$_content\"}}" + + response="$(_post "$_data" "$WEIXIN_WORK_WEBHOOK" "" "POST" "application/json")" + + if [ "$?" = "0" ] && _contains "$response" "errmsg\":\"ok"; then + _info "weixin_work webhooks event fired success." + return 0 + fi + + _err "weixin_work webhooks event fired error." + _err "$response" + return 1 +}