From ed9e196bf68d9eb4494fbd33826eaa2e2a9366bf Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 00:58:33 +0100 Subject: [PATCH 01/32] Update list of DNS providers for Plesk XML API --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c8bebc6f..32d30147 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,7 @@ You don't have to do anything manually! 1. acme-dns (https://github.com/joohoi/acme-dns) 1. TELE3 (https://www.tele3.cz) 1. EUSERV.EU (https://www.euserv.eu) +1. Plesk XML API (https://www.plesk.com) And: From 1339b9422d0a7ad0f8c57b549ddb50eff7c18d05 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:04:08 +0100 Subject: [PATCH 02/32] Update for dns pleskxml --- dnsapi/README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 1f394f92..568baba9 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -897,6 +897,26 @@ acme.sh --issue --dns dns_euserv -d example.com -d *.example.com --insecure The `EUSERV_Username` and `EUSERV_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. Please report any issues to https://github.com/initit/acme.sh or to + +## 47. Use Plesk XML API to automatically issue cert + +The plesk plugin uses an XML API to add and remove dns records. +The Plesk API URI (URL), and the user name and password for logging in, must be configured. + +``` +export pleskxml_uri="https://YOUR_PLESK_URI_HERE:8443/enterprise/control/agent.php" + (or probably something similar) +export pleskxml_user="plesk username" +export pleskxml_pass="plesk password" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com +``` + +The `pleskxml_user`, `pleskxml_pass` and `pleskxml_uri` will be saved in `~/.acme.sh/account.conf` and are reused when needed. + # Use custom API If your API is not supported yet, you can write your own DNS API. @@ -917,4 +937,4 @@ See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide # Use lexicon DNS API -https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api \ No newline at end of file +https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api From 274393ac64fbccf994fd240498cb863348388da3 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:09:50 +0100 Subject: [PATCH 03/32] Create DNS 01 module for Plesk XML API --- dnsapi/dns_pleskxml | 402 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 dnsapi/dns_pleskxml diff --git a/dnsapi/dns_pleskxml b/dnsapi/dns_pleskxml new file mode 100644 index 00000000..a8a74721 --- /dev/null +++ b/dnsapi/dns_pleskxml @@ -0,0 +1,402 @@ +#!/usr/bin/env sh + +## Name: dns_pleskxml.sh +## Created by Stilez. +## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files) + +## This DNS01 method uses the Plesk XML API described at: +## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709 +## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784 + +## Note: a DNS ID with host = empty string is OK for this API, see +## https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 +## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action. +## So this API module can handle such a request, if needed. + +## The plesk plugin uses the xml api to add and remvoe the dns records. Therefore the url, username +## and password have to be configured by the user before this module is called. +## +## ``` +## export pleskxml_uri="https://YOUR_PLESK_URI_HERE:8443/enterprise/control/agent.php" +## (or probably something similar) +## export pleskxml_user="plesk username" +## export pleskxml_pass="plesk password" +## ``` + +## Ok, let's issue a cert now: +## ``` +## acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com +## ``` +## +## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed. + + +#################### INTERNAL VARIABLES + NEWLINE ################################## + +pleskxml_init_checks_done=0 + +# Variable containing bare newline - not a style issue +# shellcheck disable=SC1004 +NEWLINE='\ +' + + +#################### API Templates ################################## + +pleskxml_tplt_get_domains="" + # Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh + # Also used to test credentials and URI. + # No args. +pleskxml_tplt_get_dns_records="%s" + # Get all DNS records for a Plesk domain ID. + # ARG = Plesk domain id to query +pleskxml_tplt_add_txt_record="%sTXT%s%s" + # Add a TXT record to a domain. + # ARGS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value +pleskxml_tplt_rmv_dns_record="%s" + # Add a TXT record to a domain. + # ARG = the Plesk internal ID for the dns record to be deleted + + +#################### Public functions ################################## + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_pleskxml_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Entering dns_pleskxml_add() to add TXT record '$2' to domain '$1'..." + + # Get credentials if not already checked, and confirm we can log in to Plesk XML API + if ! _credential_check; then + return 1 + fi + + # Get root and subdomain details, and Plesk domain ID + if ! _pleskxml_get_root_domain "$fulldomain"; then + return 1 + fi + + _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record' + + + # printf using template in a variable - not a style issue + # shellcheck disable=SC2059 + request="$( printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue" )" + if ! _call_api "$request"; then + return 1 + fi + + # OK, we should have added a TXT record. Let's check and return success if so. + # All that should be left in the result, is one section, containing okNEW_DNS_RECORD_ID + + results="$( _api_response_split "$pleskxml_prettyprint_result" 'result' '' )" + + if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then + # Error - doesn't contain expected string. Something's wrong. + _err 'Error when calling Plesk XML API.' + _err 'The result did not contain the expected XXXXX section, or contained other values as well.' + _err 'This is unexpected: something has gone wrong.' + _err 'The full response was:\n' "$pleskxml_prettyprint_result" + return 1 + fi + + recid="$( _value "$results" | grep -E '[0-9]+' | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/' )" + + _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." + + return 0 +} + +#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_pleskxml_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Entering dns_pleskxml_rm() to remove TXT record '$2' from domain '$1'..." + + # Get credentials if not already checked, and confirm we can log in to Plesk XML API + if ! _credential_check; then + return 1 + fi + + # Get root and subdomain details, and Plesk domain ID + if ! _pleskxml_get_root_domain "$fulldomain"; then + return 1 + fi + + _debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs' + + # printf using template in a variable - not a style issue + # shellcheck disable=SC2059 + request="$( printf "$pleskxml_tplt_get_dns_records" "$root_domain_id" )" + if ! _call_api "$request"; then + return 1 + fi + + # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) + reclist="$( _api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | \ + grep "${root_domain_id}" | \ + grep -E '[0-9]+' | \ + grep 'TXT' \ + )" + + if [ -z "$reclist" ]; then + _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting." + return 1 + fi + + _debug "Got list of DNS TXT records for root domain '$root_domain_name'"':\n'"$reclist" + + recid="$( _value "$reclist" | \ + grep "$1." | \ + grep "$txtvalue" | \ + sed -E 's/(^.*|<\/id>.*$)//g' \ + )" + + _debug "List of DNS TXT records for host:"'\n'"$( _value "$reclist" | grep "$1." )" + + + if ! _value "$recid" | grep -Eq '^[0-9]+$'; then + _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" + _err "Cannot delete TXT record. Exiting." + return 1 + fi + + _debug "Found Plesk record ID for target text string '${txtvalue}': ID=${recid}" + _debug 'Calling Plesk XML API to remove TXT record' + + # printf using template in a variable - not a style issue + # shellcheck disable=SC2059 + request="$( printf "$pleskxml_tplt_rmv_dns_record" "$recid" )" + if ! _call_api "$request"; then + return 1 + fi + + # OK, we should have removed a TXT record. Let's check and return success if so. + # All that should be left in the result, is one section, containing okPLESK_DELETED_DNS_RECORD_ID + + results="$( _api_response_split "$pleskxml_prettyprint_result" 'result' '' )" + + if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then + # Error - doesn't contain expected string. Something's wrong. + _err 'Error when calling Plesk XML API.' + _err 'The result did not contain the expected XXXXX section, or contained other values as well.' + _err 'This is unexpected: something has gone wrong.' + _err 'The full response was:\n' "$pleskxml_prettyprint_result" + return 1 + fi + + _info "Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm()." + return 0 +} + + + +#################### Private functions below ################################## + +# Outputs value of a variable +_value() { + printf '%s' "$1" +} + + +# Outputs value of a variable (FQDN) and cuts it at 2 delimiters +# $1, $2 = where to cut +# $3 = FQDN +_valuecut() { + printf '%s' "$3" | cut -d . -f "${1}-${2}" +} + + +# Cleans up an API response, splits it "per item" and greps for a string to validate useful lines +# $1 - result string from API +# $2 - tag to resplit on (usually "result" or "domain") +# $3 - regex to recognise useful return lines +_api_response_split() { + printf '%s' "$1" | \ + sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' | \ + tr -d '\n\r' | \ + sed -E "s/<\/?$2>/${NEWLINE}/g" | \ + grep -E "$3" +} + + +# Calls Plesk XML API, and checks results for obvious issues +_call_api() { + request="$1" + errtext='' + + _debug 'Entered _call_api(). Calling Plesk XML API with request:\n' "'${request}'" + + export _H1="HTTP_AUTH_LOGIN: $pleskxml_user" + export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass" + export _H3="content-Type: text/xml" + export _H4="HTTP_PRETTY_PRINT: true" + pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")" + pleskxml_retcode="$?" + _debug "acme _post() returned retcode=$pleskxml_retcode. Literal response:" '\n' "'${pleskxml_prettyprint_result}'" + + # Error handling + + # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. + # Also detect if there simply aren't any status lines (null result?) and report that, as well. + + statuslines="$( echo "$pleskxml_prettyprint_result" | grep -E '^[[:space:]]*[^<]*[[:space:]]*$' )" + + if _value "$statuslines" | grep -qv 'ok'; then + + # We have some status lines that aren't "ok". Get the details + errtext="$( \ + _value "$pleskxml_prettyprint_result" | \ + grep -iE "(||)" | \ + sed -E 's/(^[[:space:]]+|<\/[a-z]+$)//g' | \ + sed -E 's/^<([a-z]+)>/\1: /' \ + )" + + elif ! _value "$statuslines" | grep -q 'ok'; then + + # We have no status lines at all. Results are empty + errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.' + + fi + + if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then + _err "The Plesk XML API call failed." + _err "The return code for the POST request was $pleskxml_retcode (0=success)." + if [ "$errtext" != "" ]; then + _err 'Status and error messages received from the Plesk server:\n' "$errtext" + else + _err "No additional error messages were received back from the Plesk server" + fi + return 1 + fi + + _debug "Leaving _call_api(). Successful call." + + return 0 +} + + +_credential_check() { + # Startup checks (credentials, URI) + + _debug "Checking Plesk XML API login credentials and URI..." + + if [ "$pleskxml_init_checks_done" -eq 1 ]; then + _debug "Initial checks already done, no need to repeat. Skipped." + return 0 + fi + + + pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}" + pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}" + pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}" + + _debug "Credentials - User: '${pleskxml_user}' Passwd: ****** URI: '${pleskxml_uri}'" + + if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then + pleskxml_user="" + pleskxml_pass="" + pleskxml_uri="" + _err "You didn't specify one or more of the Plesk XML API username, password, or URI." + _err "Please create these and try again." + _err "Instructions are in the module source code." + return 1 + fi + + # Test the API is usable, by trying to read the list of managed domains... + _call_api "$pleskxml_tplt_get_domains" + if [ "$pleskxml_retcode" -ne 0 ]; then + _err '\nFailed to access Plesk XML API.' + _err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again." + return 1 + fi + + _saveaccountconf_mutable pleskxml_uri "$pleskxml_uri" + _saveaccountconf_mutable pleskxml_user "$pleskxml_user" + _saveaccountconf_mutable pleskxml_pass "$pleskxml_pass" + + _debug "Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use." + + pleskxml_init_checks_done=1 + + return 0 +} + + +# For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any. +_pleskxml_get_root_domain() { + _debug "Identifying DNS root domain for '$1' that is managed by the Plesk account." + + # test if the domain is valid for splitting. + + if _value "$root_domain_name" | grep -qvE '^[^.]+\.[^.]+\.[^.]'; then + ### COMMENTED OUT ALSO FOR SAME REASON + ### _err "Invalid domain. The ACME domain must contain at least three parts (aa.bb.tld) to identify a host, domain, and tld for the TXT record." + _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." + return 1 + fi + + _debug "Querying Plesk server for list of managed domains..." + + _call_api "$pleskxml_tplt_get_domains" + if [ "$pleskxml_retcode" -ne 0 ]; then + return 1 + fi + + # Generate a hacked list of domains known to this Plesk account. + # We convert tags to so it'll flag on a hit with either or fields, + # for non-Western character sets. + # Output will be one line per known domain, containing 1 or 2 tages and an tag + # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. + output="$( _api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -E 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '' )" + + _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" + + # loop and test if domain, or any parent domain, is managed by Plesk + # Loop until we don't have any '.' in the sring we're testing as a root domain + + root_domain_name="$1" + doneloop=0 + + while _contains "$root_domain_name" '\.'; do + + _debug "Checking if '$root_domain_name' is managed by the Plesk server..." + + root_domain_id="$( _value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/' )" + + if [ -n "$root_domain_id" ]; then + # Found a match + # Note that a result with host = empty string is OK for this API, see + # https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 + # See notes at top of this file + sub_domain_name="$( _value "$1" | sed -E "s/\.?${root_domain_name}"'$//' )" + _info "Matched host '$1' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." + return 0 + fi + + # No match, try next parent up (if any)... + + if _contains "$root_domain_name" '\.[^.]+\.'; then + _debug "No match, trying next parent up..." + else + _debug "No match,and next parent would be a TLD..." + fi + root_domain_name="$( _valuecut 2 1000 "$root_domain_name" )" + doneloop=1 + + done + + # if we get here, we failed to find a root domain match in the list of domains managed by Plesk. + # if we never ran the loop a first time, $1 wasn't at least a 2 level domain (domain.tld) and wasn't valid anyway + + if [ -z $doneloop ]; then + _err "'$1' isn't a valid domain for ACME DNS. Exiting." + else + _err "Cannot find '$1' or any parent domain of it, in Plesk." + _err "Are you sure that this domain is managed by this Plesk server?" + fi + + return 1 +} From a00300f88a7e9d85883616e55819f51052e64b7e Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:23:14 +0100 Subject: [PATCH 04/32] revert changes to this file --- README.md | 646 ++++++++++++++++-------------------------------------- 1 file changed, 189 insertions(+), 457 deletions(-) diff --git a/README.md b/README.md index 32d30147..613fff7f 100644 --- a/README.md +++ b/README.md @@ -1,503 +1,235 @@ + +Skip to content +Pull requests +Issues +Marketplace +Explore +@stilez +Learn Git and GitHub without any code! + +Using the Hello World guide, you’ll start a branch, write comments, and open a pull request. + +395 +14.7k + + 1.9k + +Neilpang/acme.sh +Code +Issues 415 +Pull requests 110 +Actions +Projects 0 +Wiki +Security +Insights +You’re editing a file in a project you don’t have write access to. Submitting a change to this file will write it to a new branch in your fork stilez/acme.sh, so you can send a pull request. +acme.sh/ + +1 + # An ACME Shell script: acme.sh [![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh) -[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +2 + +​ + +3 + + [![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +4 + - An ACME protocol client written purely in Shell (Unix shell) language. + +5 + - Full ACME protocol implementation. + +6 + - Support ACME v1 and ACME v2 + +7 + - Support ACME v2 wildcard certs + +8 + - Simple, powerful and very easy to use. You only need 3 minutes to learn it. + +9 + - Bash, dash and sh compatible. + +10 + - Simplest shell script for Let's Encrypt free certificate client. + +11 + - Purely written in Shell with no dependencies on python or the official Let's Encrypt client. + +12 + - Just one script to issue, renew and install your certificates automatically. + +13 + - DOES NOT require `root/sudoer` access. + +14 + - Docker friendly + +15 + - IPv6 support +16 + +- Cron job notifications for renewal or error etc. + +17 + +​ + +18 + It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. +19 + +​ + +20 + Wiki: https://github.com/Neilpang/acme.sh/wiki +21 + +​ + +22 + For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/Neilpang/acme.sh/wiki/Run-acme.sh-in-docker) +23 + +​ + +24 + Twitter: [@neilpangxa](https://twitter.com/neilpangxa) +25 + +​ + +26 + +​ + +27 # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) +28 + +​ + +29 + # Who: + +30 + - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) + +31 + - [ruby-china.org](https://ruby-china.org/topics/31983) + +32 + - [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) + +33 + - [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) + +34 + - [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) + +35 + - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) + +36 + - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) + +37 + - [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html) + +38 + - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) -- [archlinux](https://aur.archlinux.org/packages/acme.sh-git/) + +39 + +- [archlinux](https://www.archlinux.org/packages/community/any/acme.sh) + +40 + - [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) + +41 + - [CentOS Web Panel](http://centos-webpanel.com/) + +42 + - [lnmp.org](https://lnmp.org/) + +43 + - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials) +44 + +​ + +45 + # Tested OS +46 + +​ + +47 + | NO | Status| Platform| -|----|-------|---------| -|1|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/ubuntu-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu -|2|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/debian-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Debian -|3|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/centos-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|CentOS -|4|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/windows-cygwin.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included) -|5|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/freebsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|FreeBSD -|6|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/pfsense.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|pfsense -|7|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/opensuse-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|openSUSE -|8|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/alpine-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Alpine Linux (with curl) -|9|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/base-archlinux.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Archlinux -|10|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/fedora-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|fedora -|11|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/kalilinux-kali-linux-docker.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Kali Linux -|12|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/oraclelinux-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Oracle Linux -|13|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/proxmox.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Proxmox https://pve.proxmox.com/wiki/HTTPSCertificateConfiguration#Let.27s_Encrypt_using_acme.sh -|14|-----| Cloud Linux https://github.com/Neilpang/le/issues/111 -|15|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/openbsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|OpenBSD -|16|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/mageia.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Mageia -|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT) -|18|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/solaris.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris -|19|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/gentoo-stage3-amd64.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux -|20|[![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX -For all build statuses, check our [weekly build project](https://github.com/Neilpang/acmetest): +@stilez +Propose file change +Commit summary +Optional extended description + + © 2019 GitHub, Inc. + Terms + Privacy + Security + Status + Help + + Contact GitHub + Pricing + API + Training + Blog + About -https://github.com/Neilpang/acmetest - - -# Supported modes - -- Webroot mode -- Standalone mode -- Apache mode -- Nginx mode -- DNS mode -- [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode) -- [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode) - - -# 1. How to install - -### 1. Install online - -Check this project: https://github.com/Neilpang/get.acme.sh - -```bash -curl https://get.acme.sh | sh -``` - -Or: - -```bash -wget -O - https://get.acme.sh | sh -``` - - -### 2. Or, Install from git - -Clone this project and launch installation: - -```bash -git clone https://github.com/Neilpang/acme.sh.git -cd ./acme.sh -./acme.sh --install -``` - -You `don't have to be root` then, although `it is recommended`. - -Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install - -The installer will perform 3 actions: - -1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. -All certs will be placed in this folder too. -2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. -3. Create daily cron job to check and renew the certs if needed. - -Cron entry example: - -```bash -0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null -``` - -After the installation, you must close the current terminal and reopen it to make the alias take effect. - -Ok, you are ready to issue certs now. - -Show help message: - -```sh -root@v1:~# acme.sh -h -``` - -# 2. Just issue a cert - -**Example 1:** Single domain. - -```bash -acme.sh --issue -d example.com -w /home/wwwroot/example.com -``` - -or: - -```bash -acme.sh --issue -d example.com -w /home/username/public_html -``` - -or: - -```bash -acme.sh --issue -d example.com -w /var/www/html -``` - -**Example 2:** Multiple domains in the same cert. - -```bash -acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com -``` - -The parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder. - -Second argument **"example.com"** is the main domain you want to issue the cert for. -You must have at least one domain there. - -You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. - -The certs will be placed in `~/.acme.sh/example.com/` - -The certs will be renewed automatically every **60** days. - -More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert - - -# 3. Install the cert to Apache/Nginx etc. - -After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers. -You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future. - -**Apache** example: -```bash -acme.sh --install-cert -d example.com \ ---cert-file /path/to/certfile/in/apache/cert.pem \ ---key-file /path/to/keyfile/in/apache/key.pem \ ---fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \ ---reloadcmd "service apache2 force-reload" -``` - -**Nginx** example: -```bash -acme.sh --install-cert -d example.com \ ---key-file /path/to/keyfile/in/nginx/key.pem \ ---fullchain-file /path/to/fullchain/nginx/cert.pem \ ---reloadcmd "service nginx force-reload" -``` - -Only the domain is required, all the other parameters are optional. - -The ownership and permission info of existing files are preserved. You can pre-create the files to define the ownership and permission. - -Install/copy the cert/key to the production Apache or Nginx path. - -The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. - - -**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.** - -# 4. Use Standalone server to issue cert - -**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** - -Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. - -```bash -acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com -``` - -More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert - - -# 5. Use Apache mode - -**(requires you to be root/sudoer, since it is required to interact with Apache server)** - -If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. - -Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder. - -Just set string "apache" as the second argument and it will force use of apache plugin automatically. - -```sh -acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com -``` - -**This apache mode is only to issue the cert, it will not change your apache config files. -You will need to configure your website config files to use the cert by yourself. -We don't want to mess your apache server, don't worry.** - -More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert - -# 6. Use Nginx mode - -**(requires you to be root/sudoer, since it is required to interact with Nginx server)** - -If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. - -Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder. - -Just set string "nginx" as the second argument. - -It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version. - -So, the config is not changed. - -```sh -acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com -``` - -**This nginx mode is only to issue the cert, it will not change your nginx config files. -You will need to configure your website config files to use the cert by yourself. -We don't want to mess your nginx server, don't worry.** - -More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert - -# 7. Automatic DNS API integration - -If your DNS provider supports API access, we can use that API to automatically issue the certs. - -You don't have to do anything manually! - -### Currently acme.sh supports: - -1. CloudFlare.com API -1. DNSPod.cn API -1. CloudXNS.com API -1. GoDaddy.com API -1. PowerDNS.com API -1. OVH, kimsufi, soyoustart and runabove API -1. nsupdate API -1. LuaDNS.com API -1. DNSMadeEasy.com API -1. AWS Route 53 -1. aliyun.com(阿里云) API -1. ISPConfig 3.1 API -1. Alwaysdata.com API -1. Linode.com API -1. FreeDNS (https://freedns.afraid.org/) -1. cyon.ch -1. Domain-Offensive/Resellerinterface/Domainrobot API -1. Gandi LiveDNS API -1. Knot DNS API -1. DigitalOcean API (native) -1. ClouDNS.net API -1. Infoblox NIOS API (https://www.infoblox.com/) -1. VSCALE (https://vscale.io/) -1. Dynu API (https://www.dynu.com) -1. DNSimple API -1. NS1.com API -1. DuckDNS.org API -1. Name.com API -1. Dyn Managed DNS API -1. Yandex PDD API (https://pdd.yandex.ru) -1. Hurricane Electric DNS service (https://dns.he.net) -1. UnoEuro API (https://www.unoeuro.com/) -1. INWX (https://www.inwx.de/) -1. Servercow (https://servercow.de) -1. Namesilo (https://www.namesilo.com) -1. InternetX autoDNS API (https://internetx.com) -1. Azure DNS -1. selectel.com(selectel.ru) DNS API -1. zonomi.com DNS API -1. DreamHost.com API -1. DirectAdmin API -1. KingHost (https://www.kinghost.com.br/) -1. Zilore (https://zilore.com) -1. Loopia.se API -1. acme-dns (https://github.com/joohoi/acme-dns) -1. TELE3 (https://www.tele3.cz) -1. EUSERV.EU (https://www.euserv.eu) -1. Plesk XML API (https://www.plesk.com) - -And: - -**lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api - (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.)** - - -**More APIs coming soon...** - -If your DNS provider is not on the supported list above, you can write your own DNS API script easily. If you do, please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute it to the project. - -For more details: [How to use DNS API](dnsapi) - -# 8. Use DNS manual mode: - -See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first. - -If your dns provider doesn't support any api access, you can add the txt record by your hand. - -```bash -acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com -``` - -You should get an output like below: - -```sh -Add the following txt record: -Domain:_acme-challenge.example.com -Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c - -Add the following txt record: -Domain:_acme-challenge.www.example.com -Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - -Please add those txt records to the domains. Waiting for the dns to take effect. -``` - -Then just rerun with `renew` argument: - -```bash -acme.sh --renew -d example.com -``` - -Ok, it's done. - -**Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.** - -**Please use dns api mode instead.** - -# 9. Issue ECC certificates - -`Let's Encrypt` can now issue **ECDSA** certificates. - -And we support them too! - -Just set the `keylength` parameter with a prefix `ec-`. - -For example: - -### Single domain ECC certificate - -```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 -``` - -### SAN multi domain ECC certificate - -```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 -``` - -Please look at the `keylength` parameter above. - -Valid values are: - -1. **ec-256 (prime256v1, "ECDSA P-256")** -2. **ec-384 (secp384r1, "ECDSA P-384")** -3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** - - - -# 10. Issue Wildcard certificates - -It's simple, just give a wildcard domain as the `-d` parameter. - -```sh -acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf -``` - - - -# 11. How to renew the certs - -No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. - -However, you can also force to renew a cert: - -```sh -acme.sh --renew -d example.com --force -``` - -or, for ECC cert: - -```sh -acme.sh --renew -d example.com --force --ecc -``` - - -# 12. How to stop cert renewal - -To stop renewal of a cert, you can execute the following to remove the cert from the renewal list: - -```sh -acme.sh --remove -d example.com [--ecc] -``` - -The cert/key file is not removed from the disk. - -You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself. - - -# 13. How to upgrade `acme.sh` - -acme.sh is in constant development, so it's strongly recommended to use the latest code. - -You can update acme.sh to the latest code: - -```sh -acme.sh --upgrade -``` - -You can also enable auto upgrade: - -```sh -acme.sh --upgrade --auto-upgrade -``` - -Then **acme.sh** will be kept up to date automatically. - -Disable auto upgrade: - -```sh -acme.sh --upgrade --auto-upgrade 0 -``` - - -# 14. Issue a cert from an existing CSR - -https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR - - -# 15. Under the Hood - -Speak ACME language using shell, directly to "Let's Encrypt". - -TODO: - - -# 16. Acknowledgments - -1. Acme-tiny: https://github.com/diafygi/acme-tiny -2. ACME protocol: https://github.com/ietf-wg-acme/acme - - -# 17. License & Others - -License is GPLv3 - -Please Star and Fork me. - -[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome. - - -# 18. Donate -Your donation makes **acme.sh** better: - -1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) - -[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list) From 4c9d99040c822b35eefbcee46b88bdb766af32a5 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:31:17 +0100 Subject: [PATCH 05/32] Fix (revert) edited .md file --- README.md | 609 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 461 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index 6c692885..d5012d68 100644 --- a/README.md +++ b/README.md @@ -1,185 +1,498 @@ - -Skip to content -Pull requests -Issues -Marketplace -Explore -@stilez -Learn Git and GitHub without any code! - -Using the Hello World guide, you’ll start a branch, write comments, and open a pull request. - -395 -14.7k - - 1.9k - -Neilpang/acme.sh -Code -Issues 415 -Pull requests 110 -Actions -Projects 0 -Wiki -Security -Insights -You’re editing a file in a project you don’t have write access to. Submitting a change to this file will write it to a new branch in your fork stilez/acme.sh, so you can send a pull request. -acme.sh/ - -1 - - -2 - - -3 +# An ACME Shell script: acme.sh [![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh) [![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -4 - - An ACME protocol client written purely in Shell (Unix shell) language. - -5 - - Full ACME protocol implementation. - -6 - - -7 - - -8 - +- Support ACME v1 and ACME v2 +- Support ACME v2 wildcard certs - Simple, powerful and very easy to use. You only need 3 minutes to learn it. - +- Bash, dash and sh compatible. +- Simplest shell script for Let's Encrypt free certificate client. +- Purely written in Shell with no dependencies on python or the official Let's Encrypt client. +- Just one script to issue, renew and install your certificates automatically. - DOES NOT require `root/sudoer` access. - -14 - - Docker friendly - -15 - - IPv6 support - -16 - - Cron job notifications for renewal or error etc. -17 - -​ - -18 - It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. -19 - - -20 - Wiki: https://github.com/Neilpang/acme.sh/wiki -21 - -​ - -22 - For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/Neilpang/acme.sh/wiki/Run-acme.sh-in-docker) -23 - -​ - -24 - Twitter: [@neilpangxa](https://twitter.com/neilpangxa) -25 - -​ - -26 - - -27 # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) -28 - -​ - - # Who: - -30 - - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) - -31 - - [ruby-china.org](https://ruby-china.org/topics/31983) - -32 - - -33 - +- [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) - [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) - -34 - - [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) - -35 - - -36 - +- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) - -37 - - [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html) - -38 - - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) - -39 - - [archlinux](https://www.archlinux.org/packages/community/any/acme.sh) - -40 - - -41 - +- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) - [CentOS Web Panel](http://centos-webpanel.com/) - - - [lnmp.org](https://lnmp.org/) - -43 - - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials) - -​ - -45 - - -46 - -​ - -47 +# Tested OS | NO | Status| Platform| +|----|-------|---------| +|1|[![](https://neilpang.github.io/acmetest/status/ubuntu-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu +|2|[![](https://neilpang.github.io/acmetest/status/debian-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Debian +|3|[![](https://neilpang.github.io/acmetest/status/centos-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|CentOS +|4|[![](https://neilpang.github.io/acmetest/status/windows-cygwin.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included) +|5|[![](https://neilpang.github.io/acmetest/status/freebsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|FreeBSD +|6|[![](https://neilpang.github.io/acmetest/status/pfsense.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|pfsense +|7|[![](https://neilpang.github.io/acmetest/status/opensuse-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|openSUSE +|8|[![](https://neilpang.github.io/acmetest/status/alpine-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Alpine Linux (with curl) +|9|[![](https://neilpang.github.io/acmetest/status/base-archlinux.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Archlinux +|10|[![](https://neilpang.github.io/acmetest/status/fedora-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|fedora +|11|[![](https://neilpang.github.io/acmetest/status/kalilinux-kali-linux-docker.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Kali Linux +|12|[![](https://neilpang.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Oracle Linux +|13|[![](https://neilpang.github.io/acmetest/status/proxmox.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Proxmox https://pve.proxmox.com/wiki/HTTPSCertificateConfiguration#Let.27s_Encrypt_using_acme.sh +|14|-----| Cloud Linux https://github.com/Neilpang/le/issues/111 +|15|[![](https://neilpang.github.io/acmetest/status/openbsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|OpenBSD +|16|[![](https://neilpang.github.io/acmetest/status/mageia.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Mageia +|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT) +|18|[![](https://neilpang.github.io/acmetest/status/solaris.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris +|19|[![](https://neilpang.github.io/acmetest/status/gentoo-stage3-amd64.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux +|20|[![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX +For all build statuses, check our [weekly build project](https://github.com/Neilpang/acmetest): + +https://github.com/Neilpang/acmetest + +# Supported CA + +- Letsencrypt.org CA(default) +- [BuyPass.com CA](https://github.com/Neilpang/acme.sh/wiki/BuyPass.com-CA) +- [Pebble strict Mode](https://github.com/letsencrypt/pebble) + +# Supported modes + +- Webroot mode +- Standalone mode +- Standalone tls-alpn mode +- Apache mode +- Nginx mode +- DNS mode +- [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode) +- [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode) + + +# 1. How to install + +### 1. Install online + +Check this project: https://github.com/Neilpang/get.acme.sh + +```bash +curl https://get.acme.sh | sh +``` + +Or: + +```bash +wget -O - https://get.acme.sh | sh +``` + + +### 2. Or, Install from git + +Clone this project and launch installation: + +```bash +git clone https://github.com/Neilpang/acme.sh.git +cd ./acme.sh +./acme.sh --install +``` + +You `don't have to be root` then, although `it is recommended`. + +Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install + +The installer will perform 3 actions: + +1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. +All certs will be placed in this folder too. +2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. +3. Create daily cron job to check and renew the certs if needed. + +Cron entry example: + +```bash +0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null +``` + +After the installation, you must close the current terminal and reopen it to make the alias take effect. + +Ok, you are ready to issue certs now. + +Show help message: + +```sh +root@v1:~# acme.sh -h +``` + +# 2. Just issue a cert + +**Example 1:** Single domain. + +```bash +acme.sh --issue -d example.com -w /home/wwwroot/example.com +``` + +or: + +```bash +acme.sh --issue -d example.com -w /home/username/public_html +``` + +or: + +```bash +acme.sh --issue -d example.com -w /var/www/html +``` + +**Example 2:** Multiple domains in the same cert. + +```bash +acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com +``` + +The parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder. + +Second argument **"example.com"** is the main domain you want to issue the cert for. +You must have at least one domain there. + +You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. + +The certs will be placed in `~/.acme.sh/example.com/` + +The certs will be renewed automatically every **60** days. + +More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert + + +# 3. Install the cert to Apache/Nginx etc. + +After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers. +You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future. + +**Apache** example: +```bash +acme.sh --install-cert -d example.com \ +--cert-file /path/to/certfile/in/apache/cert.pem \ +--key-file /path/to/keyfile/in/apache/key.pem \ +--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \ +--reloadcmd "service apache2 force-reload" +``` + +**Nginx** example: +```bash +acme.sh --install-cert -d example.com \ +--key-file /path/to/keyfile/in/nginx/key.pem \ +--fullchain-file /path/to/fullchain/nginx/cert.pem \ +--reloadcmd "service nginx force-reload" +``` + +Only the domain is required, all the other parameters are optional. + +The ownership and permission info of existing files are preserved. You can pre-create the files to define the ownership and permission. + +Install/copy the cert/key to the production Apache or Nginx path. + +The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. + + +**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.** + +# 4. Use Standalone server to issue cert + +**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** + +Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. + +```bash +acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com +``` + +More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert + +# 5. Use Standalone ssl server to issue cert + +**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** + +Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. + +```bash +acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com +``` + +More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert + + +# 6. Use Apache mode + +**(requires you to be root/sudoer, since it is required to interact with Apache server)** + +If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. + +Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder. + +Just set string "apache" as the second argument and it will force use of apache plugin automatically. + +```sh +acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com +``` + +**This apache mode is only to issue the cert, it will not change your apache config files. +You will need to configure your website config files to use the cert by yourself. +We don't want to mess your apache server, don't worry.** + +More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert + +# 7. Use Nginx mode + +**(requires you to be root/sudoer, since it is required to interact with Nginx server)** + +If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. + +Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder. + +Just set string "nginx" as the second argument. + +It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version. + +So, the config is not changed. + +```sh +acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com +``` + +**This nginx mode is only to issue the cert, it will not change your nginx config files. +You will need to configure your website config files to use the cert by yourself. +We don't want to mess your nginx server, don't worry.** + +More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert + +# 8. Automatic DNS API integration + +If your DNS provider supports API access, we can use that API to automatically issue the certs. + +You don't have to do anything manually! + +### Currently acme.sh supports most of the dns providers: + +https://github.com/Neilpang/acme.sh/wiki/dnsapi + +# 9. Use DNS manual mode: + +See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first. + +If your dns provider doesn't support any api access, you can add the txt record by your hand. + +```bash +acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com +``` + +You should get an output like below: + +```sh +Add the following txt record: +Domain:_acme-challenge.example.com +Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c + +Add the following txt record: +Domain:_acme-challenge.www.example.com +Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +Please add those txt records to the domains. Waiting for the dns to take effect. +``` + +Then just rerun with `renew` argument: + +```bash +acme.sh --renew -d example.com +``` + +Ok, it's done. + +**Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.** + +**Please use dns api mode instead.** + +# 10. Issue ECC certificates + +`Let's Encrypt` can now issue **ECDSA** certificates. + +And we support them too! + +Just set the `keylength` parameter with a prefix `ec-`. + +For example: + +### Single domain ECC certificate + +```bash +acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 +``` + +### SAN multi domain ECC certificate + +```bash +acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 +``` + +Please look at the `keylength` parameter above. + +Valid values are: + +1. **ec-256 (prime256v1, "ECDSA P-256")** +2. **ec-384 (secp384r1, "ECDSA P-384")** +3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** + + + +# 11. Issue Wildcard certificates + +It's simple, just give a wildcard domain as the `-d` parameter. + +```sh +acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf +``` + + + +# 12. How to renew the certs + +No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. + +However, you can also force to renew a cert: + +```sh +acme.sh --renew -d example.com --force +``` + +or, for ECC cert: + +```sh +acme.sh --renew -d example.com --force --ecc +``` + + +# 13. How to stop cert renewal + +To stop renewal of a cert, you can execute the following to remove the cert from the renewal list: + +```sh +acme.sh --remove -d example.com [--ecc] +``` + +The cert/key file is not removed from the disk. + +You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself. + + +# 14. How to upgrade `acme.sh` + +acme.sh is in constant development, so it's strongly recommended to use the latest code. + +You can update acme.sh to the latest code: + +```sh +acme.sh --upgrade +``` + +You can also enable auto upgrade: + +```sh +acme.sh --upgrade --auto-upgrade +``` + +Then **acme.sh** will be kept up to date automatically. + +Disable auto upgrade: + +```sh +acme.sh --upgrade --auto-upgrade 0 +``` + + +# 15. Issue a cert from an existing CSR + +https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR + + +# 16. Send notifications in cronjob + +https://github.com/Neilpang/acme.sh/wiki/notify + + +# 17. Under the Hood + +Speak ACME language using shell, directly to "Let's Encrypt". + +TODO: + + +# 18. Acknowledgments + +1. Acme-tiny: https://github.com/diafygi/acme-tiny +2. ACME protocol: https://github.com/ietf-wg-acme/acme + + +## Contributors + +### Code Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)] + + + + + + + + + + + + +# 19. License & Others + +License is GPLv3 + +Please Star and Fork me. + +[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome. + + +# 20. Donate +Your donation makes **acme.sh** better: + +1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) + +[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list) From a6614abd24600618a23ee390470eea7af912b9f4 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:00:59 +0000 Subject: [PATCH 06/32] Formatting fixes for Travis --- dnsapi/dns_pleskxml | 102 +++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/dnsapi/dns_pleskxml b/dnsapi/dns_pleskxml index a8a74721..bed1b26f 100644 --- a/dnsapi/dns_pleskxml +++ b/dnsapi/dns_pleskxml @@ -44,18 +44,21 @@ NEWLINE='\ #################### API Templates ################################## pleskxml_tplt_get_domains="" - # Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh - # Also used to test credentials and URI. - # No args. +# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh +# Also used to test credentials and URI. +# No args. + pleskxml_tplt_get_dns_records="%s" - # Get all DNS records for a Plesk domain ID. - # ARG = Plesk domain id to query +# Get all DNS records for a Plesk domain ID. +# ARG = Plesk domain id to query + pleskxml_tplt_add_txt_record="%sTXT%s%s" - # Add a TXT record to a domain. - # ARGS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value +# Add a TXT record to a domain. +# ARGS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value + pleskxml_tplt_rmv_dns_record="%s" - # Add a TXT record to a domain. - # ARG = the Plesk internal ID for the dns record to be deleted +# Add a TXT record to a domain. +# ARG = the Plesk internal ID for the dns record to be deleted #################### Public functions ################################## @@ -82,7 +85,7 @@ dns_pleskxml_add() { # printf using template in a variable - not a style issue # shellcheck disable=SC2059 - request="$( printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue" )" + request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")" if ! _call_api "$request"; then return 1 fi @@ -90,7 +93,7 @@ dns_pleskxml_add() { # OK, we should have added a TXT record. Let's check and return success if so. # All that should be left in the result, is one section, containing okNEW_DNS_RECORD_ID - results="$( _api_response_split "$pleskxml_prettyprint_result" 'result' '' )" + results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then # Error - doesn't contain expected string. Something's wrong. @@ -101,7 +104,7 @@ dns_pleskxml_add() { return 1 fi - recid="$( _value "$results" | grep -E '[0-9]+' | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/' )" + recid="$(_value "$results" | grep -E '[0-9]+' | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." @@ -129,16 +132,16 @@ dns_pleskxml_rm() { # printf using template in a variable - not a style issue # shellcheck disable=SC2059 - request="$( printf "$pleskxml_tplt_get_dns_records" "$root_domain_id" )" + request="$(printf "$pleskxml_tplt_get_dns_records" "$root_domain_id")" if ! _call_api "$request"; then return 1 fi # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) - reclist="$( _api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | \ - grep "${root_domain_id}" | \ - grep -E '[0-9]+' | \ - grep 'TXT' \ + reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ + | grep "${root_domain_id}" \ + | grep -E '[0-9]+' \ + | grep 'TXT' \ )" if [ -z "$reclist" ]; then @@ -148,14 +151,13 @@ dns_pleskxml_rm() { _debug "Got list of DNS TXT records for root domain '$root_domain_name'"':\n'"$reclist" - recid="$( _value "$reclist" | \ - grep "$1." | \ - grep "$txtvalue" | \ - sed -E 's/(^.*|<\/id>.*$)//g' \ - )" - - _debug "List of DNS TXT records for host:"'\n'"$( _value "$reclist" | grep "$1." )" + recid="$(_value "$reclist" \ + | grep "$1." \ + | grep "$txtvalue" \ + | sed -E 's/(^.*|<\/id>.*$)//g' \ + )" + _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" if ! _value "$recid" | grep -Eq '^[0-9]+$'; then _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" @@ -168,7 +170,7 @@ dns_pleskxml_rm() { # printf using template in a variable - not a style issue # shellcheck disable=SC2059 - request="$( printf "$pleskxml_tplt_rmv_dns_record" "$recid" )" + request="$(printf "$pleskxml_tplt_rmv_dns_record" "$recid")" if ! _call_api "$request"; then return 1 fi @@ -176,7 +178,7 @@ dns_pleskxml_rm() { # OK, we should have removed a TXT record. Let's check and return success if so. # All that should be left in the result, is one section, containing okPLESK_DELETED_DNS_RECORD_ID - results="$( _api_response_split "$pleskxml_prettyprint_result" 'result' '' )" + results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then # Error - doesn't contain expected string. Something's wrong. @@ -214,11 +216,11 @@ _valuecut() { # $2 - tag to resplit on (usually "result" or "domain") # $3 - regex to recognise useful return lines _api_response_split() { - printf '%s' "$1" | \ - sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' | \ - tr -d '\n\r' | \ - sed -E "s/<\/?$2>/${NEWLINE}/g" | \ - grep -E "$3" + printf '%s' "$1" \ + | sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' \ + | tr -d '\n\r' \ + | sed -E "s/<\/?$2>/${NEWLINE}/g" \ + | grep -E "$3" } @@ -242,17 +244,16 @@ _call_api() { # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. - statuslines="$( echo "$pleskxml_prettyprint_result" | grep -E '^[[:space:]]*[^<]*[[:space:]]*$' )" + statuslines="$(echo "$pleskxml_prettyprint_result" | grep -E '^[[:space:]]*[^<]*[[:space:]]*$')" if _value "$statuslines" | grep -qv 'ok'; then # We have some status lines that aren't "ok". Get the details - errtext="$( \ - _value "$pleskxml_prettyprint_result" | \ - grep -iE "(||)" | \ - sed -E 's/(^[[:space:]]+|<\/[a-z]+$)//g' | \ - sed -E 's/^<([a-z]+)>/\1: /' \ - )" + errtext="$( _value "$pleskxml_prettyprint_result" \ + | grep -iE "(||)" \ + | sed -E 's/(^[[:space:]]+|<\/[a-z]+$)//g' \ + | sed -E 's/^<([a-z]+)>/\1: /' \ + )" elif ! _value "$statuslines" | grep -q 'ok'; then @@ -326,14 +327,17 @@ _credential_check() { # For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any. + +# IMPORTANT NOTE: a result with host = empty string is OK for this API, see +# https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 +# See notes at top of this file + _pleskxml_get_root_domain() { _debug "Identifying DNS root domain for '$1' that is managed by the Plesk account." # test if the domain is valid for splitting. - if _value "$root_domain_name" | grep -qvE '^[^.]+\.[^.]+\.[^.]'; then - ### COMMENTED OUT ALSO FOR SAME REASON - ### _err "Invalid domain. The ACME domain must contain at least three parts (aa.bb.tld) to identify a host, domain, and tld for the TXT record." + if _value "$root_domain_name" | grep -qvE '^[^.]+\.[^.]+\.[^.]'; then _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." return 1 fi @@ -350,7 +354,8 @@ _pleskxml_get_root_domain() { # for non-Western character sets. # Output will be one line per known domain, containing 1 or 2 tages and an tag # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. - output="$( _api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -E 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '' )" + + output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -E 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '')" _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" @@ -364,14 +369,13 @@ _pleskxml_get_root_domain() { _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - root_domain_id="$( _value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/' )" + root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match - # Note that a result with host = empty string is OK for this API, see - # https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 - # See notes at top of this file - sub_domain_name="$( _value "$1" | sed -E "s/\.?${root_domain_name}"'$//' )" + # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT. + # SO WE HANDLE IT AND DON'T PREVENT IT + sub_domain_name="$(_value "$1" | sed -E "s/\.?${root_domain_name}"'$//')" _info "Matched host '$1' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." return 0 fi @@ -379,11 +383,11 @@ _pleskxml_get_root_domain() { # No match, try next parent up (if any)... if _contains "$root_domain_name" '\.[^.]+\.'; then - _debug "No match, trying next parent up..." + _debug "No match, trying next parent up..." else _debug "No match,and next parent would be a TLD..." fi - root_domain_name="$( _valuecut 2 1000 "$root_domain_name" )" + root_domain_name="$(_valuecut 2 1000 "$root_domain_name")" doneloop=1 done From 9299a83b175c77feec281a8f8754283e8c6333a0 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:10:03 +0000 Subject: [PATCH 07/32] Travis fixes --- dnsapi/dns_pleskxml | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/dnsapi/dns_pleskxml b/dnsapi/dns_pleskxml index bed1b26f..794127c7 100644 --- a/dnsapi/dns_pleskxml +++ b/dnsapi/dns_pleskxml @@ -30,8 +30,7 @@ ## ## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed. - -#################### INTERNAL VARIABLES + NEWLINE ################################## +#################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ################################## pleskxml_init_checks_done=0 @@ -40,9 +39,6 @@ pleskxml_init_checks_done=0 NEWLINE='\ ' - -#################### API Templates ################################## - pleskxml_tplt_get_domains="" # Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh # Also used to test credentials and URI. @@ -60,7 +56,6 @@ pleskxml_tplt_rmv_dns_record="%s # Add a TXT record to a domain. # ARG = the Plesk internal ID for the dns record to be deleted - #################### Public functions ################################## #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -141,7 +136,7 @@ dns_pleskxml_rm() { reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ | grep "${root_domain_id}" \ | grep -E '[0-9]+' \ - | grep 'TXT' \ + | grep 'TXT' )" if [ -z "$reclist" ]; then @@ -154,8 +149,8 @@ dns_pleskxml_rm() { recid="$(_value "$reclist" \ | grep "$1." \ | grep "$txtvalue" \ - | sed -E 's/(^.*|<\/id>.*$)//g' \ - )" + | sed -E 's/(^.*|<\/id>.*$)//g' + )" _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" @@ -193,8 +188,6 @@ dns_pleskxml_rm() { return 0 } - - #################### Private functions below ################################## # Outputs value of a variable @@ -202,7 +195,6 @@ _value() { printf '%s' "$1" } - # Outputs value of a variable (FQDN) and cuts it at 2 delimiters # $1, $2 = where to cut # $3 = FQDN @@ -210,20 +202,18 @@ _valuecut() { printf '%s' "$3" | cut -d . -f "${1}-${2}" } - # Cleans up an API response, splits it "per item" and greps for a string to validate useful lines # $1 - result string from API # $2 - tag to resplit on (usually "result" or "domain") # $3 - regex to recognise useful return lines _api_response_split() { printf '%s' "$1" \ - | sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' \ - | tr -d '\n\r' \ - | sed -E "s/<\/?$2>/${NEWLINE}/g" \ - | grep -E "$3" + | sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' \ + | tr -d '\n\r' \ + | sed -E "s/<\/?$2>/${NEWLINE}/g" \ + | grep -E "$3" } - # Calls Plesk XML API, and checks results for obvious issues _call_api() { request="$1" @@ -249,11 +239,11 @@ _call_api() { if _value "$statuslines" | grep -qv 'ok'; then # We have some status lines that aren't "ok". Get the details - errtext="$( _value "$pleskxml_prettyprint_result" \ + errtext="$(_value "$pleskxml_prettyprint_result" \ | grep -iE "(||)" \ | sed -E 's/(^[[:space:]]+|<\/[a-z]+$)//g' \ - | sed -E 's/^<([a-z]+)>/\1: /' \ - )" + | sed -E 's/^<([a-z]+)>/\1: /' + )" elif ! _value "$statuslines" | grep -q 'ok'; then @@ -278,10 +268,8 @@ _call_api() { return 0 } - +# Startup checks (credentials, URI) _credential_check() { - # Startup checks (credentials, URI) - _debug "Checking Plesk XML API login credentials and URI..." if [ "$pleskxml_init_checks_done" -eq 1 ]; then @@ -289,7 +277,6 @@ _credential_check() { return 0 fi - pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}" pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}" pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}" @@ -325,7 +312,6 @@ _credential_check() { return 0 } - # For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any. # IMPORTANT NOTE: a result with host = empty string is OK for this API, see @@ -369,7 +355,7 @@ _pleskxml_get_root_domain() { _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" + root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match From 6df31eb7f58f32428de4a57971b882421d19b3f5 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:13:15 +0000 Subject: [PATCH 08/32] travis --- dnsapi/dns_pleskxml | 1 - 1 file changed, 1 deletion(-) diff --git a/dnsapi/dns_pleskxml b/dnsapi/dns_pleskxml index 794127c7..83017c3b 100644 --- a/dnsapi/dns_pleskxml +++ b/dnsapi/dns_pleskxml @@ -77,7 +77,6 @@ dns_pleskxml_add() { _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record' - # printf using template in a variable - not a style issue # shellcheck disable=SC2059 request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")" From 1253357a39206dd43047bdbbbd341ffef1d735ab Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 01:48:02 +0000 Subject: [PATCH 09/32] edits to comments --- dnsapi/dns_pleskxml | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/dnsapi/dns_pleskxml b/dnsapi/dns_pleskxml index 83017c3b..e2ec5717 100644 --- a/dnsapi/dns_pleskxml +++ b/dnsapi/dns_pleskxml @@ -1,6 +1,6 @@ #!/usr/bin/env sh -## Name: dns_pleskxml.sh +## Name: dns_pleskxml ## Created by Stilez. ## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files) @@ -13,14 +13,16 @@ ## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action. ## So this API module can handle such a request, if needed. +## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records. + ## The plesk plugin uses the xml api to add and remvoe the dns records. Therefore the url, username ## and password have to be configured by the user before this module is called. ## ## ``` -## export pleskxml_uri="https://YOUR_PLESK_URI_HERE:8443/enterprise/control/agent.php" +## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php" ## (or probably something similar) -## export pleskxml_user="plesk username" -## export pleskxml_pass="plesk password" +## export pleskxml_user="my plesk username" +## export pleskxml_pass="my plesk password" ## ``` ## Ok, let's issue a cert now: @@ -42,19 +44,19 @@ NEWLINE='\ pleskxml_tplt_get_domains="" # Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh # Also used to test credentials and URI. -# No args. +# No params. pleskxml_tplt_get_dns_records="%s" # Get all DNS records for a Plesk domain ID. -# ARG = Plesk domain id to query +# PARAM = Plesk domain id to query pleskxml_tplt_add_txt_record="%sTXT%s%s" # Add a TXT record to a domain. -# ARGS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value +# PARAMS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value pleskxml_tplt_rmv_dns_record="%s" -# Add a TXT record to a domain. -# ARG = the Plesk internal ID for the dns record to be deleted +# Delete a specific TXT record from a domain. +# PARAM = the Plesk internal ID for the DNS record to be deleted #################### Public functions ################################## @@ -63,7 +65,7 @@ dns_pleskxml_add() { fulldomain=$1 txtvalue=$2 - _info "Entering dns_pleskxml_add() to add TXT record '$2' to domain '$1'..." + _info "Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'..." # Get credentials if not already checked, and confirm we can log in to Plesk XML API if ! _credential_check; then @@ -110,7 +112,7 @@ dns_pleskxml_rm() { fulldomain=$1 txtvalue=$2 - _info "Entering dns_pleskxml_rm() to remove TXT record '$2' from domain '$1'..." + _info "Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'..." # Get credentials if not already checked, and confirm we can log in to Plesk XML API if ! _credential_check; then @@ -189,19 +191,19 @@ dns_pleskxml_rm() { #################### Private functions below ################################## -# Outputs value of a variable +# Outputs value of a variable without additional newlines etc _value() { printf '%s' "$1" } -# Outputs value of a variable (FQDN) and cuts it at 2 delimiters +# Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between # $1, $2 = where to cut # $3 = FQDN _valuecut() { printf '%s' "$3" | cut -d . -f "${1}-${2}" } -# Cleans up an API response, splits it "per item" and greps for a string to validate useful lines +# Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines # $1 - result string from API # $2 - tag to resplit on (usually "result" or "domain") # $3 - regex to recognise useful return lines @@ -228,8 +230,6 @@ _call_api() { pleskxml_retcode="$?" _debug "acme _post() returned retcode=$pleskxml_retcode. Literal response:" '\n' "'${pleskxml_prettyprint_result}'" - # Error handling - # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. @@ -320,7 +320,7 @@ _credential_check() { _pleskxml_get_root_domain() { _debug "Identifying DNS root domain for '$1' that is managed by the Plesk account." - # test if the domain is valid for splitting. + # test if the domain as provided is valid for splitting. if _value "$root_domain_name" | grep -qvE '^[^.]+\.[^.]+\.[^.]'; then _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." @@ -334,10 +334,10 @@ _pleskxml_get_root_domain() { return 1 fi - # Generate a hacked list of domains known to this Plesk account. + # Generate a crude list of domains known to this Plesk account. # We convert tags to so it'll flag on a hit with either or fields, # for non-Western character sets. - # Output will be one line per known domain, containing 1 or 2 tages and an tag + # Output will be one line per known domain, containing 2 tages and a single tag # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -E 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '')" @@ -345,7 +345,7 @@ _pleskxml_get_root_domain() { _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" # loop and test if domain, or any parent domain, is managed by Plesk - # Loop until we don't have any '.' in the sring we're testing as a root domain + # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain root_domain_name="$1" doneloop=0 @@ -378,7 +378,7 @@ _pleskxml_get_root_domain() { done # if we get here, we failed to find a root domain match in the list of domains managed by Plesk. - # if we never ran the loop a first time, $1 wasn't at least a 2 level domain (domain.tld) and wasn't valid anyway + # if we never ran the loop even once, $1 wasn't a 2nd level (or deeper) domain (e.g. domain.tld) and wasn't valid anyway if [ -z $doneloop ]; then _err "'$1' isn't a valid domain for ACME DNS. Exiting." From 9eb5f65b8f272b71fdf2d69a33492e67b5b76836 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 07:38:22 +0000 Subject: [PATCH 10/32] edit comments --- dnsapi/dns_pleskxml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_pleskxml b/dnsapi/dns_pleskxml index e2ec5717..d613f8e1 100644 --- a/dnsapi/dns_pleskxml +++ b/dnsapi/dns_pleskxml @@ -60,7 +60,7 @@ pleskxml_tplt_rmv_dns_record="%s #################### Public functions ################################## -#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +#Usage: dns_pleskxml_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_pleskxml_add() { fulldomain=$1 txtvalue=$2 @@ -107,7 +107,7 @@ dns_pleskxml_add() { return 0 } -#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +#Usage: dns_pleskxml_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_pleskxml_rm() { fulldomain=$1 txtvalue=$2 From bc291141b13a802ac0190ba06946ef2fa9add768 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 16:58:22 +0000 Subject: [PATCH 11/32] fix filename --- dnsapi/dns_pleskxml.sh | 391 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 dnsapi/dns_pleskxml.sh diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh new file mode 100644 index 00000000..25d1e6dc --- /dev/null +++ b/dnsapi/dns_pleskxml.sh @@ -0,0 +1,391 @@ +#!/usr/bin/env sh + +## Name: dns_pleskxml.sh +## Created by Stilez. +## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files) + +## This DNS01 method uses the Plesk XML API described at: +## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709 +## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784 + +## Note: a DNS ID with host = empty string is OK for this API, see +## https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 +## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action. +## So this API module can handle such a request, if needed. + +## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records. + +## The plesk plugin uses the xml api to add and remvoe the dns records. Therefore the url, username +## and password have to be configured by the user before this module is called. +## +## ``` +## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php" +## (or probably something similar) +## export pleskxml_user="my plesk username" +## export pleskxml_pass="my plesk password" +## ``` + +## Ok, let's issue a cert now: +## ``` +## acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com +## ``` +## +## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed. + +#################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ################################## + +pleskxml_init_checks_done=0 + +# Variable containing bare newline - not a style issue +# shellcheck disable=SC1004 +NEWLINE='\ +' + +pleskxml_tplt_get_domains="" +# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh +# Also used to test credentials and URI. +# No params. + +pleskxml_tplt_get_dns_records="%s" +# Get all DNS records for a Plesk domain ID. +# PARAM = Plesk domain id to query + +pleskxml_tplt_add_txt_record="%sTXT%s%s" +# Add a TXT record to a domain. +# PARAMS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value + +pleskxml_tplt_rmv_dns_record="%s" +# Delete a specific TXT record from a domain. +# PARAM = the Plesk internal ID for the DNS record to be deleted + +#################### Public functions ################################## + +#Usage: dns_pleskxml_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_pleskxml_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'..." + + # Get credentials if not already checked, and confirm we can log in to Plesk XML API + if ! _credential_check; then + return 1 + fi + + # Get root and subdomain details, and Plesk domain ID + if ! _pleskxml_get_root_domain "$fulldomain"; then + return 1 + fi + + _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record' + + # printf using template in a variable - not a style issue + # shellcheck disable=SC2059 + request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")" + if ! _call_api "$request"; then + return 1 + fi + + # OK, we should have added a TXT record. Let's check and return success if so. + # All that should be left in the result, is one section, containing okNEW_DNS_RECORD_ID + + results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" + + if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then + # Error - doesn't contain expected string. Something's wrong. + _err 'Error when calling Plesk XML API.' + _err 'The result did not contain the expected XXXXX section, or contained other values as well.' + _err 'This is unexpected: something has gone wrong.' + _err 'The full response was:\n' "$pleskxml_prettyprint_result" + return 1 + fi + + recid="$(_value "$results" | grep -E '[0-9]+' | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" + + _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." + + return 0 +} + +#Usage: dns_pleskxml_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_pleskxml_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'..." + + # Get credentials if not already checked, and confirm we can log in to Plesk XML API + if ! _credential_check; then + return 1 + fi + + # Get root and subdomain details, and Plesk domain ID + if ! _pleskxml_get_root_domain "$fulldomain"; then + return 1 + fi + + _debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs' + + # printf using template in a variable - not a style issue + # shellcheck disable=SC2059 + request="$(printf "$pleskxml_tplt_get_dns_records" "$root_domain_id")" + if ! _call_api "$request"; then + return 1 + fi + + # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) + reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ + | grep "${root_domain_id}" \ + | grep -E '[0-9]+' \ + | grep 'TXT' + )" + + if [ -z "$reclist" ]; then + _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting." + return 1 + fi + + _debug "Got list of DNS TXT records for root domain '$root_domain_name'"':\n'"$reclist" + + recid="$(_value "$reclist" \ + | grep "$1." \ + | grep "$txtvalue" \ + | sed -E 's/(^.*|<\/id>.*$)//g' + )" + + _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" + + if ! _value "$recid" | grep -Eq '^[0-9]+$'; then + _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" + _err "Cannot delete TXT record. Exiting." + return 1 + fi + + _debug "Found Plesk record ID for target text string '${txtvalue}': ID=${recid}" + _debug 'Calling Plesk XML API to remove TXT record' + + # printf using template in a variable - not a style issue + # shellcheck disable=SC2059 + request="$(printf "$pleskxml_tplt_rmv_dns_record" "$recid")" + if ! _call_api "$request"; then + return 1 + fi + + # OK, we should have removed a TXT record. Let's check and return success if so. + # All that should be left in the result, is one section, containing okPLESK_DELETED_DNS_RECORD_ID + + results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" + + if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then + # Error - doesn't contain expected string. Something's wrong. + _err 'Error when calling Plesk XML API.' + _err 'The result did not contain the expected XXXXX section, or contained other values as well.' + _err 'This is unexpected: something has gone wrong.' + _err 'The full response was:\n' "$pleskxml_prettyprint_result" + return 1 + fi + + _info "Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm()." + return 0 +} + +#################### Private functions below ################################## + +# Outputs value of a variable without additional newlines etc +_value() { + printf '%s' "$1" +} + +# Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between +# $1, $2 = where to cut +# $3 = FQDN +_valuecut() { + printf '%s' "$3" | cut -d . -f "${1}-${2}" +} + +# Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines +# $1 - result string from API +# $2 - tag to resplit on (usually "result" or "domain") +# $3 - regex to recognise useful return lines +_api_response_split() { + printf '%s' "$1" \ + | sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' \ + | tr -d '\n\r' \ + | sed -E "s/<\/?$2>/${NEWLINE}/g" \ + | grep -E "$3" +} + +# Calls Plesk XML API, and checks results for obvious issues +_call_api() { + request="$1" + errtext='' + + _debug 'Entered _call_api(). Calling Plesk XML API with request:\n' "'${request}'" + + export _H1="HTTP_AUTH_LOGIN: $pleskxml_user" + export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass" + export _H3="content-Type: text/xml" + export _H4="HTTP_PRETTY_PRINT: true" + pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")" + pleskxml_retcode="$?" + _debug "acme _post() returned retcode=$pleskxml_retcode. Literal response:" '\n' "'${pleskxml_prettyprint_result}'" + + # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. + # Also detect if there simply aren't any status lines (null result?) and report that, as well. + + statuslines="$(echo "$pleskxml_prettyprint_result" | grep -E '^[[:space:]]*[^<]*[[:space:]]*$')" + + if _value "$statuslines" | grep -qv 'ok'; then + + # We have some status lines that aren't "ok". Get the details + errtext="$(_value "$pleskxml_prettyprint_result" \ + | grep -iE "(||)" \ + | sed -E 's/(^[[:space:]]+|<\/[a-z]+$)//g' \ + | sed -E 's/^<([a-z]+)>/\1: /' + )" + + elif ! _value "$statuslines" | grep -q 'ok'; then + + # We have no status lines at all. Results are empty + errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.' + + fi + + if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then + _err "The Plesk XML API call failed." + _err "The return code for the POST request was $pleskxml_retcode (0=success)." + if [ "$errtext" != "" ]; then + _err 'Status and error messages received from the Plesk server:\n' "$errtext" + else + _err "No additional error messages were received back from the Plesk server" + fi + return 1 + fi + + _debug "Leaving _call_api(). Successful call." + + return 0 +} + +# Startup checks (credentials, URI) +_credential_check() { + _debug "Checking Plesk XML API login credentials and URI..." + + if [ "$pleskxml_init_checks_done" -eq 1 ]; then + _debug "Initial checks already done, no need to repeat. Skipped." + return 0 + fi + + pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}" + pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}" + pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}" + + _debug "Credentials - User: '${pleskxml_user}' Passwd: ****** URI: '${pleskxml_uri}'" + + if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then + pleskxml_user="" + pleskxml_pass="" + pleskxml_uri="" + _err "You didn't specify one or more of the Plesk XML API username, password, or URI." + _err "Please create these and try again." + _err "Instructions are in the module source code." + return 1 + fi + + # Test the API is usable, by trying to read the list of managed domains... + _call_api "$pleskxml_tplt_get_domains" + if [ "$pleskxml_retcode" -ne 0 ]; then + _err '\nFailed to access Plesk XML API.' + _err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again." + return 1 + fi + + _saveaccountconf_mutable pleskxml_uri "$pleskxml_uri" + _saveaccountconf_mutable pleskxml_user "$pleskxml_user" + _saveaccountconf_mutable pleskxml_pass "$pleskxml_pass" + + _debug "Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use." + + pleskxml_init_checks_done=1 + + return 0 +} + +# For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any. + +# IMPORTANT NOTE: a result with host = empty string is OK for this API, see +# https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 +# See notes at top of this file + +_pleskxml_get_root_domain() { + _debug "Identifying DNS root domain for '$1' that is managed by the Plesk account." + + # test if the domain as provided is valid for splitting. + + if _value "$root_domain_name" | grep -qvE '^[^.]+\.[^.]+\.[^.]'; then + _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." + return 1 + fi + + _debug "Querying Plesk server for list of managed domains..." + + _call_api "$pleskxml_tplt_get_domains" + if [ "$pleskxml_retcode" -ne 0 ]; then + return 1 + fi + + # Generate a crude list of domains known to this Plesk account. + # We convert tags to so it'll flag on a hit with either or fields, + # for non-Western character sets. + # Output will be one line per known domain, containing 2 tages and a single tag + # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. + + output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -E 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '')" + + _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" + + # loop and test if domain, or any parent domain, is managed by Plesk + # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain + + root_domain_name="$1" + doneloop=0 + + while _contains "$root_domain_name" '\.'; do + + _debug "Checking if '$root_domain_name' is managed by the Plesk server..." + + root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" + + if [ -n "$root_domain_id" ]; then + # Found a match + # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT. + # SO WE HANDLE IT AND DON'T PREVENT IT + sub_domain_name="$(_value "$1" | sed -E "s/\.?${root_domain_name}"'$//')" + _info "Matched host '$1' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." + return 0 + fi + + # No match, try next parent up (if any)... + + if _contains "$root_domain_name" '\.[^.]+\.'; then + _debug "No match, trying next parent up..." + else + _debug "No match,and next parent would be a TLD..." + fi + root_domain_name="$(_valuecut 2 1000 "$root_domain_name")" + doneloop=1 + + done + + # if we get here, we failed to find a root domain match in the list of domains managed by Plesk. + # if we never ran the loop even once, $1 wasn't a 2nd level (or deeper) domain (e.g. domain.tld) and wasn't valid anyway + + if [ -z $doneloop ]; then + _err "'$1' isn't a valid domain for ACME DNS. Exiting." + else + _err "Cannot find '$1' or any parent domain of it, in Plesk." + _err "Are you sure that this domain is managed by this Plesk server?" + fi + + return 1 +} From 7c09bdc6e0dde113a241d1526ede4b9a01a7f864 Mon Sep 17 00:00:00 2001 From: stilez Date: Sun, 27 Oct 2019 16:58:58 +0000 Subject: [PATCH 12/32] renamed --- dnsapi/dns_pleskxml | 391 -------------------------------------------- 1 file changed, 391 deletions(-) delete mode 100644 dnsapi/dns_pleskxml diff --git a/dnsapi/dns_pleskxml b/dnsapi/dns_pleskxml deleted file mode 100644 index d613f8e1..00000000 --- a/dnsapi/dns_pleskxml +++ /dev/null @@ -1,391 +0,0 @@ -#!/usr/bin/env sh - -## Name: dns_pleskxml -## Created by Stilez. -## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files) - -## This DNS01 method uses the Plesk XML API described at: -## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709 -## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784 - -## Note: a DNS ID with host = empty string is OK for this API, see -## https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 -## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action. -## So this API module can handle such a request, if needed. - -## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records. - -## The plesk plugin uses the xml api to add and remvoe the dns records. Therefore the url, username -## and password have to be configured by the user before this module is called. -## -## ``` -## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php" -## (or probably something similar) -## export pleskxml_user="my plesk username" -## export pleskxml_pass="my plesk password" -## ``` - -## Ok, let's issue a cert now: -## ``` -## acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com -## ``` -## -## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed. - -#################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ################################## - -pleskxml_init_checks_done=0 - -# Variable containing bare newline - not a style issue -# shellcheck disable=SC1004 -NEWLINE='\ -' - -pleskxml_tplt_get_domains="" -# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh -# Also used to test credentials and URI. -# No params. - -pleskxml_tplt_get_dns_records="%s" -# Get all DNS records for a Plesk domain ID. -# PARAM = Plesk domain id to query - -pleskxml_tplt_add_txt_record="%sTXT%s%s" -# Add a TXT record to a domain. -# PARAMS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value - -pleskxml_tplt_rmv_dns_record="%s" -# Delete a specific TXT record from a domain. -# PARAM = the Plesk internal ID for the DNS record to be deleted - -#################### Public functions ################################## - -#Usage: dns_pleskxml_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_pleskxml_add() { - fulldomain=$1 - txtvalue=$2 - - _info "Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'..." - - # Get credentials if not already checked, and confirm we can log in to Plesk XML API - if ! _credential_check; then - return 1 - fi - - # Get root and subdomain details, and Plesk domain ID - if ! _pleskxml_get_root_domain "$fulldomain"; then - return 1 - fi - - _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record' - - # printf using template in a variable - not a style issue - # shellcheck disable=SC2059 - request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")" - if ! _call_api "$request"; then - return 1 - fi - - # OK, we should have added a TXT record. Let's check and return success if so. - # All that should be left in the result, is one section, containing okNEW_DNS_RECORD_ID - - results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - - if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then - # Error - doesn't contain expected string. Something's wrong. - _err 'Error when calling Plesk XML API.' - _err 'The result did not contain the expected XXXXX section, or contained other values as well.' - _err 'This is unexpected: something has gone wrong.' - _err 'The full response was:\n' "$pleskxml_prettyprint_result" - return 1 - fi - - recid="$(_value "$results" | grep -E '[0-9]+' | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" - - _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." - - return 0 -} - -#Usage: dns_pleskxml_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_pleskxml_rm() { - fulldomain=$1 - txtvalue=$2 - - _info "Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'..." - - # Get credentials if not already checked, and confirm we can log in to Plesk XML API - if ! _credential_check; then - return 1 - fi - - # Get root and subdomain details, and Plesk domain ID - if ! _pleskxml_get_root_domain "$fulldomain"; then - return 1 - fi - - _debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs' - - # printf using template in a variable - not a style issue - # shellcheck disable=SC2059 - request="$(printf "$pleskxml_tplt_get_dns_records" "$root_domain_id")" - if ! _call_api "$request"; then - return 1 - fi - - # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) - reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ - | grep "${root_domain_id}" \ - | grep -E '[0-9]+' \ - | grep 'TXT' - )" - - if [ -z "$reclist" ]; then - _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting." - return 1 - fi - - _debug "Got list of DNS TXT records for root domain '$root_domain_name'"':\n'"$reclist" - - recid="$(_value "$reclist" \ - | grep "$1." \ - | grep "$txtvalue" \ - | sed -E 's/(^.*|<\/id>.*$)//g' - )" - - _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" - - if ! _value "$recid" | grep -Eq '^[0-9]+$'; then - _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" - _err "Cannot delete TXT record. Exiting." - return 1 - fi - - _debug "Found Plesk record ID for target text string '${txtvalue}': ID=${recid}" - _debug 'Calling Plesk XML API to remove TXT record' - - # printf using template in a variable - not a style issue - # shellcheck disable=SC2059 - request="$(printf "$pleskxml_tplt_rmv_dns_record" "$recid")" - if ! _call_api "$request"; then - return 1 - fi - - # OK, we should have removed a TXT record. Let's check and return success if so. - # All that should be left in the result, is one section, containing okPLESK_DELETED_DNS_RECORD_ID - - results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - - if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then - # Error - doesn't contain expected string. Something's wrong. - _err 'Error when calling Plesk XML API.' - _err 'The result did not contain the expected XXXXX section, or contained other values as well.' - _err 'This is unexpected: something has gone wrong.' - _err 'The full response was:\n' "$pleskxml_prettyprint_result" - return 1 - fi - - _info "Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm()." - return 0 -} - -#################### Private functions below ################################## - -# Outputs value of a variable without additional newlines etc -_value() { - printf '%s' "$1" -} - -# Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between -# $1, $2 = where to cut -# $3 = FQDN -_valuecut() { - printf '%s' "$3" | cut -d . -f "${1}-${2}" -} - -# Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines -# $1 - result string from API -# $2 - tag to resplit on (usually "result" or "domain") -# $3 - regex to recognise useful return lines -_api_response_split() { - printf '%s' "$1" \ - | sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' \ - | tr -d '\n\r' \ - | sed -E "s/<\/?$2>/${NEWLINE}/g" \ - | grep -E "$3" -} - -# Calls Plesk XML API, and checks results for obvious issues -_call_api() { - request="$1" - errtext='' - - _debug 'Entered _call_api(). Calling Plesk XML API with request:\n' "'${request}'" - - export _H1="HTTP_AUTH_LOGIN: $pleskxml_user" - export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass" - export _H3="content-Type: text/xml" - export _H4="HTTP_PRETTY_PRINT: true" - pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")" - pleskxml_retcode="$?" - _debug "acme _post() returned retcode=$pleskxml_retcode. Literal response:" '\n' "'${pleskxml_prettyprint_result}'" - - # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. - # Also detect if there simply aren't any status lines (null result?) and report that, as well. - - statuslines="$(echo "$pleskxml_prettyprint_result" | grep -E '^[[:space:]]*[^<]*[[:space:]]*$')" - - if _value "$statuslines" | grep -qv 'ok'; then - - # We have some status lines that aren't "ok". Get the details - errtext="$(_value "$pleskxml_prettyprint_result" \ - | grep -iE "(||)" \ - | sed -E 's/(^[[:space:]]+|<\/[a-z]+$)//g' \ - | sed -E 's/^<([a-z]+)>/\1: /' - )" - - elif ! _value "$statuslines" | grep -q 'ok'; then - - # We have no status lines at all. Results are empty - errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.' - - fi - - if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then - _err "The Plesk XML API call failed." - _err "The return code for the POST request was $pleskxml_retcode (0=success)." - if [ "$errtext" != "" ]; then - _err 'Status and error messages received from the Plesk server:\n' "$errtext" - else - _err "No additional error messages were received back from the Plesk server" - fi - return 1 - fi - - _debug "Leaving _call_api(). Successful call." - - return 0 -} - -# Startup checks (credentials, URI) -_credential_check() { - _debug "Checking Plesk XML API login credentials and URI..." - - if [ "$pleskxml_init_checks_done" -eq 1 ]; then - _debug "Initial checks already done, no need to repeat. Skipped." - return 0 - fi - - pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}" - pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}" - pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}" - - _debug "Credentials - User: '${pleskxml_user}' Passwd: ****** URI: '${pleskxml_uri}'" - - if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then - pleskxml_user="" - pleskxml_pass="" - pleskxml_uri="" - _err "You didn't specify one or more of the Plesk XML API username, password, or URI." - _err "Please create these and try again." - _err "Instructions are in the module source code." - return 1 - fi - - # Test the API is usable, by trying to read the list of managed domains... - _call_api "$pleskxml_tplt_get_domains" - if [ "$pleskxml_retcode" -ne 0 ]; then - _err '\nFailed to access Plesk XML API.' - _err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again." - return 1 - fi - - _saveaccountconf_mutable pleskxml_uri "$pleskxml_uri" - _saveaccountconf_mutable pleskxml_user "$pleskxml_user" - _saveaccountconf_mutable pleskxml_pass "$pleskxml_pass" - - _debug "Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use." - - pleskxml_init_checks_done=1 - - return 0 -} - -# For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any. - -# IMPORTANT NOTE: a result with host = empty string is OK for this API, see -# https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 -# See notes at top of this file - -_pleskxml_get_root_domain() { - _debug "Identifying DNS root domain for '$1' that is managed by the Plesk account." - - # test if the domain as provided is valid for splitting. - - if _value "$root_domain_name" | grep -qvE '^[^.]+\.[^.]+\.[^.]'; then - _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." - return 1 - fi - - _debug "Querying Plesk server for list of managed domains..." - - _call_api "$pleskxml_tplt_get_domains" - if [ "$pleskxml_retcode" -ne 0 ]; then - return 1 - fi - - # Generate a crude list of domains known to this Plesk account. - # We convert tags to so it'll flag on a hit with either or fields, - # for non-Western character sets. - # Output will be one line per known domain, containing 2 tages and a single tag - # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. - - output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -E 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '')" - - _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" - - # loop and test if domain, or any parent domain, is managed by Plesk - # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain - - root_domain_name="$1" - doneloop=0 - - while _contains "$root_domain_name" '\.'; do - - _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - - root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" - - if [ -n "$root_domain_id" ]; then - # Found a match - # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT. - # SO WE HANDLE IT AND DON'T PREVENT IT - sub_domain_name="$(_value "$1" | sed -E "s/\.?${root_domain_name}"'$//')" - _info "Matched host '$1' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." - return 0 - fi - - # No match, try next parent up (if any)... - - if _contains "$root_domain_name" '\.[^.]+\.'; then - _debug "No match, trying next parent up..." - else - _debug "No match,and next parent would be a TLD..." - fi - root_domain_name="$(_valuecut 2 1000 "$root_domain_name")" - doneloop=1 - - done - - # if we get here, we failed to find a root domain match in the list of domains managed by Plesk. - # if we never ran the loop even once, $1 wasn't a 2nd level (or deeper) domain (e.g. domain.tld) and wasn't valid anyway - - if [ -z $doneloop ]; then - _err "'$1' isn't a valid domain for ACME DNS. Exiting." - else - _err "Cannot find '$1' or any parent domain of it, in Plesk." - _err "Are you sure that this domain is managed by this Plesk server?" - fi - - return 1 -} From d7affad05981f3fdc59ecf1f30e4455f06cc9f5a Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 29 Oct 2019 10:30:00 +0000 Subject: [PATCH 13/32] various small improves --- dnsapi/dns_pleskxml.sh | 73 +++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 25d1e6dc..bd6eaa87 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -189,7 +189,7 @@ dns_pleskxml_rm() { return 0 } -#################### Private functions below ################################## +#################### Private functions below (utility functions) ################################## # Outputs value of a variable without additional newlines etc _value() { @@ -203,6 +203,12 @@ _valuecut() { printf '%s' "$3" | cut -d . -f "${1}-${2}" } +# Counts '.' present in a domain name +# $1 = domain name +_countdots() { + _value "$1" | tr -dc '.' | wc -c +} + # Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines # $1 - result string from API # $2 - tag to resplit on (usually "result" or "domain") @@ -215,6 +221,8 @@ _api_response_split() { | grep -E "$3" } +#################### Private functions below (DNS functions) ################################## + # Calls Plesk XML API, and checks results for obvious issues _call_api() { request="$1" @@ -228,7 +236,7 @@ _call_api() { export _H4="HTTP_PRETTY_PRINT: true" pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")" pleskxml_retcode="$?" - _debug "acme _post() returned retcode=$pleskxml_retcode. Literal response:" '\n' "'${pleskxml_prettyprint_result}'" + _debug 'The responses from the Plesk XML server were:\n' "retcode=$pleskxml_retcode. Literal response:"'\n' "'$pleskxml_prettyprint_result'" # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. @@ -239,9 +247,9 @@ _call_api() { # We have some status lines that aren't "ok". Get the details errtext="$(_value "$pleskxml_prettyprint_result" \ - | grep -iE "(||)" \ - | sed -E 's/(^[[:space:]]+|<\/[a-z]+$)//g' \ - | sed -E 's/^<([a-z]+)>/\1: /' + | grep -E "(||)" \ + | sed -E 's/^<(status|errcode|errtext)>/\1: /' \ + | sed -E 's/(^[[:space:]]+|<\/(status|errcode|errtext)>$)//g' \ )" elif ! _value "$statuslines" | grep -q 'ok'; then @@ -252,14 +260,23 @@ _call_api() { fi if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then - _err "The Plesk XML API call failed." - _err "The return code for the POST request was $pleskxml_retcode (0=success)." + # Call failed, for reasons either in the retcode or the response text... + + if [ "$pleskxml_retcode" -eq 0 ]; then + _err "The POST request was successfully sent to the Plesk server." + else + _err "The return code for the POST request was $pleskxml_retcode (non-zero = could not submit request to server)." + fi + if [ "$errtext" != "" ]; then - _err 'Status and error messages received from the Plesk server:\n' "$errtext" + _err 'The error responses received from the Plesk server were:\n' "$errtext" else _err "No additional error messages were received back from the Plesk server" fi + + _err "The Plesk XML API call failed." return 1 + fi _debug "Leaving _call_api(). Successful call." @@ -319,10 +336,12 @@ _credential_check() { _pleskxml_get_root_domain() { _debug "Identifying DNS root domain for '$1' that is managed by the Plesk account." + original_full_domain_name="$1" + root_domain_name="$1" # test if the domain as provided is valid for splitting. - if _value "$root_domain_name" | grep -qvE '^[^.]+\.[^.]+\.[^.]'; then + if ! _countdots "$root_domain_name"; then _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." return 1 fi @@ -347,10 +366,7 @@ _pleskxml_get_root_domain() { # loop and test if domain, or any parent domain, is managed by Plesk # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain - root_domain_name="$1" - doneloop=0 - - while _contains "$root_domain_name" '\.'; do + while true; do _debug "Checking if '$root_domain_name' is managed by the Plesk server..." @@ -360,32 +376,23 @@ _pleskxml_get_root_domain() { # Found a match # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT. # SO WE HANDLE IT AND DON'T PREVENT IT - sub_domain_name="$(_value "$1" | sed -E "s/\.?${root_domain_name}"'$//')" - _info "Matched host '$1' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." + sub_domain_name="$(_value "$original_full_domain_name" | sed -E "s/\.?${root_domain_name}"'$//')" + _info "Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." return 0 fi # No match, try next parent up (if any)... - if _contains "$root_domain_name" '\.[^.]+\.'; then - _debug "No match, trying next parent up..." - else - _debug "No match,and next parent would be a TLD..." - fi root_domain_name="$(_valuecut 2 1000 "$root_domain_name")" - doneloop=1 + + if ! _countdots "$root_domain_name"; then + _debug "No match, and next parent would be a TLD..." + _err "Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk." + _err "Are you sure that this domain is managed by this Plesk server?" + return 1 + fi + + _debug "No match, trying next parent up..." done - - # if we get here, we failed to find a root domain match in the list of domains managed by Plesk. - # if we never ran the loop even once, $1 wasn't a 2nd level (or deeper) domain (e.g. domain.tld) and wasn't valid anyway - - if [ -z $doneloop ]; then - _err "'$1' isn't a valid domain for ACME DNS. Exiting." - else - _err "Cannot find '$1' or any parent domain of it, in Plesk." - _err "Are you sure that this domain is managed by this Plesk server?" - fi - - return 1 } From b7c3df455e275a6fa0556d178c48f944666410bf Mon Sep 17 00:00:00 2001 From: stilez Date: Wed, 30 Oct 2019 09:38:03 +0000 Subject: [PATCH 14/32] travis fix --- dnsapi/dns_pleskxml.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index bd6eaa87..aa1da6f4 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -249,7 +249,7 @@ _call_api() { errtext="$(_value "$pleskxml_prettyprint_result" \ | grep -E "(||)" \ | sed -E 's/^<(status|errcode|errtext)>/\1: /' \ - | sed -E 's/(^[[:space:]]+|<\/(status|errcode|errtext)>$)//g' \ + | sed -E 's/(^[[:space:]]+|<\/(status|errcode|errtext)>$)//g' )" elif ! _value "$statuslines" | grep -q 'ok'; then From 05ced9fbc4edf0c2d3bbe9913209e6c750702cad Mon Sep 17 00:00:00 2001 From: stilez Date: Wed, 30 Oct 2019 09:53:40 +0000 Subject: [PATCH 15/32] edit a comment --- dnsapi/dns_pleskxml.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index aa1da6f4..24650d10 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -4,7 +4,7 @@ ## Created by Stilez. ## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files) -## This DNS01 method uses the Plesk XML API described at: +## This DNS-01 method uses the Plesk XML API described at: ## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709 ## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784 @@ -15,8 +15,8 @@ ## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records. -## The plesk plugin uses the xml api to add and remvoe the dns records. Therefore the url, username -## and password have to be configured by the user before this module is called. +## The user credentials (username+password) and URL/URI for the Plesk XML API must be set by the user +## before this module is called (case sensitive): ## ## ``` ## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php" From 3441bd0e7c476a5add8dbfec730b5fa25b7a9f7e Mon Sep 17 00:00:00 2001 From: stilez Date: Wed, 30 Oct 2019 10:22:04 +0000 Subject: [PATCH 16/32] improve _err message and remove a dubious _debug message. --- dnsapi/dns_pleskxml.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 24650d10..8a957e40 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -297,15 +297,13 @@ _credential_check() { pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}" pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}" - _debug "Credentials - User: '${pleskxml_user}' Passwd: ****** URI: '${pleskxml_uri}'" - if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then pleskxml_user="" pleskxml_pass="" pleskxml_uri="" _err "You didn't specify one or more of the Plesk XML API username, password, or URI." _err "Please create these and try again." - _err "Instructions are in the module source code." + _err "Instructions are in the 'dns_pleskxml' plugin source code or in the acme.sh documentation." return 1 fi From 2422e0b481b74e008fc995afd4e1994d6f0817e4 Mon Sep 17 00:00:00 2001 From: stilez Date: Wed, 30 Oct 2019 17:01:06 +0000 Subject: [PATCH 17/32] grep -E and sed -E --- dnsapi/dns_pleskxml.sh | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 8a957e40..03c58534 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -91,7 +91,7 @@ dns_pleskxml_add() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then + if ! _value "$results" | grep 'ok' | egrep -q '[0-9]+'; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' @@ -100,7 +100,7 @@ dns_pleskxml_add() { return 1 fi - recid="$(_value "$results" | grep -E '[0-9]+' | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" + recid="$(_value "$results" | egrep '[0-9]+' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." @@ -136,7 +136,7 @@ dns_pleskxml_rm() { # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ | grep "${root_domain_id}" \ - | grep -E '[0-9]+' \ + | egrep '[0-9]+' \ | grep 'TXT' )" @@ -150,12 +150,12 @@ dns_pleskxml_rm() { recid="$(_value "$reclist" \ | grep "$1." \ | grep "$txtvalue" \ - | sed -E 's/(^.*|<\/id>.*$)//g' + | sed -r 's/(^.*|<\/id>.*$)//g' )" _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" - if ! _value "$recid" | grep -Eq '^[0-9]+$'; then + if ! _value "$recid" | egrep -q '^[0-9]+$'; then _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" _err "Cannot delete TXT record. Exiting." return 1 @@ -176,7 +176,7 @@ dns_pleskxml_rm() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | grep -qE '[0-9]+'; then + if ! _value "$results" | grep 'ok' | egrep -q '[0-9]+'; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' @@ -215,10 +215,10 @@ _countdots() { # $3 - regex to recognise useful return lines _api_response_split() { printf '%s' "$1" \ - | sed -E 's/(^[[:space:]]+|[[:space:]]+$)//g' \ + | sed -r 's/(^[[:space:]]+|[[:space:]]+$)//g' \ | tr -d '\n\r' \ - | sed -E "s/<\/?$2>/${NEWLINE}/g" \ - | grep -E "$3" + | sed -r "s/<\/?$2>/${NEWLINE}/g" \ + | egrep "$3" } #################### Private functions below (DNS functions) ################################## @@ -241,15 +241,15 @@ _call_api() { # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. - statuslines="$(echo "$pleskxml_prettyprint_result" | grep -E '^[[:space:]]*[^<]*[[:space:]]*$')" + statuslines="$(echo "$pleskxml_prettyprint_result" | egrep '^[[:space:]]*[^<]*[[:space:]]*$')" if _value "$statuslines" | grep -qv 'ok'; then # We have some status lines that aren't "ok". Get the details errtext="$(_value "$pleskxml_prettyprint_result" \ - | grep -E "(||)" \ - | sed -E 's/^<(status|errcode|errtext)>/\1: /' \ - | sed -E 's/(^[[:space:]]+|<\/(status|errcode|errtext)>$)//g' + | egrep "(||)" \ + | sed -r 's/^<(status|errcode|errtext)>/\1: /' \ + | sed -r 's/(^[[:space:]]+|<\/(status|errcode|errtext)>$)//g' )" elif ! _value "$statuslines" | grep -q 'ok'; then @@ -357,7 +357,7 @@ _pleskxml_get_root_domain() { # Output will be one line per known domain, containing 2 tages and a single tag # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. - output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -E 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '')" + output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -r 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '')" _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" @@ -368,13 +368,13 @@ _pleskxml_get_root_domain() { _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -E 's/^.*([0-9]+)<\/id>.*$/\1/')" + root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT. # SO WE HANDLE IT AND DON'T PREVENT IT - sub_domain_name="$(_value "$original_full_domain_name" | sed -E "s/\.?${root_domain_name}"'$//')" + sub_domain_name="$(_value "$original_full_domain_name" | sed -r "s/\.?${root_domain_name}"'$//')" _info "Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." return 0 fi From a32b95544ba8aa385251f757940c93f3fef266c0 Mon Sep 17 00:00:00 2001 From: stilez Date: Wed, 30 Oct 2019 17:06:03 +0000 Subject: [PATCH 18/32] [[:space:]] -> " " --- dnsapi/dns_pleskxml.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 03c58534..12f56316 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -215,7 +215,7 @@ _countdots() { # $3 - regex to recognise useful return lines _api_response_split() { printf '%s' "$1" \ - | sed -r 's/(^[[:space:]]+|[[:space:]]+$)//g' \ + | sed -r 's/(^ +| +$)//g' \ | tr -d '\n\r' \ | sed -r "s/<\/?$2>/${NEWLINE}/g" \ | egrep "$3" @@ -241,15 +241,15 @@ _call_api() { # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. - statuslines="$(echo "$pleskxml_prettyprint_result" | egrep '^[[:space:]]*[^<]*[[:space:]]*$')" + statuslines="$(echo "$pleskxml_prettyprint_result" | egrep '^ *[^<]* *$')" if _value "$statuslines" | grep -qv 'ok'; then # We have some status lines that aren't "ok". Get the details errtext="$(_value "$pleskxml_prettyprint_result" \ | egrep "(||)" \ - | sed -r 's/^<(status|errcode|errtext)>/\1: /' \ - | sed -r 's/(^[[:space:]]+|<\/(status|errcode|errtext)>$)//g' + | sed -r 's/^ *<(status|errcode|errtext)>/\1: /' \ + | sed -r 's/<\/(status|errcode|errtext)>$//g' )" elif ! _value "$statuslines" | grep -q 'ok'; then From 343d7df57c366fe594edf8f4a523611a18cb0ac2 Mon Sep 17 00:00:00 2001 From: stilez Date: Wed, 30 Oct 2019 22:11:16 +0000 Subject: [PATCH 19/32] shellcheck directive --- dnsapi/dns_pleskxml.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 12f56316..c12b2eeb 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -1,5 +1,9 @@ #!/usr/bin/env sh +# Globally disable this shellcheck error. +# Shellcheck errors on egrep ("deprecated"), but acme.sh uses egrep for compatibility. +# shellcheck disable=SC2196 + ## Name: dns_pleskxml.sh ## Created by Stilez. ## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files) From 2d1a776db792e475dfdedc3a3bfde7b649d8fa7e Mon Sep 17 00:00:00 2001 From: stilez Date: Mon, 4 Nov 2019 18:40:12 +0000 Subject: [PATCH 20/32] Replace egrep -> basic regex grep (( ... isn't it annoying that basic regex has * but not + ..... )) --- dnsapi/dns_pleskxml.sh | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index c12b2eeb..f3d6a1de 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -1,9 +1,5 @@ #!/usr/bin/env sh -# Globally disable this shellcheck error. -# Shellcheck errors on egrep ("deprecated"), but acme.sh uses egrep for compatibility. -# shellcheck disable=SC2196 - ## Name: dns_pleskxml.sh ## Created by Stilez. ## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files) @@ -95,7 +91,7 @@ dns_pleskxml_add() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | egrep -q '[0-9]+'; then + if ! _value "$results" | grep 'ok' | grep -q '[0-9][0-9]*'; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' @@ -104,7 +100,7 @@ dns_pleskxml_add() { return 1 fi - recid="$(_value "$results" | egrep '[0-9]+' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" + recid="$(_value "$results" | grep '[0-9][0-9]*' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." @@ -140,7 +136,7 @@ dns_pleskxml_rm() { # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ | grep "${root_domain_id}" \ - | egrep '[0-9]+' \ + | grep '[0-9][0-9]*' \ | grep 'TXT' )" @@ -159,7 +155,7 @@ dns_pleskxml_rm() { _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" - if ! _value "$recid" | egrep -q '^[0-9]+$'; then + if ! _value "$recid" | grep -q '^[0-9][0-9]*$'; then _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" _err "Cannot delete TXT record. Exiting." return 1 @@ -180,7 +176,7 @@ dns_pleskxml_rm() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | egrep -q '[0-9]+'; then + if ! _value "$results" | grep 'ok' | grep -q '[0-9][0-9]*'; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' @@ -216,13 +212,16 @@ _countdots() { # Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines # $1 - result string from API # $2 - tag to resplit on (usually "result" or "domain") -# $3 - regex to recognise useful return lines +# $3 - basic regex to recognise useful return lines +# note: $3 matches via basic NOT extended regex (BRE), as extended regex capabilities not needed at the moment. +# Last line could change to instead, with suitablew ewscaping of ['"/$], +# if future Plesk XML API changes ever require extended regex _api_response_split() { printf '%s' "$1" \ | sed -r 's/(^ +| +$)//g' \ | tr -d '\n\r' \ | sed -r "s/<\/?$2>/${NEWLINE}/g" \ - | egrep "$3" + | grep "$3" } #################### Private functions below (DNS functions) ################################## @@ -245,15 +244,13 @@ _call_api() { # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. - statuslines="$(echo "$pleskxml_prettyprint_result" | egrep '^ *[^<]* *$')" + statuslines="$(echo "$pleskxml_prettyprint_result" | grep '^ *[^<]* *$')" if _value "$statuslines" | grep -qv 'ok'; then # We have some status lines that aren't "ok". Get the details errtext="$(_value "$pleskxml_prettyprint_result" \ - | egrep "(||)" \ - | sed -r 's/^ *<(status|errcode|errtext)>/\1: /' \ - | sed -r 's/<\/(status|errcode|errtext)>$//g' + | sed -rn 's/^ *<(status|errcode|errtext)>([^<]+)<\/(status|errcode|errtext)> *$/\1: \2/p' \ )" elif ! _value "$statuslines" | grep -q 'ok'; then From 63a779baa86ed8609c7030fa8aa37deb11341752 Mon Sep 17 00:00:00 2001 From: stilez Date: Mon, 4 Nov 2019 18:44:14 +0000 Subject: [PATCH 21/32] remove unnecessary \ --- dnsapi/dns_pleskxml.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index f3d6a1de..c673744f 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -250,7 +250,7 @@ _call_api() { # We have some status lines that aren't "ok". Get the details errtext="$(_value "$pleskxml_prettyprint_result" \ - | sed -rn 's/^ *<(status|errcode|errtext)>([^<]+)<\/(status|errcode|errtext)> *$/\1: \2/p' \ + | sed -rn 's/^ *<(status|errcode|errtext)>([^<]+)<\/(status|errcode|errtext)> *$/\1: \2/p' )" elif ! _value "$statuslines" | grep -q 'ok'; then From 04b0c62bf959c800c36f9b19da599741caacd1c8 Mon Sep 17 00:00:00 2001 From: stilez Date: Mon, 4 Nov 2019 19:05:44 +0000 Subject: [PATCH 22/32] basic regex's to use \+ Maybe BRE aren't as basic as they sound. But I'm sure `man grep` didn't list the extra syntax of "preceded by backslash" :) So let's use it --- dnsapi/dns_pleskxml.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index c673744f..dca0d246 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -91,7 +91,7 @@ dns_pleskxml_add() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | grep -q '[0-9][0-9]*'; then + if ! _value "$results" | grep 'ok' | grep -q '[0-9]\+'; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' @@ -100,7 +100,7 @@ dns_pleskxml_add() { return 1 fi - recid="$(_value "$results" | grep '[0-9][0-9]*' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" + recid="$(_value "$results" | grep '[0-9]\+' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." @@ -136,7 +136,7 @@ dns_pleskxml_rm() { # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ | grep "${root_domain_id}" \ - | grep '[0-9][0-9]*' \ + | grep '[0-9]\+' \ | grep 'TXT' )" @@ -155,7 +155,7 @@ dns_pleskxml_rm() { _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" - if ! _value "$recid" | grep -q '^[0-9][0-9]*$'; then + if ! _value "$recid" | grep -q '^[0-9]\+$'; then _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" _err "Cannot delete TXT record. Exiting." return 1 @@ -176,7 +176,7 @@ dns_pleskxml_rm() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | grep -q '[0-9][0-9]*'; then + if ! _value "$results" | grep 'ok' | grep -q '[0-9]\+'; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' From 896778cead810f88ffd8e5efa61d97cbd78dfe6d Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 17:56:20 +0000 Subject: [PATCH 23/32] Grep fixes and minor improvements --- dnsapi/dns_pleskxml.sh | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index dca0d246..551e42fe 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -91,7 +91,7 @@ dns_pleskxml_add() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | grep -q '[0-9]\+'; then + if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' @@ -100,7 +100,7 @@ dns_pleskxml_add() { return 1 fi - recid="$(_value "$results" | grep '[0-9]\+' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" + recid="$(_value "$results" | grep '[0-9]\{1,\}' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." @@ -136,7 +136,7 @@ dns_pleskxml_rm() { # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ | grep "${root_domain_id}" \ - | grep '[0-9]\+' \ + | grep '[0-9]\{1,\}' \ | grep 'TXT' )" @@ -155,7 +155,7 @@ dns_pleskxml_rm() { _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" - if ! _value "$recid" | grep -q '^[0-9]\+$'; then + if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" _err "Cannot delete TXT record. Exiting." return 1 @@ -176,7 +176,7 @@ dns_pleskxml_rm() { results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" - if ! _value "$results" | grep 'ok' | grep -q '[0-9]\+'; then + if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' @@ -244,20 +244,27 @@ _call_api() { # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. - statuslines="$(echo "$pleskxml_prettyprint_result" | grep '^ *[^<]* *$')" + statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *[^<]* *$')" + statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *ok *$')" - if _value "$statuslines" | grep -qv 'ok'; then - - # We have some status lines that aren't "ok". Get the details - errtext="$(_value "$pleskxml_prettyprint_result" \ - | sed -rn 's/^ *<(status|errcode|errtext)>([^<]+)<\/(status|errcode|errtext)> *$/\1: \2/p' - )" - - elif ! _value "$statuslines" | grep -q 'ok'; then + if [ -z "$statuslines_count_total" ]; then # We have no status lines at all. Results are empty errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.' + elif [ "$statuslines_count_okay" -ne "$statuslines_count_total" ]; then + + # We have some status lines that aren't "ok". Any available details are in API response fields "status" "errcode" and "errtext" + # Workaround for basic regex: + # - filter output to keep only lines like this: "SPACEStextSPACES" (shouldn't be necessary with prettyprint but guarantees subsequent code is ok) + # - then edit the 3 "useful" error tokens individually and remove closing tags on all lines + # - then filter again to remove all lines not edited (which will be the lines not starting A-Z) + errtext="$(_value "$pleskxml_prettyprint_result" \ + | grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' \ + | sed 's/^ */Status: /;s/^ */Error code: /;s/^ */Error text: /;s/<\/.*$//' \ + | grep '^[A-Z]' + )" + fi if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then @@ -266,7 +273,7 @@ _call_api() { if [ "$pleskxml_retcode" -eq 0 ]; then _err "The POST request was successfully sent to the Plesk server." else - _err "The return code for the POST request was $pleskxml_retcode (non-zero = could not submit request to server)." + _err "The return code for the POST request was $pleskxml_retcode (non-zero = failure in submitting request to server)." fi if [ "$errtext" != "" ]; then From cbacc779fcc3dc9bf3b5924d5691c12f9d74ebe3 Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 18:50:39 +0000 Subject: [PATCH 24/32] Fix some sed -r, and clean up some variable references ("$1" -> "$varname") --- dnsapi/dns_pleskxml.sh | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 551e42fe..2c9a4d56 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -100,7 +100,7 @@ dns_pleskxml_add() { return 1 fi - recid="$(_value "$results" | grep '[0-9]\{1,\}' | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" + recid="$(_value "$results" | grep '[0-9]\{1,\}' | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." @@ -145,16 +145,16 @@ dns_pleskxml_rm() { return 1 fi - _debug "Got list of DNS TXT records for root domain '$root_domain_name'"':\n'"$reclist" + _debug "Got list of DNS TXT records for root domain '$root_domain_name'. Full list is:"'\n'"$reclist" + + _debug "DNS TXT records for host '$fulldomain':"'\n'"$(_value "$reclist" | grep "${fulldomain}.")" recid="$(_value "$reclist" \ - | grep "$1." \ - | grep "$txtvalue" \ - | sed -r 's/(^.*|<\/id>.*$)//g' + | grep "${fulldomain}." \ + | grep "${txtvalue}" \ + | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/' )" - _debug "List of DNS TXT records for host:"'\n'"$(_value "$reclist" | grep "$1.")" - if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" _err "Cannot delete TXT record. Exiting." @@ -341,13 +341,13 @@ _credential_check() { # See notes at top of this file _pleskxml_get_root_domain() { - _debug "Identifying DNS root domain for '$1' that is managed by the Plesk account." original_full_domain_name="$1" - root_domain_name="$1" + + _debug "Identifying DNS root domain for '$original_full_domain_name' that is managed by the Plesk account." # test if the domain as provided is valid for splitting. - if ! _countdots "$root_domain_name"; then + if ! _countdots "$original_full_domain_name"; then _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." return 1 fi @@ -372,6 +372,8 @@ _pleskxml_get_root_domain() { # loop and test if domain, or any parent domain, is managed by Plesk # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain + root_domain_name="$original_full_domain_name" + while true; do _debug "Checking if '$root_domain_name' is managed by the Plesk server..." From a8d670fc0d7abb2efa3cbcc411c79f37ad8c737d Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 19:01:32 +0000 Subject: [PATCH 25/32] Rest of sed -r --- dnsapi/dns_pleskxml.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 2c9a4d56..c2de2184 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -211,16 +211,16 @@ _countdots() { # Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines # $1 - result string from API -# $2 - tag to resplit on (usually "result" or "domain") +# $2 - plain text tag to resplit on (usually "result" or "domain"). NOT REGEX # $3 - basic regex to recognise useful return lines # note: $3 matches via basic NOT extended regex (BRE), as extended regex capabilities not needed at the moment. -# Last line could change to instead, with suitablew ewscaping of ['"/$], +# Last line could change to instead, with suitable escaping of ['"/$], # if future Plesk XML API changes ever require extended regex _api_response_split() { printf '%s' "$1" \ - | sed -r 's/(^ +| +$)//g' \ + | sed 's/^ +//;s/ +$//' \ | tr -d '\n\r' \ - | sed -r "s/<\/?$2>/${NEWLINE}/g" \ + | sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" \ | grep "$3" } @@ -365,7 +365,7 @@ _pleskxml_get_root_domain() { # Output will be one line per known domain, containing 2 tages and a single tag # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. - output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed -r 's/<(\/?)ascii-name>/<\1name>/g' | grep '' | grep '')" + output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" @@ -378,13 +378,13 @@ _pleskxml_get_root_domain() { _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed -r 's/^.*([0-9]+)<\/id>.*$/\1/')" + root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT. # SO WE HANDLE IT AND DON'T PREVENT IT - sub_domain_name="$(_value "$original_full_domain_name" | sed -r "s/\.?${root_domain_name}"'$//')" + sub_domain_name="$(_value "$original_full_domain_name" | sed "s/\.\{0,1\}${root_domain_name}"'$//')" _info "Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." return 0 fi From 4216c9e8f745d76588c8dc8592ade0e020f82d62 Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 19:19:08 +0000 Subject: [PATCH 26/32] rmv spaces --- dnsapi/dns_pleskxml.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index c2de2184..d2a2684a 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -146,7 +146,7 @@ dns_pleskxml_rm() { fi _debug "Got list of DNS TXT records for root domain '$root_domain_name'. Full list is:"'\n'"$reclist" - + _debug "DNS TXT records for host '$fulldomain':"'\n'"$(_value "$reclist" | grep "${fulldomain}.")" recid="$(_value "$reclist" \ From a9726bd52f764f519332783f8002ef8e2c742b5b Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 20:58:51 +0000 Subject: [PATCH 27/32] bugfix --- dnsapi/dns_pleskxml.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index d2a2684a..ad8d5cfd 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -393,7 +393,7 @@ _pleskxml_get_root_domain() { root_domain_name="$(_valuecut 2 1000 "$root_domain_name")" - if ! _countdots "$root_domain_name"; then + if [ _countdots "$root_domain_name" -eq 0 ]; then _debug "No match, and next parent would be a TLD..." _err "Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk." _err "Are you sure that this domain is managed by this Plesk server?" From 38854bd876d11c4bf0f696e23c694a61d2b640ec Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 21:00:34 +0000 Subject: [PATCH 28/32] bugfix --- dnsapi/dns_pleskxml.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index ad8d5cfd..ca64325b 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -393,7 +393,7 @@ _pleskxml_get_root_domain() { root_domain_name="$(_valuecut 2 1000 "$root_domain_name")" - if [ _countdots "$root_domain_name" -eq 0 ]; then + if [ "$(_countdots "$root_domain_name")" -eq 0 ]; then _debug "No match, and next parent would be a TLD..." _err "Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk." _err "Are you sure that this domain is managed by this Plesk server?" From 43011f3bfa2e08807b3e7450d123aabcbd6905a8 Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 21:04:10 +0000 Subject: [PATCH 29/32] enhance --- dnsapi/dns_pleskxml.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index ca64325b..5abe9dd1 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -203,10 +203,10 @@ _valuecut() { printf '%s' "$3" | cut -d . -f "${1}-${2}" } -# Counts '.' present in a domain name +# Counts '.' present in a domain name or other string # $1 = domain name _countdots() { - _value "$1" | tr -dc '.' | wc -c + _value "$1" | tr -dc '.' | wc -c | sed 's/ //g' } # Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines From 05247dc4a4dca564a8d38e223da89556e2e68493 Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 21:09:14 +0000 Subject: [PATCH 30/32] fix --- dnsapi/dns_pleskxml.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 5abe9dd1..b6781c2e 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -347,7 +347,7 @@ _pleskxml_get_root_domain() { # test if the domain as provided is valid for splitting. - if ! _countdots "$original_full_domain_name"; then + if [ "$(_countdots "$original_full_domain_name")" -eq 0 ]; then _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." return 1 fi From 6d0e4bed4b9d9bcb31e445ecadc7c4f741abc6fc Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 23:26:05 +0000 Subject: [PATCH 31/32] remove \n in output messages --- dnsapi/dns_pleskxml.sh | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index b6781c2e..ca07bc4e 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -96,7 +96,8 @@ dns_pleskxml_add() { _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' _err 'This is unexpected: something has gone wrong.' - _err 'The full response was:\n' "$pleskxml_prettyprint_result" + _err 'The full response was:' + _err "$pleskxml_prettyprint_result" return 1 fi @@ -134,7 +135,9 @@ dns_pleskxml_rm() { fi # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) + # Also strip out spaces between tags, redundant and group tags and any tags reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ + | sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s###g;s#<[a-z][^/<>]*/>##g' \ | grep "${root_domain_id}" \ | grep '[0-9]\{1,\}' \ | grep 'TXT' @@ -145,9 +148,8 @@ dns_pleskxml_rm() { return 1 fi - _debug "Got list of DNS TXT records for root domain '$root_domain_name'. Full list is:"'\n'"$reclist" - - _debug "DNS TXT records for host '$fulldomain':"'\n'"$(_value "$reclist" | grep "${fulldomain}.")" + _debug "Got list of DNS TXT records for root domain '$root_domain_name':" + _debug "$reclist" recid="$(_value "$reclist" \ | grep "${fulldomain}." \ @@ -181,7 +183,8 @@ dns_pleskxml_rm() { _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' _err 'This is unexpected: something has gone wrong.' - _err 'The full response was:\n' "$pleskxml_prettyprint_result" + _err 'The full response was:' + _err "$pleskxml_prettyprint_result" return 1 fi @@ -231,7 +234,8 @@ _call_api() { request="$1" errtext='' - _debug 'Entered _call_api(). Calling Plesk XML API with request:\n' "'${request}'" + _debug 'Entered _call_api(). Calling Plesk XML API with request:' + _debug "'$request'" export _H1="HTTP_AUTH_LOGIN: $pleskxml_user" export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass" @@ -239,7 +243,9 @@ _call_api() { export _H4="HTTP_PRETTY_PRINT: true" pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")" pleskxml_retcode="$?" - _debug 'The responses from the Plesk XML server were:\n' "retcode=$pleskxml_retcode. Literal response:"'\n' "'$pleskxml_prettyprint_result'" + _debug 'The responses from the Plesk XML server were:' + _debug "retcode=$pleskxml_retcode. Literal response:" + _debug "'$pleskxml_prettyprint_result'" # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. @@ -277,7 +283,8 @@ _call_api() { fi if [ "$errtext" != "" ]; then - _err 'The error responses received from the Plesk server were:\n' "$errtext" + _err 'The error responses received from the Plesk server were:' + _err "$errtext" else _err "No additional error messages were received back from the Plesk server" fi @@ -318,7 +325,7 @@ _credential_check() { # Test the API is usable, by trying to read the list of managed domains... _call_api "$pleskxml_tplt_get_domains" if [ "$pleskxml_retcode" -ne 0 ]; then - _err '\nFailed to access Plesk XML API.' + _err 'Failed to access Plesk XML API.' _err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again." return 1 fi @@ -367,7 +374,8 @@ _pleskxml_get_root_domain() { output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" - _debug 'Domains managed by Plesk server are (ignore the hacked output):\n' "$output" + _debug 'Domains managed by Plesk server are (ignore the hacked output):' + _debug "$output" # loop and test if domain, or any parent domain, is managed by Plesk # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain From 51cfd996eb8aaa142c9cbe61270bf1c5751f7d1e Mon Sep 17 00:00:00 2001 From: stilez Date: Tue, 5 Nov 2019 23:29:51 +0000 Subject: [PATCH 32/32] rmv space --- dnsapi/dns_pleskxml.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index ca07bc4e..c5d9e544 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -96,7 +96,7 @@ dns_pleskxml_add() { _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' _err 'This is unexpected: something has gone wrong.' - _err 'The full response was:' + _err 'The full response was:' _err "$pleskxml_prettyprint_result" return 1 fi