From 86276ad17b50227b9b9d7f2d72abd7cdf22f19a8 Mon Sep 17 00:00:00 2001 From: Oliver Dick Date: Wed, 1 Aug 2018 16:37:08 +0200 Subject: [PATCH 01/64] added hosting.de DNS Plugin * can be used with API of hosting.de * can also be used with ICANN registrar http.net * needs just API key and endpoint * support wildcard certificates --- dnsapi/dns_hostingde.sh | 110 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 dnsapi/dns_hostingde.sh diff --git a/dnsapi/dns_hostingde.sh b/dnsapi/dns_hostingde.sh new file mode 100644 index 00000000..a6abc428 --- /dev/null +++ b/dnsapi/dns_hostingde.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env sh + +# hosting.de API + +# Values to export: +# export HOSTINGDE_ENDPOINT='https://secure.hosting.de' +# export HOSTINGDE_APIKEY='xxxxx' + + +######## Public functions ##################### + +dns_hostingde_add() { + fulldomain="${1}" + txtvalue="${2}" + _debug "Calling: _hostingde_addRecord() '${fulldomain}' '${txtvalue}'" + _hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_addRecord +} + +dns_hostingde_rm() { + fulldomain="${1}" + txtvalue="${2}" + _debug "Calling: _hostingde_removeRecord() '${fulldomain}' '${txtvalue}'" + _hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_removeRecord +} + +#################### own Private functions below ################################## + +_hostingde_apiKey() { + HOSTINGDE_APIKEY="${HOSTINGDE_APIKEY:-$(_readaccountconf_mutable HOSTINGDE_APIKEY)}" + if [ -z "$HOSTINGDE_APIKEY" ] || [ -z "$HOSTINGDE_ENDPOINT" ]; then + HOSTINGDE_APIKEY="" + HOSTINGDE_ENDPOINT="" + _err "You haven't specified hosting.de API key or endpoint yet." + _err "Please create your key and try again." + return 1 + fi + + _saveaccountconf_mutable HOSTINGDE_APIKEY "$HOSTINGDE_APIKEY" + _saveaccountconf_mutable HOSTINGDE_ENDPOINT "$HOSTINGDE_ENDPOINT" +} + +_hostingde_getZoneConfig() { + _info "Getting ZoneConfig" + curZone="${fulldomain#*.}" + returnCode=1 + while _contains "${curZone}" "\\."; do + curData="{\"filter\":{\"field\":\"zoneName\",\"value\":\"${curZone}\"},\"limit\":1,\"authToken\":\"${HOSTINGDE_APIKEY}\"}" + curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind")" + _debug "Calling zoneConfigsFind: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind'" + _debug "Result of zoneConfigsFind: '$curResult'" + if _contains "${curResult}" '"status": "error"'; then + if _contains "${curResult}" '"code": 10109'; then + _err "The API-Key is invalid or could not be found" + else + _err "UNKNOWN API ERROR" + fi + returnCode=1 + break; + fi + if _contains "${curResult}" '"totalEntries": 1'; then + _info "Retrieved zone data." + _debug "Zone data: '${curResult}'" + + # read ZoneConfigId for later update + zoneConfigId=$(echo "${curResult}" | _egrep_o '"id":.*' | cut -d ':' -f 2 | cut -d '"' -f 2) + _debug "zoneConfigId '${zoneConfigId}'" + returnCode=0 + break + fi + curZone="${curZone#*.}" + done + if [ $returnCode -ne 0 ]; then + _info "ZoneEnd reached, Zone ${curZone} not found in hosting.de API" + fi + return $returnCode +} + +_hostingde_addRecord() { + _info "Adding record to zone" + curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}" + curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")" + _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'" + _debug "Result of zoneUpdate: '$curResult'" + if _contains "${curResult}" '"status": "error"'; then + if _contains "${curResult}" '"code": 10109'; then + _err "The API-Key is invalid or could not be found" + else + _err "UNKNOWN API ERROR" + fi + return 1 + fi + return 0 +} + +_hostingde_removeRecord() { + _info "Removing record from zone" + curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}" + curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")" + _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'" + _debug "Result of zoneUpdate: '$curResult'" + if _contains "${curResult}" '"status": "error"'; then + if _contains "${curResult}" '"code": 10109'; then + _err "The API-Key is invalid or could not be found" + else + _err "UNKNOWN API ERROR" + fi + return 1 + fi + return 0 +} From 5494e88e08f22400ed7fabc3c9f445eca85334e5 Mon Sep 17 00:00:00 2001 From: Oliver Dick Date: Wed, 1 Aug 2018 17:00:22 +0200 Subject: [PATCH 02/64] making shfmt happy --- dnsapi/dns_hostingde.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_hostingde.sh b/dnsapi/dns_hostingde.sh index a6abc428..39bcfb63 100644 --- a/dnsapi/dns_hostingde.sh +++ b/dnsapi/dns_hostingde.sh @@ -6,7 +6,6 @@ # export HOSTINGDE_ENDPOINT='https://secure.hosting.de' # export HOSTINGDE_APIKEY='xxxxx' - ######## Public functions ##################### dns_hostingde_add() { @@ -55,7 +54,7 @@ _hostingde_getZoneConfig() { _err "UNKNOWN API ERROR" fi returnCode=1 - break; + break fi if _contains "${curResult}" '"totalEntries": 1'; then _info "Retrieved zone data." @@ -70,7 +69,7 @@ _hostingde_getZoneConfig() { curZone="${curZone#*.}" done if [ $returnCode -ne 0 ]; then - _info "ZoneEnd reached, Zone ${curZone} not found in hosting.de API" + _info "ZoneEnd reached, Zone ${curZone} not found in hosting.de API" fi return $returnCode } From 4162975f9f2db76fbc5fcfbdaa3bea5f0df6e9cc Mon Sep 17 00:00:00 2001 From: Oliver Dick Date: Thu, 2 Aug 2018 15:43:40 +0200 Subject: [PATCH 03/64] added hosting.de API to README's --- README.md | 1 + dnsapi/README.md | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/README.md b/README.md index c8bebc6f..614476a0 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. hosting.de (https://www.hosting.de) And: diff --git a/dnsapi/README.md b/dnsapi/README.md index 1f394f92..bce0ffef 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -897,6 +897,29 @@ 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 +## 48. Use hosting.de API + +Create an API key in your hosting.de account here: https://secure.hosting.de + +The key needs the following rights: +- DNS_ZONES_EDIT +- DNS_ZONES_LIST + +Set your API Key and endpoint: + +``` +export HOSTINGDE_APIKEY="xxx" +export HOSTINGDE_ENDPOINT="https://secure.hosting.de" +``` + +The plugin can also be used for the http.net API. http.net customers have to set endpoint to https://partner.http.net. + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_hostingde -d example.com -d *.example.com +``` + +The hosting.de API key and endpoint will be saved in `~/.acme.sh/account.conf` and will be reused when needed. # Use custom API If your API is not supported yet, you can write your own DNS API. From ed95509a4f938737957c641a9c5257bda55a1540 Mon Sep 17 00:00:00 2001 From: Oliver Dick Date: Thu, 2 Aug 2018 15:47:02 +0200 Subject: [PATCH 04/64] hosting.de API keys can contain special chars, so using simple quotes --- dnsapi/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index bce0ffef..01192b13 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -908,8 +908,8 @@ The key needs the following rights: Set your API Key and endpoint: ``` -export HOSTINGDE_APIKEY="xxx" -export HOSTINGDE_ENDPOINT="https://secure.hosting.de" +export HOSTINGDE_APIKEY='xxx' +export HOSTINGDE_ENDPOINT='https://secure.hosting.de' ``` The plugin can also be used for the http.net API. http.net customers have to set endpoint to https://partner.http.net. From 5b7cac100220f4d6354d400d655c2e87d9023999 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Wed, 12 Sep 2018 05:34:56 +0200 Subject: [PATCH 05/64] [FIX] Delete all occurrences of TXT key, dont fail if there is more than one, [FIX] Respect pagination on domain listing, before only the first page was loaded --- dnsapi/dns_dgon.sh | 159 ++++++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 67 deletions(-) diff --git a/dnsapi/dns_dgon.sh b/dnsapi/dns_dgon.sh index 5d38ef76..e92c7809 100755 --- a/dnsapi/dns_dgon.sh +++ b/dnsapi/dns_dgon.sh @@ -104,48 +104,60 @@ dns_dgon_rm() { ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} GURL="https://api.digitalocean.com/v2/domains/$_domain/records" - ## while we dont have a record ID we keep going - while [ -z "$record" ]; do + ## Get all the matching records + while [ true ]; do ## 1) get the URL ## the create request - get ## args: URL, [onlyheader, timeout] domain_list="$(_get "$GURL")" - ## 2) find record - ## check for what we are looing for: "type":"A","name":"$_sub_domain" - record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" - ## 3) check record and get next page - if [ -z "$record" ]; then - ## find the next page if we dont have a match - nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")" - if [ -z "$nextpage" ]; then - _err "no record and no nextpage in digital ocean DNS removal" - return 1 - fi - _debug2 nextpage "$nextpage" - GURL="$nextpage" + + ## check response + if [ "$?" != "0" ]; then + _err "error in domain_list response: $domain_list" + return 1 fi - ## we break out of the loop when we have a record + _debug2 domain_list "$domain_list" + + ## 2) find records + ## check for what we are looking for: "type":"A","name":"$_sub_domain" + record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" + + if [ ! -z "$record" ]; then + + ## we found records + rec_ids="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" + _debug rec_ids "$rec_ids" + if [ ! -z "$rec_ids" ]; then + echo "$rec_ids" | while IFS= read -r rec_id ; do + ## delete the record + ## delete URL for removing the one we dont want + DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" + + ## the create request - delete + ## args: BODY, URL, [need64, httpmethod] + response="$(_post "" "$DURL" "" "DELETE")" + + ## check response (sort of) + if [ "$?" != "0" ]; then + _err "error in remove response: $response" + return 1 + fi + _debug2 response "$response" + + done + fi + fi + + ## 3) find the next page + nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")" + if [ -z "$nextpage" ]; then + break + fi + _debug2 nextpage "$nextpage" + GURL="$nextpage" + done - ## we found the record - rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" - _debug rec_id "$rec_id" - - ## delete the record - ## delete URL for removing the one we dont want - DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" - - ## the create request - delete - ## args: BODY, URL, [need64, httpmethod] - response="$(_post "" "$DURL" "" "DELETE")" - - ## check response (sort of) - if [ "$?" != "0" ]; then - _err "error in remove response: $response" - return 1 - fi - _debug2 response "$response" - ## finished correctly return 0 } @@ -178,44 +190,57 @@ _get_base_domain() { export _H2="Authorization: Bearer $DO_API_KEY" _debug DO_API_KEY "$DO_API_KEY" ## get URL for the list of domains - ## havent seen this request paginated, tested with 18 domains (more requires manual requests with DO) + ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} DOMURL="https://api.digitalocean.com/v2/domains" - ## get the domain list (DO gives basically a full XFER!) - domain_list="$(_get "$DOMURL")" + ## while we dont have a matching domain we keep going + while [ -z "$found" ]; do + ## get the domain list (current page) + domain_list="$(_get "$DOMURL")" - ## check response - if [ "$?" != "0" ]; then - _err "error in domain_list response: $domain_list" - return 1 - fi - _debug2 domain_list "$domain_list" - - ## for each shortening of our $fulldomain, check if it exists in the $domain_list - ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge" - i=2 - while [ $i -gt 0 ]; do - ## get next longest domain - _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") - ## check we got something back from our cut (or are we at the end) - if [ -z "$_domain" ]; then - ## we got to the end of the domain - invalid domain - _err "domain not found in DigitalOcean account" + ## check response + if [ "$?" != "0" ]; then + _err "error in domain_list response: $domain_list" return 1 fi - ## we got part of a domain back - grep it out - found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" - ## check if it exists - if [ ! -z "$found" ]; then - ## exists - exit loop returning the parts - sub_point=$(_math $i - 1) - _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - return 0 + _debug2 domain_list "$domain_list" + + ## for each shortening of our $fulldomain, check if it exists in the $domain_list + ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge" + i=2 + while [ $i -gt 0 ]; do + ## get next longest domain + _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") + ## check we got something back from our cut (or are we at the end) + if [ -z "$_domain" ]; then + break + fi + ## we got part of a domain back - grep it out + found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" + ## check if it exists + if [ ! -z "$found" ]; then + ## exists - exit loop returning the parts + sub_point=$(_math $i - 1) + _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") + _debug _domain "$_domain" + _debug _sub_domain "$_sub_domain" + return 0 + fi + ## increment cut point $i + i=$(_math $i + 1) + done + + if [ -z "$found" ]; then + ## find the next page if we dont have a match + nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")" + if [ -z "$nextpage" ]; then + _err "no record and no nextpage in digital ocean DNS removal" + return 1 + fi + _debug2 nextpage "$nextpage" + DOMURL="$nextpage" fi - ## increment cut point $i - i=$(_math $i + 1) + done ## we went through the entire domain zone list and dint find one that matched From 4a18c45e4f34bcc05ecc3891af9ac72f5df2b68f Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Wed, 12 Sep 2018 05:46:51 +0200 Subject: [PATCH 06/64] fixup! [FIX] Delete all occurrences of TXT key, dont fail if there is more than one, [FIX] Respect pagination on domain listing, before only the first page was loaded --- dnsapi/dns_dgon.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_dgon.sh b/dnsapi/dns_dgon.sh index e92c7809..24e1a9f2 100755 --- a/dnsapi/dns_dgon.sh +++ b/dnsapi/dns_dgon.sh @@ -105,7 +105,7 @@ dns_dgon_rm() { GURL="https://api.digitalocean.com/v2/domains/$_domain/records" ## Get all the matching records - while [ true ]; do + while true; do ## 1) get the URL ## the create request - get ## args: URL, [onlyheader, timeout] @@ -128,7 +128,7 @@ dns_dgon_rm() { rec_ids="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" _debug rec_ids "$rec_ids" if [ ! -z "$rec_ids" ]; then - echo "$rec_ids" | while IFS= read -r rec_id ; do + echo "$rec_ids" | while IFS= read -r rec_id; do ## delete the record ## delete URL for removing the one we dont want DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" From 4c1f70af4b27781a3f5055328f704a268fb8a5d4 Mon Sep 17 00:00:00 2001 From: evoadmin Date: Tue, 2 Oct 2018 10:43:25 +0300 Subject: [PATCH 07/64] Update dns_he.sh If you have a password with special char it will fail at Remove record --- dnsapi/dns_he.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh index da4a1b81..df00c746 100755 --- a/dnsapi/dns_he.sh +++ b/dnsapi/dns_he.sh @@ -92,7 +92,9 @@ dns_he_rm() { return 1 fi # Remove the record - body="email=${HE_Username}&pass=${HE_Password}" + username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)" + password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)" + body="email=${username_encoded}&pass=${password_encoded}" body="$body&menu=edit_zone" body="$body&hosted_dns_zoneid=$_zone_id" body="$body&hosted_dns_recordid=$_record_id" From fd536d373ebd65eecf8ce8e2f760ef186e5ce74f Mon Sep 17 00:00:00 2001 From: Phil Ross Date: Thu, 18 Oct 2018 17:12:06 +0100 Subject: [PATCH 08/64] Skip aliases of already verified domains. When issuing a two-domain certificate using a different alias for each domain, if the first domain is already verified, verification for the second domain would be attempted (unsuccessfully) using the alias of the first domain. Increment the alias index when skipping verified domains so that the correct alias will be used for subsequent domains. --- acme.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/acme.sh b/acme.sh index ee238274..8ae6600f 100755 --- a/acme.sh +++ b/acme.sh @@ -2925,6 +2925,7 @@ _clearupdns() { _debug txt "$txt" if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then _debug "$d is already verified, skip $vtype." + _alias_index="$(_math "$_alias_index" + 1)" continue fi @@ -3775,6 +3776,7 @@ $_authorizations_map" _debug d "$d" if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then _debug "$d is already verified, skip $vtype." + _alias_index="$(_math "$_alias_index" + 1)" continue fi From 46b3a9158c80ab5f1c76437ed1fb9fad2c13e96a Mon Sep 17 00:00:00 2001 From: LLeny <5269958+LLeny@users.noreply.github.com> Date: Sun, 21 Oct 2018 18:17:23 +0800 Subject: [PATCH 09/64] Fixes Neilpang/acme.sh#1888 --- dnsapi/dns_namecheap.sh | 64 +++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh index 7089c2d0..a6651be6 100755 --- a/dnsapi/dns_namecheap.sh +++ b/dnsapi/dns_namecheap.sh @@ -199,9 +199,12 @@ _namecheap_check_config() { _set_namecheap_TXT() { subdomain=$2 txt=$3 - tld=$(echo "$1" | cut -d '.' -f 2) - sld=$(echo "$1" | cut -d '.' -f 1) - request="namecheap.domains.dns.getHosts&SLD=$sld&TLD=$tld" + + if ! _namecheap_set_tld_sld "$1"; then + return 1 + fi + + request="namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}" if ! _namecheap_post "$request"; then _err "$error" @@ -231,7 +234,7 @@ EOT _debug hostrequestfinal "$_hostrequest" - request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}" + request="namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}" if ! _namecheap_post "$request"; then _err "$error" @@ -244,9 +247,12 @@ EOT _del_namecheap_TXT() { subdomain=$2 txt=$3 - tld=$(echo "$1" | cut -d '.' -f 2) - sld=$(echo "$1" | cut -d '.' -f 1) - request="namecheap.domains.dns.getHosts&SLD=$sld&TLD=$tld" + + if ! _namecheap_set_tld_sld "$1"; then + return 1 + fi + + request="namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}" if ! _namecheap_post "$request"; then _err "$error" @@ -286,7 +292,7 @@ EOT _debug hostrequestfinal "$_hostrequest" - request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}" + request="namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}" if ! _namecheap_post "$request"; then _err "$error" @@ -306,3 +312,45 @@ _namecheap_add_host() { _hostindex=$(_math "$_hostindex" + 1) _hostrequest=$(printf '%s&HostName%d=%s&RecordType%d=%s&Address%d=%s&MXPref%d=%d&TTL%d=%d' "$_hostrequest" "$_hostindex" "$1" "$_hostindex" "$2" "$_hostindex" "$3" "$_hostindex" "$4" "$_hostindex" "$5") } + +_namecheap_set_tld_sld() { + domain=$1 + _tld="" + _sld="" + + i=2 + + while true; do + + _tld=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug tld "$_tld" + + if [ -z "$_tld" ]; then + _debug "invalid tld" + return 1 + fi + + j=$(_math "$i" - 1) + + _sld=$(printf "%s" "$domain" | cut -d . -f 1-"$j") + _debug sld "$_sld" + + if [ -z "$_sld" ]; then + _debug "invalid sld" + return 1 + fi + + request="namecheap.domains.dns.getHosts&SLD=$_sld&TLD=$_tld" + + if ! _namecheap_post "$request"; then + _debug "sld($_sld)/tld($_tld) not found" + else + _debug "sld($_sld)/tld($_tld) found" + return 0 + fi + + i=$(_math "$i" + 1) + + done + +} From a894b7cc9b374d5588b346a9afde1282b5d067d3 Mon Sep 17 00:00:00 2001 From: hebbet Date: Wed, 24 Oct 2018 16:33:02 +0200 Subject: [PATCH 10/64] add link to cloudflare profil for api key --- dnsapi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 2cecfa5a..71ba53b8 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -6,7 +6,7 @@ https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode ## 1. Use CloudFlare domain API to automatically issue cert -First you need to login to your CloudFlare account to get your API key. +First you need to login to your CloudFlare account to get your [API key](https://dash.cloudflare.com/profile). ``` export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" From 9672c6b885a100a9c83ce1651591271a5f3e2b2a Mon Sep 17 00:00:00 2001 From: neilpang Date: Tue, 30 Oct 2018 22:14:49 +0800 Subject: [PATCH 11/64] fix https://github.com/Neilpang/acme.sh/issues/1905 --- acme.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index ee238274..b59332b9 100755 --- a/acme.sh +++ b/acme.sh @@ -4602,7 +4602,8 @@ deploy() { _initpath "$_d" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then - _err "Domain is not valid:'$_d'" + _err "The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install." + _err "Can not find path:'$DOMAIN_PATH'" return 1 fi @@ -4629,7 +4630,8 @@ installcert() { _initpath "$_main_domain" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then - _err "Domain is not valid:'$_main_domain'" + _err "The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install." + _err "Can not find path:'$DOMAIN_PATH'" return 1 fi From 7903fcb48c3b90bced87b187a05c538ec7c74fe8 Mon Sep 17 00:00:00 2001 From: neilpang Date: Tue, 30 Oct 2018 22:50:44 +0800 Subject: [PATCH 12/64] fix typo --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index b59332b9..7944d5df 100755 --- a/acme.sh +++ b/acme.sh @@ -5476,7 +5476,7 @@ Parameters: --log-level 1|2 Specifies the log level, default is 1. --syslog [0|3|6|7] Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug. - These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert: + These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert: --cert-file After issue/renew, the cert will be copied to this path. --key-file After issue/renew, the key will be copied to this path. From 12956679e73e615882fc556518fba00c2d07baf4 Mon Sep 17 00:00:00 2001 From: Joakim Lemb Date: Mon, 5 Nov 2018 14:52:26 +0100 Subject: [PATCH 13/64] Added top URI parameter --- dnsapi/dns_azure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh index c6893a0c..ae8aa1ca 100644 --- a/dnsapi/dns_azure.sh +++ b/dnsapi/dns_azure.sh @@ -316,7 +316,7 @@ _get_root() { ## (ZoneListResult with continuation token for the next page of results) ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways ## - _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken" + _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?\$top=500&api-version=2017-09-01" "" "$accesstoken" # Find matching domain name is Json response while true; do h=$(printf "%s" "$domain" | cut -d . -f $i-100) From 4b581f37203a8dea1c7c1ef5d25322fed49bb3e8 Mon Sep 17 00:00:00 2001 From: pavelaks Date: Sat, 10 Nov 2018 12:10:06 +0300 Subject: [PATCH 14/64] Update README.md Fix VSCALE example (add export before VSCALE_API_KEY) --- dnsapi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 2cecfa5a..9413925b 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -454,7 +454,7 @@ The `Infoblox_Creds` and `Infoblox_Server` will be saved in `~/.acme.sh/account. First you need to create/obtain API tokens on your [settings panel](https://vscale.io/panel/settings/tokens/). ``` -VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" +export VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" ``` Ok, let's issue a cert now: From 552710ac2a2e5dcb85d31f3d3d4ed5c2a0dbd5ec Mon Sep 17 00:00:00 2001 From: nakermann1973 <35577878+nakermann1973@users.noreply.github.com> Date: Tue, 13 Nov 2018 10:15:38 +0100 Subject: [PATCH 15/64] Add missing bind-tools package The bind_tools package is required for dns_nsupdate to work --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 5a64c720..c1a2199b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ RUN apk update -f \ && apk --no-cache add -f \ openssl \ coreutils \ + bind-tools \ curl \ socat \ && rm -rf /var/cache/apk/* From 5fee82ce39e70a301cece87185762f415489b258 Mon Sep 17 00:00:00 2001 From: Thomas Rohlik Date: Mon, 19 Nov 2018 16:09:32 +0100 Subject: [PATCH 16/64] Fix dot Very important commit :1st_place_medal: --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index ee238274..0930effd 100755 --- a/acme.sh +++ b/acme.sh @@ -5485,7 +5485,7 @@ Parameters: --server SERVER ACME Directory Resource URI. (default: https://acme-v01.api.letsencrypt.org/directory) --accountconf Specifies a customized account config file. - --home Specifies the home dir for $PROJECT_NAME . + --home Specifies the home dir for $PROJECT_NAME. --cert-home Specifies the home dir to save all the certs, only valid for '--install' command. --config-home Specifies the home dir to save all the configurations. --useragent Specifies the user agent string. it will be saved for future use too. From 137dc1eac0bdc6f664c7fbc3aae9b1cce4c58a85 Mon Sep 17 00:00:00 2001 From: neilpang Date: Fri, 23 Nov 2018 22:53:02 +0800 Subject: [PATCH 17/64] fix https://github.com/Neilpang/acme.sh/issues/1912 --- acme.sh | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/acme.sh b/acme.sh index c7138c1b..502b6950 100755 --- a/acme.sh +++ b/acme.sh @@ -124,23 +124,19 @@ if [ -t 1 ]; then fi __green() { - if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then - printf '\033[1;31;32m' + if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then + printf '\033[1;31;32m%b\033[0m' "$1" + return fi printf -- "%b" "$1" - if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then - printf '\033[0m' - fi } __red() { - if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then - printf '\033[1;31;40m' + if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then + printf '\033[1;31;40m%b\033[0m' "$1" + return fi printf -- "%b" "$1" - if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then - printf '\033[0m' - fi } _printargs() { From a6f2110141011c950c6709b083331f0dd933de9b Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Sat, 24 Nov 2018 01:58:46 +0900 Subject: [PATCH 18/64] Add DNS API support for MyDNS.JP --- dnsapi/README.md | 16 ++++ dnsapi/dns_mydnsjp.sh | 210 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 dnsapi/dns_mydnsjp.sh diff --git a/dnsapi/README.md b/dnsapi/README.md index 2cecfa5a..7362eb2c 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -1014,6 +1014,22 @@ Now you can issue a certificate. acme.sh --issue --dns dns_namecheap -d example.com -d *.example.com ``` +## 54. Use MyDNS.JP API + +First, register to MyDNS.JP and get MasterID and Password. + +``` +export MYDNSJP_MasterID=MasterID +export MYDNSJP_Password=Password +``` + +To issue a certificate: + +``` +acme.sh --issue --dns dns_mydnsjp -d example.com -d www.example.com +``` +The `MYDNSJP_MasterID` and `MYDNSJP_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_mydnsjp.sh b/dnsapi/dns_mydnsjp.sh new file mode 100644 index 00000000..d421329c --- /dev/null +++ b/dnsapi/dns_mydnsjp.sh @@ -0,0 +1,210 @@ +#!/usr/bin/env sh + +#Here is a api script for MyDNS.JP. +#This file name is "dns_mydnsjp.sh" +#So, here must be a method dns_mydnsjp_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: epgdatacapbon +#Report Bugs here: https://github.com/epgdatacapbon/acme.sh +# +######## Public functions ##################### + +# Export MyDNS.JP MasterID and Password in following variables... +# MYDNSJP_MasterID=MasterID +# MYDNSJP_Password=Password + +MYDNSJP_API="http://www.mydns.jp" + +#Usage: dns_mydnsjp_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_mydnsjp_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Using mydnsjp" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + # Load the credentials from the account conf file + MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}" + MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}" + if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then + MYDNSJP_MasterID="" + MYDNSJP_Password="" + _err "You don't specify mydnsjp api MasterID and Password yet." + _err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again." + return 1 + fi + + # Save the credentials to the account conf file + _saveaccountconf_mutable MYDNSJP_MasterID "$MYDNSJP_MasterID" + _saveaccountconf_mutable MYDNSJP_Password "$MYDNSJP_Password" + + _debug "First detect the root zone." + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if _mydnsjp_api "REGIST" "$_domain" "$txtvalue"; then + if printf -- "%s" "$response" | grep "OK." >/dev/null; then + _info "Added, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + + return 1 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_mydnsjp_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Removing TXT record" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + # Load the credentials from the account conf file + MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}" + MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}" + if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then + MYDNSJP_MasterID="" + MYDNSJP_Password="" + _err "You don't specify mydnsjp api MasterID and Password yet." + _err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again." + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if _mydnsjp_api "DELETE" "$_domain" "$txtvalue"; then + if printf -- "%s" "$response" | grep "OK." >/dev/null; then + _info "Deleted, OK" + return 0 + else + _err "Delete txt record error." + return 1 + fi + fi + _err "Delete txt record error." + + return 1 +} + +#################### Private functions below ################################## +# _acme-challenge.www.domain.com +# returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + fulldomain=$1 + i=2 + p=1 + + # Get the root domain + _mydnsjp_retrieve_domain + if [ "$?" != "0" ]; then + # not valid + return 1 + fi + + while true; do + _domain=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) + + if [ -z "$_domain" ]; then + # not valid + return 1 + fi + + if [ "$_domain" = "$_root_domain" ]; then + _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-$p) + return 0 + fi + + p=$i + i=$(_math "$i" + 1) + done + + return 1 +} + +# Retrieve the root domain +# returns 0 success +_mydnsjp_retrieve_domain() { + _debug "Login to MyDNS.JP" + + response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")" + cookie="$(grep '^Set-Cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)" + + # If cookies is not empty then logon successful + if [ -z "$cookie" ]; then + _err "Fail to get a cookie." + return 1 + fi + + _debug "Retrieve DOMAIN INFO page" + + export _H1="Cookie:${cookie}" + + response="$(_get "$MYDNSJP_API/?MENU=300")" + + if [ "$?" != "0" ]; then + _err "Fail to retrieve DOMAIN INFO." + return 1 + fi + + _root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/') + + # Logout + response="$(_get "$MYDNSJP_API/?MENU=090")" + + _debug _root_domain "$_root_domain" + + if [ -z "$_root_domain" ]; then + _err "Fail to get the root domain." + return 1 + fi + + return 0 +} + +_mydnsjp_api() { + cmd=$1 + domain=$2 + txtvalue=$3 + + # Base64 encode the credentials + credentials=$(printf "%s:%s" "$MYDNSJP_MasterID" "$MYDNSJP_Password" | _base64) + + # Construct the HTTP Authorization header + export _H1="Content-Type: application/x-www-form-urlencoded" + export _H2="Authorization: Basic ${credentials}" + + response="$(_post "CERTBOT_DOMAIN=$domain&CERTBOT_VALIDATION=$txtvalue&EDIT_CMD=$cmd" "$MYDNSJP_API/directedit.html")" + + if [ "$?" != "0" ]; then + _err "error $domain" + return 1 + fi + + _debug2 response "$response" + + return 0 +} From 14ad5955b58a48720da0b70bb902900029f9b3f8 Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Sat, 24 Nov 2018 01:58:46 +0900 Subject: [PATCH 19/64] Add DNS API support for MyDNS.JP --- dnsapi/README.md | 16 ++++ dnsapi/dns_mydnsjp.sh | 210 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 dnsapi/dns_mydnsjp.sh diff --git a/dnsapi/README.md b/dnsapi/README.md index 02e8fd82..f126568b 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -1014,6 +1014,22 @@ Now you can issue a certificate. acme.sh --issue --dns dns_namecheap -d example.com -d *.example.com ``` +## 54. Use MyDNS.JP API + +First, register to MyDNS.JP and get MasterID and Password. + +``` +export MYDNSJP_MasterID=MasterID +export MYDNSJP_Password=Password +``` + +To issue a certificate: + +``` +acme.sh --issue --dns dns_mydnsjp -d example.com -d www.example.com +``` +The `MYDNSJP_MasterID` and `MYDNSJP_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_mydnsjp.sh b/dnsapi/dns_mydnsjp.sh new file mode 100644 index 00000000..d421329c --- /dev/null +++ b/dnsapi/dns_mydnsjp.sh @@ -0,0 +1,210 @@ +#!/usr/bin/env sh + +#Here is a api script for MyDNS.JP. +#This file name is "dns_mydnsjp.sh" +#So, here must be a method dns_mydnsjp_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: epgdatacapbon +#Report Bugs here: https://github.com/epgdatacapbon/acme.sh +# +######## Public functions ##################### + +# Export MyDNS.JP MasterID and Password in following variables... +# MYDNSJP_MasterID=MasterID +# MYDNSJP_Password=Password + +MYDNSJP_API="http://www.mydns.jp" + +#Usage: dns_mydnsjp_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_mydnsjp_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Using mydnsjp" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + # Load the credentials from the account conf file + MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}" + MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}" + if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then + MYDNSJP_MasterID="" + MYDNSJP_Password="" + _err "You don't specify mydnsjp api MasterID and Password yet." + _err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again." + return 1 + fi + + # Save the credentials to the account conf file + _saveaccountconf_mutable MYDNSJP_MasterID "$MYDNSJP_MasterID" + _saveaccountconf_mutable MYDNSJP_Password "$MYDNSJP_Password" + + _debug "First detect the root zone." + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if _mydnsjp_api "REGIST" "$_domain" "$txtvalue"; then + if printf -- "%s" "$response" | grep "OK." >/dev/null; then + _info "Added, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + + return 1 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_mydnsjp_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Removing TXT record" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + # Load the credentials from the account conf file + MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}" + MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}" + if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then + MYDNSJP_MasterID="" + MYDNSJP_Password="" + _err "You don't specify mydnsjp api MasterID and Password yet." + _err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again." + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if _mydnsjp_api "DELETE" "$_domain" "$txtvalue"; then + if printf -- "%s" "$response" | grep "OK." >/dev/null; then + _info "Deleted, OK" + return 0 + else + _err "Delete txt record error." + return 1 + fi + fi + _err "Delete txt record error." + + return 1 +} + +#################### Private functions below ################################## +# _acme-challenge.www.domain.com +# returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + fulldomain=$1 + i=2 + p=1 + + # Get the root domain + _mydnsjp_retrieve_domain + if [ "$?" != "0" ]; then + # not valid + return 1 + fi + + while true; do + _domain=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) + + if [ -z "$_domain" ]; then + # not valid + return 1 + fi + + if [ "$_domain" = "$_root_domain" ]; then + _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-$p) + return 0 + fi + + p=$i + i=$(_math "$i" + 1) + done + + return 1 +} + +# Retrieve the root domain +# returns 0 success +_mydnsjp_retrieve_domain() { + _debug "Login to MyDNS.JP" + + response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")" + cookie="$(grep '^Set-Cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)" + + # If cookies is not empty then logon successful + if [ -z "$cookie" ]; then + _err "Fail to get a cookie." + return 1 + fi + + _debug "Retrieve DOMAIN INFO page" + + export _H1="Cookie:${cookie}" + + response="$(_get "$MYDNSJP_API/?MENU=300")" + + if [ "$?" != "0" ]; then + _err "Fail to retrieve DOMAIN INFO." + return 1 + fi + + _root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/') + + # Logout + response="$(_get "$MYDNSJP_API/?MENU=090")" + + _debug _root_domain "$_root_domain" + + if [ -z "$_root_domain" ]; then + _err "Fail to get the root domain." + return 1 + fi + + return 0 +} + +_mydnsjp_api() { + cmd=$1 + domain=$2 + txtvalue=$3 + + # Base64 encode the credentials + credentials=$(printf "%s:%s" "$MYDNSJP_MasterID" "$MYDNSJP_Password" | _base64) + + # Construct the HTTP Authorization header + export _H1="Content-Type: application/x-www-form-urlencoded" + export _H2="Authorization: Basic ${credentials}" + + response="$(_post "CERTBOT_DOMAIN=$domain&CERTBOT_VALIDATION=$txtvalue&EDIT_CMD=$cmd" "$MYDNSJP_API/directedit.html")" + + if [ "$?" != "0" ]; then + _err "error $domain" + return 1 + fi + + _debug2 response "$response" + + return 0 +} From d55c64c83891e06fef9301c3bbbad54b36de27b2 Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Sat, 24 Nov 2018 14:07:50 +0900 Subject: [PATCH 20/64] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b9a5cc59..e0f601d3 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,7 @@ You don't have to do anything manually! 1. netcup DNS API (https://www.netcup.de) 1. GratisDNS.dk (https://gratisdns.dk) 1. Namecheap API (https://www.namecheap.com/) +1. MyDNS.JP API (https://www.mydns.jp/) And: From be5085f2052321f0fa1887bd7be7b3ca0de52aa2 Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Sun, 25 Nov 2018 18:14:52 +0900 Subject: [PATCH 21/64] Increase security using https for MyDNS.JP API --- dnsapi/dns_mydnsjp.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 dnsapi/dns_mydnsjp.sh diff --git a/dnsapi/dns_mydnsjp.sh b/dnsapi/dns_mydnsjp.sh old mode 100644 new mode 100755 index d421329c..aab2aabf --- a/dnsapi/dns_mydnsjp.sh +++ b/dnsapi/dns_mydnsjp.sh @@ -15,7 +15,7 @@ # MYDNSJP_MasterID=MasterID # MYDNSJP_Password=Password -MYDNSJP_API="http://www.mydns.jp" +MYDNSJP_API="https://www.mydns.jp" #Usage: dns_mydnsjp_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_mydnsjp_add() { @@ -151,7 +151,7 @@ _mydnsjp_retrieve_domain() { _debug "Login to MyDNS.JP" response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")" - cookie="$(grep '^Set-Cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)" + cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)" # If cookies is not empty then logon successful if [ -z "$cookie" ]; then From 7917aa2a7c7012d2d0ee2e5d14924dbee801a3b5 Mon Sep 17 00:00:00 2001 From: neilpang Date: Sun, 2 Dec 2018 19:37:35 +0800 Subject: [PATCH 22/64] fix https://github.com/Neilpang/acme.sh/issues/1941 cache dns zones response --- dnsapi/dns_cf.sh | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index 3595b9b0..cbebb03a 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -34,6 +34,9 @@ dns_cf_add() { _saveaccountconf_mutable CF_Key "$CF_Key" _saveaccountconf_mutable CF_Email "$CF_Email" + _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '@.' '__')" + _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" + _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" @@ -58,9 +61,12 @@ dns_cf_add() { # if [ "$count" = "0" ]; then _info "Adding record" if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then - if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then + if _contains "$response" "$fulldomain"; then _info "Added, OK" return 0 + elif _contains "$response" "The record already exists"; then + _info "Already exists, OK" + return 0 else _err "Add txt record error." return 1 @@ -99,11 +105,16 @@ dns_cf_rm() { return 1 fi + _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '@.' '__')" + _debug "First detect the root zone" if ! _get_root "$fulldomain"; then + _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" _err "invalid domain" return 1 fi + _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" + _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" @@ -143,6 +154,21 @@ dns_cf_rm() { # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { + + _cf_zones="$(_readdomainconf $_DOMAIN_CF_ZONES_CACHE_NAME_)" + _debug2 "_cf_zones" "$_cf_zones" + if [ -z "$_cf_zones" ]; then + _debug "$_DOMAIN_CF_ZONES_CACHE_NAME_ is none, so get it." + if ! _cf_rest GET "zones"; then + return 1 + fi + _cf_zones="$response" + _savedomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" "$(echo "$_cf_zones" | _base64)" + else + _debug "$_DOMAIN_CF_ZONES_CACHE_NAME_ found" + _cf_zones="$(echo "$_cf_zones" | _dbase64)" + fi + domain=$1 i=2 p=1 @@ -154,12 +180,8 @@ _get_root() { return 1 fi - if ! _cf_rest GET "zones?name=$h"; then - return 1 - fi - - if _contains "$response" "\"name\":\"$h\"" >/dev/null; then - _domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") + if _contains "$_cf_zones" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(printf "%s\n" "$_cf_zones" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h From 9a27b389765ac6d7a256333e9e1f6fe3c4b92e08 Mon Sep 17 00:00:00 2001 From: "Aaron W. Swenson" Date: Sun, 2 Dec 2018 11:18:41 -0500 Subject: [PATCH 23/64] Update Linode API to v4 Linode API has made breaking changes that are resolved by this update. No user action is required. Additionally, related README.md entry updated to include new cloud manager interface. --- dnsapi/README.md | 13 +++++++++++-- dnsapi/dns_linode.sh | 39 ++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index bb0e89d0..5642aa4e 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -264,9 +264,18 @@ when needed. ## 14. Use Linode domain API First you need to login to your Linode account to get your API Key. -[https://manager.linode.com/profile/api](https://manager.linode.com/profile/api) -Then add an API key with label *ACME* and copy the new key. + * [Classic Manager](https://manager.linode.com/profile/api) + + Under "Add an API key", Give the new key a "Label" (we recommend *ACME*), + set the expiry to never, "Create API Key", and copy the new key into the `LINODE_API_KEY` command + below. + + * [Cloud Manager](https://cloud.linode.com/profile/tokens) + + Click on "Add a Personal Access Token". Give the new key a "Label" (we + recommend *ACME*), give it Read/Write access to "Domains". "Submit", and + copy the new key into the `LINODE_API_KEY` command below. ```sh export LINODE_API_KEY="..." diff --git a/dnsapi/dns_linode.sh b/dnsapi/dns_linode.sh index ead5b164..d03e6c47 100755 --- a/dnsapi/dns_linode.sh +++ b/dnsapi/dns_linode.sh @@ -2,7 +2,7 @@ #Author: Philipp Grosswiler -LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action=" +LINODE_API_URL="https://api.linode.com/v4/domains" ######## Public functions ##################### @@ -27,10 +27,14 @@ dns_linode_add() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue" + _payload="{ + \"type\": \"TXT\", + \"name\": \"$_sub_domain\", + \"target\": \"$txtvalue\" + }" - if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then - _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) _debug _resource_id "$_resource_id" if [ -z "$_resource_id" ]; then @@ -65,25 +69,21 @@ dns_linode_rm() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _parameters="&DomainID=$_domain_id" - - if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then + if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" - resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")" + resource="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$_sub_domain\".*}")" if [ "$resource" ]; then - _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_resource_id" ]; then _debug _resource_id "$_resource_id" - _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id" + if _rest DELETE "/$_domain_id/records/$_resource_id" && [ -n "$response" ]; then + # On 200/OK, empty set is returned. Check for error, if any. + _error_response=$(printf "%s\n" "$response" | _egrep_o "\"errors\"" | cut -d : -f 2 | tr -d " " | _head_n 1) - if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then - _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) - _debug _resource_id "$_resource_id" - - if [ -z "$_resource_id" ]; then - _err "Error deleting the domain resource." + if [ -n "$_error_response" ]; then + _err "Error deleting the domain resource: $_error_response" return 1 fi @@ -127,7 +127,7 @@ _get_root() { i=2 p=1 - if _rest GET "domain.list"; then + if _rest GET; then response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" while true; do h=$(printf "%s" "$domain" | cut -d . -f $i-100) @@ -137,9 +137,9 @@ _get_root() { return 1 fi - hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")" + hostedzone="$(echo "$response" | _egrep_o "{.*\"domain\":\s*\"$h\".*}")" if [ "$hostedzone" ]; then - _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h @@ -165,6 +165,7 @@ _rest() { export _H1="Accept: application/json" export _H2="Content-Type: application/json" + export _H3="Authorization: Bearer $LINODE_API_KEY" if [ "$mtd" != "GET" ]; then # both POST and DELETE. From 598becf6197d750615f9f62a881f4c383c2f35b2 Mon Sep 17 00:00:00 2001 From: neilpang Date: Mon, 3 Dec 2018 20:31:20 +0800 Subject: [PATCH 24/64] minor, fix format --- dnsapi/dns_cf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index cbebb03a..944956ad 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -155,7 +155,7 @@ dns_cf_rm() { # _domain_id=sdjkglgdfewsdfg _get_root() { - _cf_zones="$(_readdomainconf $_DOMAIN_CF_ZONES_CACHE_NAME_)" + _cf_zones="$(_readdomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_")" _debug2 "_cf_zones" "$_cf_zones" if [ -z "$_cf_zones" ]; then _debug "$_DOMAIN_CF_ZONES_CACHE_NAME_ is none, so get it." From fb08b53f0b4c43db21bc33b804c031c9cc8ae975 Mon Sep 17 00:00:00 2001 From: Adrian Almenar Date: Mon, 3 Dec 2018 18:42:33 +0100 Subject: [PATCH 25/64] Add Neodigit.net DNS API --- README.md | 3 +- dnsapi/README.md | 13 +++ dnsapi/dns_neodigit.sh | 181 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 dnsapi/dns_neodigit.sh diff --git a/README.md b/README.md index c4ea5c6e..6dcc8eaf 100644 --- a/README.md +++ b/README.md @@ -329,8 +329,9 @@ You don't have to do anything manually! 1. Namecheap API (https://www.namecheap.com/) 1. MyDNS.JP API (https://www.mydns.jp/) 1. hosting.de (https://www.hosting.de) +1. Neodigit.net API (https://www.neodigit.net) -And: +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.)** diff --git a/dnsapi/README.md b/dnsapi/README.md index bb0e89d0..b5f4d2cc 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -1054,6 +1054,19 @@ acme.sh --issue --dns dns_hostingde -d example.com -d *.example.com The hosting.de API key and endpoint will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +## 56. Use Neodigit.net API + +``` +export NEODIGIT_API_TOKEN="eXJxTkdUVUZmcHQ3QWJackQ4ZGlMejRDSklRYmo5VG5zcFFKK2thYnE0WnVnNnMy" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_neodigit -d example.com -d www.example.com +``` + +Neodigit API Token will be saved in `~/.acme.sh/account.conf` and will be used when needed. + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_neodigit.sh b/dnsapi/dns_neodigit.sh new file mode 100644 index 00000000..9835613d --- /dev/null +++ b/dnsapi/dns_neodigit.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env sh + +# +# NEODIGIT_API_TOKEN="jasdfhklsjadhflnhsausdfas" + +# This is Neodigit.net api wrapper for acme.sh +# +# Author: Adrian Almenar +# Report Bugs here: https://github.com/tecnocratica/acme.sh +# +NEODIGIT_API_URL="https://api.neodigit.net/v1" +# +######## Public functions ##################### + +# Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_neodigit_add() { + fulldomain=$1 + txtvalue=$2 + + NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" + if [ -z "$NEODIGIT_API_TOKEN" ]; then + NEODIGIT_API_TOKEN="" + _err "You haven't specified a Token api key." + _err "Please create the key and try again." + return 1 + fi + + #save the api key and email to the account conf file. + _saveaccountconf_mutable NEODIGIT_API_TOKEN "$NEODIGIT_API_TOKEN" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + _debug domain "$_domain" + _debug sub_domain "$_sub_domain" + + _debug "Getting txt records" + _neo_rest GET "dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain" + + _debug _code "$_code" + + if [ "$_code" != "200" ]; then + _err "error retrieving data!" + return 1 + fi + + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + _debug domain "$_domain" + _debug sub_domain "$_sub_domain" + + _info "Adding record" + if _neo_rest POST "dns/zones/$_domain_id/records" "{\"record\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":60}}"; then + if printf -- "%s" "$response" | grep "$_sub_domain" >/dev/null; then + _info "Added, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + return 1 +} + +#fulldomain txtvalue +dns_neodigit_rm() { + fulldomain=$1 + txtvalue=$2 + + NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" + if [ -z "$NEODIGIT_API_TOKEN" ]; then + NEODIGIT_API_TOKEN="" + _err "You haven't specified a Token api key." + _err "Please create the key and try again." + return 1 + fi + + #save the api key and email to the account conf file. + _saveaccountconf_mutable NEODIGIT_API_TOKEN "$NEODIGIT_API_TOKEN" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + _neo_rest GET "dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain&content=$txtvalue" + + if [ "$_code" != "200" ]; then + _err "error retrieving data!" + return 1 + fi + + record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) + _debug "record_id" "$record_id" + if [ -z "$record_id" ]; then + _err "Can not get record id to remove." + return 1 + fi + if ! _neo_rest DELETE "dns/zones/$_domain_id/records/$record_id"; then + _err "Delete record error." + return 1 + fi + +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=dasfdsafsadg5ythd +_get_root() { + domain=$1 + i=2 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if ! _neo_rest GET "dns/zones?name=$h"; then + return 1 + fi + + _debug p "$p" + + if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_neo_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + export _H1="X-TCPanel-Token: $NEODIGIT_API_TOKEN" + export _H2="Content-Type: application/json" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$NEODIGIT_API_URL/$ep" "" "$m")" + else + response="$(_get "$NEODIGIT_API_URL/$ep")" + fi + + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} From f4ad42bb842e410bd86500cf5bb936d8c3f1e989 Mon Sep 17 00:00:00 2001 From: Adrian Almenar Date: Tue, 4 Dec 2018 14:33:00 +0100 Subject: [PATCH 26/64] Changes requested on commit review --- dnsapi/dns_neodigit.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_neodigit.sh b/dnsapi/dns_neodigit.sh index 9835613d..d87845ad 100644 --- a/dnsapi/dns_neodigit.sh +++ b/dnsapi/dns_neodigit.sh @@ -17,7 +17,7 @@ dns_neodigit_add() { fulldomain=$1 txtvalue=$2 - NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" + NEODIGIT_API_TOKEN="$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" if [ -z "$NEODIGIT_API_TOKEN" ]; then NEODIGIT_API_TOKEN="" _err "You haven't specified a Token api key." @@ -73,7 +73,7 @@ dns_neodigit_rm() { fulldomain=$1 txtvalue=$2 - NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" + NEODIGIT_API_TOKEN="$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" if [ -z "$NEODIGIT_API_TOKEN" ]; then NEODIGIT_API_TOKEN="" _err "You haven't specified a Token api key." @@ -102,7 +102,7 @@ dns_neodigit_rm() { return 1 fi - record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) + record_id=$(echo "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." @@ -140,7 +140,7 @@ _get_root() { _debug p "$p" if _contains "$response" "\"name\":\"$h\"" >/dev/null; then - _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) + _domain_id=$(echo "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h From 842f030355ee9013b6ba6861d347ef15a4e546e3 Mon Sep 17 00:00:00 2001 From: Adrian Almenar Date: Tue, 4 Dec 2018 14:36:28 +0100 Subject: [PATCH 27/64] Revert change --- dnsapi/dns_neodigit.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_neodigit.sh b/dnsapi/dns_neodigit.sh index d87845ad..64ea8786 100644 --- a/dnsapi/dns_neodigit.sh +++ b/dnsapi/dns_neodigit.sh @@ -17,7 +17,7 @@ dns_neodigit_add() { fulldomain=$1 txtvalue=$2 - NEODIGIT_API_TOKEN="$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" + NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" if [ -z "$NEODIGIT_API_TOKEN" ]; then NEODIGIT_API_TOKEN="" _err "You haven't specified a Token api key." @@ -73,7 +73,7 @@ dns_neodigit_rm() { fulldomain=$1 txtvalue=$2 - NEODIGIT_API_TOKEN="$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" + NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" if [ -z "$NEODIGIT_API_TOKEN" ]; then NEODIGIT_API_TOKEN="" _err "You haven't specified a Token api key." From 7ff525468f0fda2c29e3aea51e8cc425a1204acc Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Wed, 5 Dec 2018 03:01:50 +0800 Subject: [PATCH 28/64] Arch Linux package acme.sh is now in [community] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4ea5c6e..d8069405 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) - [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html) - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) -- [archlinux](https://aur.archlinux.org/packages/acme.sh-git/) +- [archlinux](https://www.archlinux.org/packages/community/any/acme.sh) - [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/) From 9841063df9dd40120d32ed8185defaee9ef8a8e6 Mon Sep 17 00:00:00 2001 From: neilpang Date: Thu, 6 Dec 2018 22:05:26 +0800 Subject: [PATCH 29/64] fix nginx mode --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 392079fb..ff241e6e 100755 --- a/acme.sh +++ b/acme.sh @@ -2839,7 +2839,7 @@ _isRealNginxConf() { _skip_ssl=1 for _listen_i in $(echo "$_seg_n" | tr "\t" ' ' | grep "^ *listen" | tr -d " "); do if [ "$_listen_i" ]; then - if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl[ |;]")" ]; then + if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl")" ]; then _debug2 "$_listen_i is ssl" else _debug2 "$_listen_i is plain text" From c84466b1319f5648b420b5670cf15d686df4646a Mon Sep 17 00:00:00 2001 From: Sergey Pashinin Date: Mon, 10 Dec 2018 16:55:21 +0300 Subject: [PATCH 30/64] Write certs in Vault for Fabio load balancer --- deploy/vault_cli.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/deploy/vault_cli.sh b/deploy/vault_cli.sh index 79c25aa2..b93fdd51 100644 --- a/deploy/vault_cli.sh +++ b/deploy/vault_cli.sh @@ -49,9 +49,13 @@ vault_cli_deploy() { return 1 fi - $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 - $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 - $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 - $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 + if [ -n "$FABIO" ]; then + $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1 + else + $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 + $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 + $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 + $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 + fi } From 9f067d7f56eba9c1b301686d2a89419d9e993ea1 Mon Sep 17 00:00:00 2001 From: Sergey Pashinin Date: Mon, 10 Dec 2018 18:17:18 +0300 Subject: [PATCH 31/64] Deploy to Hashicorp Vault docs --- deploy/README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/deploy/README.md b/deploy/README.md index 5c03ce6a..cec7d773 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -295,4 +295,40 @@ You can then deploy the certificate as follows ```sh acme.sh --deploy -d www.mydomain.com --deploy-hook gitlab -``` \ No newline at end of file +``` + +## 12. Deploy your cert to Hashicorp Vault + +```sh +export VAULT_PREFIX="acme" +``` + +You can then deploy the certificate as follows + +```sh +acme.sh --deploy -d www.mydomain.com --deploy-hook vault_cli +``` + +Your certs will be saved in Vault using this structure: + +```sh +vault write "${VAULT_PREFIX}/${domain}/cert.pem" value=@"..." +vault write "${VAULT_PREFIX}/${domain}/cert.key" value=@"..." +vault write "${VAULT_PREFIX}/${domain}/chain.pem" value=@"..." +vault write "${VAULT_PREFIX}/${domain}/fullchain.pem" value=@"..." +``` + +You might be using Fabio load balancer (which can get certs from +Vault). It needs a bit different structure of your certs in Vault. It +gets certs only from keys that were saved in `prefix/domain`, like this: + +```bash +vault write /www.domain.com cert=@cert.pem key=@key.pem +``` + +If you want to save certs in Vault this way just set "FABIO" env +variable to anything (ex: "1") before running `acme.sh`: + +```sh +export FABIO="1" +``` From a43545c6ea9f79df2acfa1bc10db8f44e803b197 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 11 Dec 2018 19:11:56 +0800 Subject: [PATCH 32/64] fix https://github.com/Neilpang/acme.sh/issues/1959 --- dnsapi/dns_cf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index 944956ad..f50ab498 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -181,7 +181,7 @@ _get_root() { fi if _contains "$_cf_zones" "\"name\":\"$h\"" >/dev/null; then - _domain_id=$(printf "%s\n" "$_cf_zones" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") + _domain_id=$(echo "$_cf_zones" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "^\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h From 5431d051685bc6c0d7cd1341dc5111892715ee19 Mon Sep 17 00:00:00 2001 From: "Daniel F. Dickinson" Date: Thu, 13 Dec 2018 01:22:44 -0500 Subject: [PATCH 33/64] dnsapi nsupdate: Add nsupdate debug option When debug is enabled, also use nsupdate's debug logging so that the user can see potential issues with the nsupdate transaction. Signed-off-by: Daniel F. Dickinson --- dnsapi/dns_nsupdate.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh index 555f4d29..609785e0 100755 --- a/dnsapi/dns_nsupdate.sh +++ b/dnsapi/dns_nsupdate.sh @@ -14,7 +14,9 @@ dns_nsupdate_add() { _saveaccountconf NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}" _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}" _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" - nsupdate -k "${NSUPDATE_KEY}" < Date: Thu, 13 Dec 2018 01:23:53 -0500 Subject: [PATCH 34/64] dnsapi: Add option to set zone for nsupdate Some DNS servers for which dns_nsupdate.sh is applicable (such as dyn.com's 'Standard DNS' TSIG update mechanism), require that the zone be set during the nsupdate transaction. Therefore we add a new environment variable NSUPDATE_ZONE which is used to set the zone for the DNS TSIG transaction. Signed-off-by: Daniel F. Dickinson --- dnsapi/README.md | 6 +++++- dnsapi/dns_nsupdate.sh | 23 +++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index afe1f7f4..30c1be53 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -146,13 +146,17 @@ Finally, make the DNS server and update Key available to `acme.sh` export NSUPDATE_SERVER="dns.example.com" export NSUPDATE_KEY="/path/to/your/nsupdate.key" ``` +and optionally (depending on DNS server) +``` +export NSUPDATE_ZONE="example.com" +``` Ok, let's issue a cert now: ``` acme.sh --issue --dns dns_nsupdate -d example.com -d www.example.com ``` -The `NSUPDATE_SERVER` and `NSUPDATE_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +The `NSUPDATE_SERVER`, `NSUPDATE_KEY`, and `NSUPDATE_ZONE` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. ## 8. Use LuaDNS domain API diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh index 609785e0..8b479f98 100755 --- a/dnsapi/dns_nsupdate.sh +++ b/dnsapi/dns_nsupdate.sh @@ -13,14 +13,24 @@ dns_nsupdate_add() { _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}" _saveaccountconf NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}" _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}" + _saveaccountconf NSUPDATE_ZONE "${NSUPDATE_ZONE}" _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D" - nsupdate -k "${NSUPDATE_KEY}" $nsdebug < Date: Sun, 16 Dec 2018 21:10:22 +0800 Subject: [PATCH 35/64] add more debug info https://github.com/Neilpang/acme.sh/issues/1932 --- acme.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index ff241e6e..5cfcb382 100755 --- a/acme.sh +++ b/acme.sh @@ -1516,7 +1516,8 @@ _calcjwk() { JWK_HEADERPLACE_PART1='{"nonce": "' JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"' else - _err "Only RSA or EC key is supported." + _err "Only RSA or EC key is supported. keyfile=$keyfile" + _debug2 "$(cat "$keyfile")" return 1 fi From 2b9ebd666280cc7832bce31d0b282df7f4d276d7 Mon Sep 17 00:00:00 2001 From: neilpang Date: Mon, 17 Dec 2018 23:02:02 +0800 Subject: [PATCH 36/64] fix showcsr https://github.com/Neilpang/acme.sh/issues/1968 --- acme.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index 5cfcb382..6264da01 100755 --- a/acme.sh +++ b/acme.sh @@ -1134,12 +1134,17 @@ _readSubjectAltNamesFromCSR() { if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then _debug "AltNames contains subject" - _dnsAltnames="$(printf "%s" "$_dnsAltnames," | sed "s/DNS:$_csrsubj,//g")" + _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')" + _debug _excapedAlgnames "$_excapedAlgnames" + _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')" + _debug _escapedSubject "$_escapedSubject" + _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" + _debug _dnsAltnames "$_dnsAltnames" else _debug "AltNames doesn't contain subject" fi - printf "%s" "$_dnsAltnames" | sed "s/DNS://g" + echo "$_dnsAltnames" | sed "s/DNS://g" } #_csrfile From 08681f4a8b9ea066b00e5f890b47bfbfe3fb5b3e Mon Sep 17 00:00:00 2001 From: neilpang Date: Tue, 18 Dec 2018 19:28:38 +0800 Subject: [PATCH 37/64] support tls-alpn-01 https://github.com/Neilpang/acme.sh/issues/1675#issuecomment-447857756 --- acme.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/acme.sh b/acme.sh index 6264da01..efd64467 100755 --- a/acme.sh +++ b/acme.sh @@ -37,6 +37,7 @@ VTYPE_HTTP="http-01" VTYPE_DNS="dns-01" VTYPE_TLS="tls-sni-01" VTYPE_TLS2="tls-sni-02" +VTYPE_ALPN="tls-alpn-01" LOCAL_ANY_ADDRESS="0.0.0.0" @@ -48,6 +49,7 @@ NO_VALUE="no" W_TLS="tls" W_DNS="dns" +W_ALPN="alpn" DNS_ALIAS_PREFIX="=" MODE_STATELESS="stateless" @@ -1046,7 +1048,7 @@ _idn() { fi } -#_createcsr cn san_list keyfile csrfile conf +#_createcsr cn san_list keyfile csrfile conf acmeValidationv1 _createcsr() { _debug _createcsr domain="$1" @@ -1054,6 +1056,7 @@ _createcsr() { csrkey="$3" csr="$4" csrconf="$5" + acmeValidationv1="$6" _debug2 domain "$domain" _debug2 domainlist "$domainlist" _debug2 csrkey "$csrkey" @@ -1062,7 +1065,9 @@ _createcsr() { printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment" >"$csrconf" - if [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then + if [ "$acmeValidationv1" ]; then + printf -- "\nsubjectAltName=DNS:$domainlist" >>"$csrconf" + elif [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then #single domain _info "Single domain" "$domain" printf -- "\nsubjectAltName=DNS:$domain" >>"$csrconf" @@ -1084,6 +1089,10 @@ _createcsr() { printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf" fi + if [ "$acmeValidationv1" ]; then + printf "\n1.3.6.1.5.5.7.1.30.1=critical,DER:04:20:${acmeValidationv1}" >> "${csrconf}" + fi + _csr_cn="$(_idn "$domain")" _debug2 _csr_cn "$_csr_cn" if _contains "$(uname -a)" "MINGW"; then @@ -2107,7 +2116,7 @@ _sleep() { fi } -# _starttlsserver san_a san_b port content _ncaddr +# _starttlsserver san_a san_b port content _ncaddr acmeValidationv1 _starttlsserver() { _info "Starting tls server." san_a="$1" @@ -2115,10 +2124,12 @@ _starttlsserver() { port="$3" content="$4" opaddr="$5" + acmeValidationv1="$6" _debug san_a "$san_a" _debug san_b "$san_b" _debug port "$port" + _debug acmeValidationv1 "$acmeValidationv1" #create key TLS_KEY if ! _createkey "2048" "$TLS_KEY"; then @@ -2131,7 +2142,7 @@ _starttlsserver() { if [ "$san_b" ]; then alt="$alt,$san_b" fi - if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF"; then + if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$acmeValidationv1"; then _err "Create tls validation csr error." return 1 fi @@ -2157,6 +2168,10 @@ _starttlsserver() { __S_OPENSSL="$__S_OPENSSL -6" fi + if [ "$acmeValidationv1" ]; then + __S_OPENSSL="$__S_OPENSSL -alpn acme-tls/1" + fi + _debug "$__S_OPENSSL" if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then $__S_OPENSSL -tlsextdebug & @@ -3067,8 +3082,8 @@ _on_before_issue() { _savedomainconf "Le_HTTPPort" "$Le_HTTPPort" fi _checkport="$Le_HTTPPort" - elif [ "$_currentRoot" = "$W_TLS" ]; then - _info "Standalone tls mode." + elif [ "$_currentRoot" = "$W_TLS" ] || [ "$_currentRoot" = "$W_ALPN" ]; then + _info "Standalone tls/alpn mode." if [ -z "$Le_TLSPort" ]; then Le_TLSPort=443 else @@ -3694,6 +3709,10 @@ $_authorizations_map" fi fi + if [ "$_currentRoot" = "$W_ALPN" ]; then + vtype="$VTYPE_ALPN" + fi + if [ "$ACME_VERSION" = "2" ]; then response="$(echo "$_authorizations_map" | grep "^$d," | sed "s/$d,//")" _debug2 "response" "$response" @@ -4007,6 +4026,16 @@ $_authorizations_map" _on_issue_err "$_post_hook" "$vlist" return 1 fi + elif [ "$vtype" = "$VTYPE_ALPN" ]; then + acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")" + _debug acmevalidationv1 "$acmevalidationv1" + if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then + _err "Start tls server error." + _clearupwebbroot "$_currentRoot" "$removelevel" "$token" + _clearup + _on_issue_err "$_post_hook" "$vlist" + return 1 + fi fi if ! __trigger_validation "$uri" "$keyauthorization"; then @@ -5469,6 +5498,7 @@ Parameters: --output-insecure Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for secure. --webroot, -w /path/to/webroot Specifies the web root folder for web root mode. --standalone Use standalone mode. + --alpn Use standalone alpn mode. --stateless Use stateless mode, see: $_STATELESS_WIKI --apache Use apache mode. --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file] Use dns mode or dns api. @@ -5823,6 +5853,14 @@ _process() { _webroot="$_webroot,$wvalue" fi ;; + --alpn) + wvalue="$W_ALPN" + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; --stateless) wvalue="$MODE_STATELESS" if [ -z "$_webroot" ]; then From 79a0a66f1f2f547464bec4ee7ab876a664a2ed78 Mon Sep 17 00:00:00 2001 From: neilpang Date: Tue, 18 Dec 2018 20:18:18 +0800 Subject: [PATCH 38/64] support --tlsport --- acme.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/acme.sh b/acme.sh index efd64467..5ce97f88 100755 --- a/acme.sh +++ b/acme.sh @@ -5529,6 +5529,7 @@ Parameters: --accountkey Specifies the account key path, only valid for the '--install' command. --days Specifies the days to renew the cert when using '--issue' command. The max value is $MAX_RENEW days. --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. + --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses. --listraw Only used for '--list' command, list the certs in raw format. --stopRenewOnError, -se Only valid for '--renew-all' command. Stop if one cert has error in renewal. @@ -5985,6 +5986,11 @@ _process() { Le_HTTPPort="$_httpport" shift ;; + --tlsport) + _tlsport="$2" + Le_TLSPort="$_tlsport" + shift + ;; --listraw) _listraw="raw" ;; From f99ca918db8e4587ec1437c6815a32a8de49c42f Mon Sep 17 00:00:00 2001 From: neilpang Date: Tue, 18 Dec 2018 20:33:33 +0800 Subject: [PATCH 39/64] fix format --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 5ce97f88..acda4bee 100755 --- a/acme.sh +++ b/acme.sh @@ -1090,7 +1090,7 @@ _createcsr() { fi if [ "$acmeValidationv1" ]; then - printf "\n1.3.6.1.5.5.7.1.30.1=critical,DER:04:20:${acmeValidationv1}" >> "${csrconf}" + printf "\n1.3.6.1.5.5.7.1.30.1=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}" fi _csr_cn="$(_idn "$domain")" From 67d3e8d04968e8beaaa463639cd5989edf5fd9b7 Mon Sep 17 00:00:00 2001 From: Ivru Date: Thu, 20 Dec 2018 16:07:05 +0100 Subject: [PATCH 40/64] Add Exoscape API support for DNSAPI --- README.md | 1 + dnsapi/README.md | 18 +++++ dnsapi/dns_exoscale.sh | 170 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100755 dnsapi/dns_exoscale.sh diff --git a/README.md b/README.md index 033711f3..6a1cf3a4 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,7 @@ You don't have to do anything manually! 1. MyDNS.JP API (https://www.mydns.jp/) 1. hosting.de (https://www.hosting.de) 1. Neodigit.net API (https://www.neodigit.net) +1. Exoscale.com API (https://www.exoscale.com/) And: diff --git a/dnsapi/README.md b/dnsapi/README.md index afe1f7f4..89cf3975 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -1076,6 +1076,24 @@ acme.sh --issue --dns dns_neodigit -d example.com -d www.example.com Neodigit API Token will be saved in `~/.acme.sh/account.conf` and will be used when needed. +## 57. Use Exoscale API + +Create an API key and secret key in the Exoscale account section + +Set your API and secret key: + +``` +export EXOSCALE_API_KEY='xxx' +export EXOSCALE_SECRET_KEY='xxx' +``` + +Now, let's issue a cert: +``` +acme.sh --issue --dns dns_netcup -d example.com -d www.example.com +``` + +The `EXOSCALE_API_KEY` and `EXOSCALE_SECRET_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_exoscale.sh b/dnsapi/dns_exoscale.sh new file mode 100755 index 00000000..f9ffff24 --- /dev/null +++ b/dnsapi/dns_exoscale.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env sh + +EXOSCALE_API=https://api.exoscale.com/dns/v1 + + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_exoscale_add() { + fulldomain=$1 + txtvalue=$2 + + if ! _checkAuth; then + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _info "Adding record" + if _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then + if _contains "$response" "$txtvalue"; then + _info "Added, OK" + return 0 + fi + fi + _err "Add txt record error." + return 1 + +} + +# Usage: fulldomain txtvalue +# Used to remove the txt record after validation +dns_exoscale_rm() { + fulldomain=$1 + txtvalue=$2 + + if ! _checkAuth; then + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + _exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token" + if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then + _record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") + fi + + if [ -z "$_record_id" ] ; then + _err "Can not get record id to remove." + return 1 + fi + + _debug "Deleting record $_record_id" + + if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then + _err "Delete record error." + return 1 + fi + + return 0 +} + + +#################### Private functions below ################################## + +_checkAuth() { + EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}" + EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}" + + if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then + EXOSCALE_API_KEY="" + EXOSCALE_SECRET_KEY="" + _err "You don't specify Exoscale application key and application secret yet." + _err "Please create you key and try again." + return 1 + fi + + _saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY" + _saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY" + + return 0 +} + +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +# _domain_token=sdjkglgdfewsdfg +_get_root() { + + if ! _exoscale_rest GET "domains"; then + return 1 + fi + + domain=$1 + i=2 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") + _domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") + if [ "$_domain_token" ] && [ "$_domain_id" ] ; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +# returns response +_exoscale_rest() { + method=$1 + path="$2" + data="$3" + token="$4" + request_url="$EXOSCALE_API/$path" + _debug "$path" + + export _H1="Accept: application/json" + + if [ "$token" ]; then + export _H2="X-DNS-Domain-Token: $token" + else + export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY" + fi + + if [ "$data" ] || [ "$method" = "DELETE" ]; then + _H3="Content-Type: application/json" + _debug data "$data" + response="$(_post "$data" "$request_url" "" "$method")" + else + response="$(_get "$request_url" "" "" "$method")" + fi + + if [ "$?" != "0" ]; then + _err "error $request_url" + return 1 + fi + _debug2 response "$response" + return 0 +} From eea9aaf9404d6f62c722e96c2d064bcab21347b4 Mon Sep 17 00:00:00 2001 From: Ivru Date: Thu, 20 Dec 2018 16:24:08 +0100 Subject: [PATCH 41/64] Fix typos --- dnsapi/dns_exoscale.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_exoscale.sh b/dnsapi/dns_exoscale.sh index f9ffff24..a837b79d 100755 --- a/dnsapi/dns_exoscale.sh +++ b/dnsapi/dns_exoscale.sh @@ -61,7 +61,7 @@ dns_exoscale_rm() { _record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") fi - if [ -z "$_record_id" ] ; then + if [ -z "$_record_id" ]; then _err "Can not get record id to remove." return 1 fi @@ -123,7 +123,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") _domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") - if [ "$_domain_token" ] && [ "$_domain_id" ] ; then + if [ "$_domain_token" ] && [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 From 8e43b86f06d244ed6635054cb768c45e86f16bdd Mon Sep 17 00:00:00 2001 From: Ivru Date: Thu, 20 Dec 2018 16:30:02 +0100 Subject: [PATCH 42/64] Export header _H3 --- dnsapi/dns_exoscale.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_exoscale.sh b/dnsapi/dns_exoscale.sh index a837b79d..bb433ec3 100755 --- a/dnsapi/dns_exoscale.sh +++ b/dnsapi/dns_exoscale.sh @@ -154,7 +154,7 @@ _exoscale_rest() { fi if [ "$data" ] || [ "$method" = "DELETE" ]; then - _H3="Content-Type: application/json" + export _H3="Content-Type: application/json" _debug data "$data" response="$(_post "$data" "$request_url" "" "$method")" else From 405173a0b4176d8858f7d31aaf570a27cafb1a90 Mon Sep 17 00:00:00 2001 From: Ivru Date: Thu, 20 Dec 2018 16:37:11 +0100 Subject: [PATCH 43/64] Remove extraneous blank lines --- dnsapi/dns_exoscale.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/dnsapi/dns_exoscale.sh b/dnsapi/dns_exoscale.sh index bb433ec3..ccf05fc5 100755 --- a/dnsapi/dns_exoscale.sh +++ b/dnsapi/dns_exoscale.sh @@ -2,7 +2,6 @@ EXOSCALE_API=https://api.exoscale.com/dns/v1 - ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -76,7 +75,6 @@ dns_exoscale_rm() { return 0 } - #################### Private functions below ################################## _checkAuth() { From 9a473640fb2341a5c9e56ffcb645200c1725392c Mon Sep 17 00:00:00 2001 From: "Aaron W. Swenson" Date: Thu, 20 Dec 2018 11:00:10 -0500 Subject: [PATCH 44/64] Revert "Update Linode API to v4" This reverts commit 9a27b389765ac6d7a256333e9e1f6fe3c4b92e08. Turns out, the Cloud Manager is not backward compatible, nor is the Classic Manager forward compatible. --- dnsapi/README.md | 13 ++----------- dnsapi/dns_linode.sh | 39 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 30c1be53..603bd72a 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -268,18 +268,9 @@ when needed. ## 14. Use Linode domain API First you need to login to your Linode account to get your API Key. +[https://manager.linode.com/profile/api](https://manager.linode.com/profile/api) - * [Classic Manager](https://manager.linode.com/profile/api) - - Under "Add an API key", Give the new key a "Label" (we recommend *ACME*), - set the expiry to never, "Create API Key", and copy the new key into the `LINODE_API_KEY` command - below. - - * [Cloud Manager](https://cloud.linode.com/profile/tokens) - - Click on "Add a Personal Access Token". Give the new key a "Label" (we - recommend *ACME*), give it Read/Write access to "Domains". "Submit", and - copy the new key into the `LINODE_API_KEY` command below. +Then add an API key with label *ACME* and copy the new key. ```sh export LINODE_API_KEY="..." diff --git a/dnsapi/dns_linode.sh b/dnsapi/dns_linode.sh index d03e6c47..ead5b164 100755 --- a/dnsapi/dns_linode.sh +++ b/dnsapi/dns_linode.sh @@ -2,7 +2,7 @@ #Author: Philipp Grosswiler -LINODE_API_URL="https://api.linode.com/v4/domains" +LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action=" ######## Public functions ##################### @@ -27,14 +27,10 @@ dns_linode_add() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _payload="{ - \"type\": \"TXT\", - \"name\": \"$_sub_domain\", - \"target\": \"$txtvalue\" - }" + _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue" - if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then - _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) _debug _resource_id "$_resource_id" if [ -z "$_resource_id" ]; then @@ -69,21 +65,25 @@ dns_linode_rm() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then + _parameters="&DomainID=$_domain_id" + + if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" - resource="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$_sub_domain\".*}")" + resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")" if [ "$resource" ]; then - _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_resource_id" ]; then _debug _resource_id "$_resource_id" - if _rest DELETE "/$_domain_id/records/$_resource_id" && [ -n "$response" ]; then - # On 200/OK, empty set is returned. Check for error, if any. - _error_response=$(printf "%s\n" "$response" | _egrep_o "\"errors\"" | cut -d : -f 2 | tr -d " " | _head_n 1) + _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id" - if [ -n "$_error_response" ]; then - _err "Error deleting the domain resource: $_error_response" + if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _resource_id "$_resource_id" + + if [ -z "$_resource_id" ]; then + _err "Error deleting the domain resource." return 1 fi @@ -127,7 +127,7 @@ _get_root() { i=2 p=1 - if _rest GET; then + if _rest GET "domain.list"; then response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" while true; do h=$(printf "%s" "$domain" | cut -d . -f $i-100) @@ -137,9 +137,9 @@ _get_root() { return 1 fi - hostedzone="$(echo "$response" | _egrep_o "{.*\"domain\":\s*\"$h\".*}")" + hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")" if [ "$hostedzone" ]; then - _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h @@ -165,7 +165,6 @@ _rest() { export _H1="Accept: application/json" export _H2="Content-Type: application/json" - export _H3="Authorization: Bearer $LINODE_API_KEY" if [ "$mtd" != "GET" ]; then # both POST and DELETE. From c8c1140f15d6c96d83bd3734fcc1604e0caa408e Mon Sep 17 00:00:00 2001 From: "Aaron W. Swenson" Date: Thu, 20 Dec 2018 11:01:34 -0500 Subject: [PATCH 45/64] Linode API v4 Redo The Cloud and Classic Manager work with different APIs, and so require a separate module, which we introduce here. The README has also been modified to state that the two are separate and incompatible, and provides instructions on using either. --- dnsapi/README.md | 48 +++++++++-- dnsapi/dns_linode_v4.sh | 185 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 5 deletions(-) create mode 100755 dnsapi/dns_linode_v4.sh diff --git a/dnsapi/README.md b/dnsapi/README.md index 603bd72a..df6db111 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -267,16 +267,26 @@ when needed. ## 14. Use Linode domain API -First you need to login to your Linode account to get your API Key. -[https://manager.linode.com/profile/api](https://manager.linode.com/profile/api) +The tokens created in the classic manager and cloud manager are incompatible +with one another. While the classic manager makes an all or nothing API, the +newer cloud manager interface promises to produce API keys with a finer +permission system. However, either way works just fine. -Then add an API key with label *ACME* and copy the new key. +### Classic Manager ### + +Classic Manager: https://manager.linode.com/profile/api + +First you need to login to your Linode account to get your API Key. + +Then add an API key with label *ACME* and copy the new key into the following +command. ```sh export LINODE_API_KEY="..." ``` -Due to the reload time of any changes in the DNS records, we have to use the `dnssleep` option to wait at least 15 minutes for the changes to take effect. +Due to the reload time of any changes in the DNS records, we have to use the +`dnssleep` option to wait at least 15 minutes for the changes to take effect. Ok, let's issue a cert now: @@ -284,7 +294,35 @@ Ok, let's issue a cert now: acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.com ``` -The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be +reused when needed. + +### Cloud Manager ### + +Cloud Manager: https://cloud.linode.com/profile/tokens + +First you need to login to your Linode account to get your API Key. + + 1. Click on "Add a Personal Access Token". + 2. Give the new key a "Label" (we recommend *ACME*) + 3. Give it Read/Write access to "Domains" + 4. "Submit" and copy the new key into the `LINODE_V4_API_KEY` command below. + +```sh +export LINODE_V4_API_KEY="..." +``` + +Due to the reload time of any changes in the DNS records, we have to use the +`dnssleep` option to wait at least 15 minutes for the changes to take effect. + +Ok, let's issue a cert now: + +```sh +acme.sh --issue --dns dns_linode_v4 --dnssleep 900 -d example.com -d www.example.com +``` + +The `LINODE_V4_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be +reused when needed. ## 15. Use FreeDNS diff --git a/dnsapi/dns_linode_v4.sh b/dnsapi/dns_linode_v4.sh new file mode 100755 index 00000000..dfa1a651 --- /dev/null +++ b/dnsapi/dns_linode_v4.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +#Original Author: Philipp Grosswiler +#v4 Update Author: Aaron W. Swenson + +LINODE_V4_API_URL="https://api.linode.com/v4/domains" + +######## Public functions ##################### + +#Usage: dns_linode_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_linode_add() { + fulldomain="${1}" + txtvalue="${2}" + + if ! _Linode_API; then + return 1 + fi + + _info "Using Linode" + _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain does not exist." + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _payload="{ + \"type\": \"TXT\", + \"name\": \"$_sub_domain\", + \"target\": \"$txtvalue\" + }" + + if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _resource_id "$_resource_id" + + if [ -z "$_resource_id" ]; then + _err "Error adding the domain resource." + return 1 + fi + + _info "Domain resource successfully added." + return 0 + fi + + return 1 +} + +#Usage: dns_linode_rm _acme-challenge.www.domain.com +dns_linode_rm() { + fulldomain="${1}" + + if ! _Linode_API; then + return 1 + fi + + _info "Using Linode" + _debug "Calling: dns_linode_rm() '${fulldomain}'" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain does not exist." + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then + response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" + + resource="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$_sub_domain\".*}")" + if [ "$resource" ]; then + _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$_resource_id" ]; then + _debug _resource_id "$_resource_id" + + if _rest DELETE "/$_domain_id/records/$_resource_id" && [ -n "$response" ]; then + # On 200/OK, empty set is returned. Check for error, if any. + _error_response=$(printf "%s\n" "$response" | _egrep_o "\"errors\"" | cut -d : -f 2 | tr -d " " | _head_n 1) + + if [ -n "$_error_response" ]; then + _err "Error deleting the domain resource: $_error_response" + return 1 + fi + + _info "Domain resource successfully deleted." + return 0 + fi + fi + + return 1 + fi + + return 0 + fi + + return 1 +} + +#################### Private functions below ################################## + +_Linode_API() { + if [ -z "$LINODE_V4_API_KEY" ]; then + LINODE_V4_API_KEY="" + + _err "You didn't specify the Linode v4 API key yet." + _err "Please create your key and try again." + + return 1 + fi + + _saveaccountconf LINODE_V4_API_KEY "$LINODE_V4_API_KEY" +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=12345 +_get_root() { + domain=$1 + i=2 + p=1 + + if _rest GET; then + response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "$response" | _egrep_o "{.*\"domain\":\s*\"$h\".*}")" + if [ "$hostedzone" ]; then + _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + fi + return 1 +} + +#method method action data +_rest() { + mtd="$1" + ep="$2" + data="$3" + + _debug mtd "$mtd" + _debug ep "$ep" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + export _H3="Authorization: Bearer $LINODE_V4_API_KEY" + + if [ "$mtd" != "GET" ]; then + # both POST and DELETE. + _debug data "$data" + response="$(_post "$data" "$LINODE_V4_API_URL$ep" "" "$mtd")" + else + response="$(_get "$LINODE_V4_API_URL$ep$data")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} From 2671af13cdfb592f56c5de1ffdbe438f79db72d8 Mon Sep 17 00:00:00 2001 From: Ketil Date: Thu, 27 Dec 2018 15:17:19 +0100 Subject: [PATCH 46/64] Bugfix for allowing '+' character in CloudFlare email --- dnsapi/dns_cf.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index f50ab498..021094d8 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -34,7 +34,7 @@ dns_cf_add() { _saveaccountconf_mutable CF_Key "$CF_Key" _saveaccountconf_mutable CF_Email "$CF_Email" - _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '@.' '__')" + _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '+@.' '___')" _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" _debug "First detect the root zone" @@ -105,7 +105,7 @@ dns_cf_rm() { return 1 fi - _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '@.' '__')" + _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '+@.' '___')" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then From 9756adb9336daf7fdfd63ce83dde75aa269708d8 Mon Sep 17 00:00:00 2001 From: Ketil Date: Thu, 27 Dec 2018 15:45:19 +0100 Subject: [PATCH 47/64] Fixed spelling of 'tigger' to 'Trigger'. --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index acda4bee..c80d8aa3 100755 --- a/acme.sh +++ b/acme.sh @@ -3443,7 +3443,7 @@ __get_domain_new_authz() { #uri keyAuthorization __trigger_validation() { - _debug2 "tigger domain validation." + _debug2 "Trigger domain validation." _t_url="$1" _debug2 _t_url "$_t_url" _t_key_authz="$2" From 920cab6f1206abde5e8ced352bdd3908a2950534 Mon Sep 17 00:00:00 2001 From: Ketil Date: Thu, 27 Dec 2018 16:06:41 +0100 Subject: [PATCH 48/64] Added fixes to also use BuyPass Go ACME server --- acme.sh | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/acme.sh b/acme.sh index acda4bee..aa2adc6c 100755 --- a/acme.sh +++ b/acme.sh @@ -13,6 +13,8 @@ _SCRIPT_="$0" _SUB_FOLDERS="dnsapi deploy" +BUYPASS_CA="https://api.buypass.no/acme/directory" + LETSENCRYPT_CA_V1="https://acme-v01.api.letsencrypt.org/directory" LETSENCRYPT_STAGING_CA_V1="https://acme-staging.api.letsencrypt.org/directory" @@ -1820,9 +1822,12 @@ _send_signed_request() { _debug3 _request_retry_times "$_request_retry_times" if [ -z "$_CACHED_NONCE" ]; then _headers="" - if [ "$ACME_NEW_NONCE" ]; then - _debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$ACME_NEW_NONCE" + if [ "$ACME_NEW_NONCE" ] || [ "$BUYPASS" ]; then nonceurl="$ACME_NEW_NONCE" + if [ "$BUYPASS" ]; then + nonceurl=$url + fi + _debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$nonceurl" if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then _headers="$(cat "$HTTP_HEADER")" fi @@ -1875,7 +1880,7 @@ _send_signed_request() { sig="$(printf "%s" "$_sig_t" | _url_replace)" _debug3 sig "$sig" - if [ "$ACME_VERSION" = "2" ]; then + if [ "$ACME_VERSION" = "2" ] || [ "$BUYPASS" ]; then body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" else body="{\"header\": $JWK_HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" @@ -2330,6 +2335,13 @@ _initAPI() { fi export ACME_AGREEMENT + BUYPASS=$(echo "$_api_server" | _egrep_o 'buypass') + if [ "$BUYPASS" ]; then + BUYPASS=1 + fi + export BUYPASS + _debug "BUYPASS" "$BUYPASS" + _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE" _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ" _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER" @@ -3448,10 +3460,16 @@ __trigger_validation() { _debug2 _t_url "$_t_url" _t_key_authz="$2" _debug2 _t_key_authz "$_t_key_authz" + _t_vtype="$3" + _debug2 _t_vtype "$_t_vtype" if [ "$ACME_VERSION" = "2" ]; then _send_signed_request "$_t_url" "{\"keyAuthorization\": \"$_t_key_authz\"}" else - _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}" + if [ "$BUYPASS" ]; then + _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}" + else + _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}" + fi fi } @@ -4038,7 +4056,7 @@ $_authorizations_map" fi fi - if ! __trigger_validation "$uri" "$keyauthorization"; then + if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then _err "$d:Can not get challenge: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup @@ -4047,14 +4065,18 @@ $_authorizations_map" fi if [ "$code" ] && [ "$code" != '202' ]; then - if [ "$ACME_VERSION" = "2" ] && [ "$code" = '200' ]; then + if [ "$BUYPASS" ] && [ "$code" = '200' ]; then _debug "trigger validation code: $code" else - _err "$d:Challenge error: $response" - _clearupwebbroot "$_currentRoot" "$removelevel" "$token" - _clearup - _on_issue_err "$_post_hook" "$vlist" - return 1 + if [ "$ACME_VERSION" = "2" ] && [ "$code" = '200' ]; then + _debug "trigger validation code: $code" + else + _err "$d:Challenge error: $response" + _clearupwebbroot "$_currentRoot" "$removelevel" "$token" + _clearup + _on_issue_err "$_post_hook" "$vlist" + return 1 + fi fi fi From 8bd12ed040c65fcc5b4919c1f81835c579da6314 Mon Sep 17 00:00:00 2001 From: Ketil Date: Fri, 28 Dec 2018 09:22:31 +0100 Subject: [PATCH 49/64] Rewrite to remove BuyPass spesific fixes and adapt ACME v1 --- acme.sh | 44 +++++++++++--------------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/acme.sh b/acme.sh index aa2adc6c..0ec08511 100755 --- a/acme.sh +++ b/acme.sh @@ -1822,12 +1822,9 @@ _send_signed_request() { _debug3 _request_retry_times "$_request_retry_times" if [ -z "$_CACHED_NONCE" ]; then _headers="" - if [ "$ACME_NEW_NONCE" ] || [ "$BUYPASS" ]; then + if [ "$ACME_NEW_NONCE" ]; then + _debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$ACME_NEW_NONCE" nonceurl="$ACME_NEW_NONCE" - if [ "$BUYPASS" ]; then - nonceurl=$url - fi - _debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$nonceurl" if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then _headers="$(cat "$HTTP_HEADER")" fi @@ -1880,11 +1877,7 @@ _send_signed_request() { sig="$(printf "%s" "$_sig_t" | _url_replace)" _debug3 sig "$sig" - if [ "$ACME_VERSION" = "2" ] || [ "$BUYPASS" ]; then - body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" - else - body="{\"header\": $JWK_HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" - fi + body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" _debug3 body "$body" response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")" @@ -2335,13 +2328,6 @@ _initAPI() { fi export ACME_AGREEMENT - BUYPASS=$(echo "$_api_server" | _egrep_o 'buypass') - if [ "$BUYPASS" ]; then - BUYPASS=1 - fi - export BUYPASS - _debug "BUYPASS" "$BUYPASS" - _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE" _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ" _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER" @@ -3455,7 +3441,7 @@ __get_domain_new_authz() { #uri keyAuthorization __trigger_validation() { - _debug2 "tigger domain validation." + _debug2 "Trigger domain validation." _t_url="$1" _debug2 _t_url "$_t_url" _t_key_authz="$2" @@ -3465,11 +3451,7 @@ __trigger_validation() { if [ "$ACME_VERSION" = "2" ]; then _send_signed_request "$_t_url" "{\"keyAuthorization\": \"$_t_key_authz\"}" else - if [ "$BUYPASS" ]; then - _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}" - else - _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}" - fi + _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}" fi } @@ -4065,18 +4047,14 @@ $_authorizations_map" fi if [ "$code" ] && [ "$code" != '202' ]; then - if [ "$BUYPASS" ] && [ "$code" = '200' ]; then + if [ "$code" = '200' ]; then _debug "trigger validation code: $code" else - if [ "$ACME_VERSION" = "2" ] && [ "$code" = '200' ]; then - _debug "trigger validation code: $code" - else - _err "$d:Challenge error: $response" - _clearupwebbroot "$_currentRoot" "$removelevel" "$token" - _clearup - _on_issue_err "$_post_hook" "$vlist" - return 1 - fi + _err "$d:Challenge error: $response" + _clearupwebbroot "$_currentRoot" "$removelevel" "$token" + _clearup + _on_issue_err "$_post_hook" "$vlist" + return 1 fi fi From 65a2f789dc415c518d16d5c50e9d0d28919eb5ff Mon Sep 17 00:00:00 2001 From: Ketil Date: Fri, 28 Dec 2018 13:26:20 +0100 Subject: [PATCH 50/64] Removing BUYPASS_CA variable --- acme.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/acme.sh b/acme.sh index 0ec08511..422383b6 100755 --- a/acme.sh +++ b/acme.sh @@ -13,8 +13,6 @@ _SCRIPT_="$0" _SUB_FOLDERS="dnsapi deploy" -BUYPASS_CA="https://api.buypass.no/acme/directory" - LETSENCRYPT_CA_V1="https://acme-v01.api.letsencrypt.org/directory" LETSENCRYPT_STAGING_CA_V1="https://acme-staging.api.letsencrypt.org/directory" From b32071ad049e3235adebd87f64b1899bbc3d21f2 Mon Sep 17 00:00:00 2001 From: neilpang Date: Fri, 28 Dec 2018 22:12:54 +0800 Subject: [PATCH 51/64] remove unused code --- acme.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/acme.sh b/acme.sh index 422383b6..dd718352 100755 --- a/acme.sh +++ b/acme.sh @@ -36,7 +36,6 @@ _OLD_STAGE_CA_HOST="https://acme-staging.api.letsencrypt.org" VTYPE_HTTP="http-01" VTYPE_DNS="dns-01" VTYPE_TLS="tls-sni-01" -VTYPE_TLS2="tls-sni-02" VTYPE_ALPN="tls-alpn-01" LOCAL_ANY_ADDRESS="0.0.0.0" @@ -3700,11 +3699,7 @@ $_authorizations_map" fi if [ "$_currentRoot" = "$W_TLS" ]; then - if [ "$ACME_VERSION" = "2" ]; then - vtype="$VTYPE_TLS2" - else - vtype="$VTYPE_TLS" - fi + vtype="$VTYPE_TLS" fi if [ "$_currentRoot" = "$W_ALPN" ]; then From 7ba9a5972dd904b1b5834e7ef1a6a3323d1e29aa Mon Sep 17 00:00:00 2001 From: neilpang Date: Fri, 28 Dec 2018 22:45:40 +0800 Subject: [PATCH 52/64] revert fix for https://github.com/Neilpang/acme.sh/issues/1941 1. fix https://github.com/Neilpang/acme.sh/issues/1977 2. The cache is too long to as a line to save in the conf --- dnsapi/dns_cf.sh | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index 021094d8..202385fe 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -34,9 +34,6 @@ dns_cf_add() { _saveaccountconf_mutable CF_Key "$CF_Key" _saveaccountconf_mutable CF_Email "$CF_Email" - _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '+@.' '___')" - _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" - _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" @@ -105,16 +102,11 @@ dns_cf_rm() { return 1 fi - _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '+@.' '___')" - _debug "First detect the root zone" if ! _get_root "$fulldomain"; then - _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" _err "invalid domain" return 1 fi - _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" - _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" @@ -154,21 +146,6 @@ dns_cf_rm() { # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { - - _cf_zones="$(_readdomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_")" - _debug2 "_cf_zones" "$_cf_zones" - if [ -z "$_cf_zones" ]; then - _debug "$_DOMAIN_CF_ZONES_CACHE_NAME_ is none, so get it." - if ! _cf_rest GET "zones"; then - return 1 - fi - _cf_zones="$response" - _savedomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" "$(echo "$_cf_zones" | _base64)" - else - _debug "$_DOMAIN_CF_ZONES_CACHE_NAME_ found" - _cf_zones="$(echo "$_cf_zones" | _dbase64)" - fi - domain=$1 i=2 p=1 @@ -180,8 +157,12 @@ _get_root() { return 1 fi - if _contains "$_cf_zones" "\"name\":\"$h\"" >/dev/null; then - _domain_id=$(echo "$_cf_zones" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "^\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") + if ! _cf_rest GET "zones?name=$h"; then + return 1 + fi + + if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h From ec67a1b2c12c4fa1bdb28feb870853652b7bbe07 Mon Sep 17 00:00:00 2001 From: neilpang Date: Fri, 28 Dec 2018 22:52:40 +0800 Subject: [PATCH 53/64] Do not limit the renew days to 60, it's just a default value. buypass support 180 days. --- acme.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acme.sh b/acme.sh index dd718352..aae16f07 100755 --- a/acme.sh +++ b/acme.sh @@ -40,7 +40,7 @@ VTYPE_ALPN="tls-alpn-01" LOCAL_ANY_ADDRESS="0.0.0.0" -MAX_RENEW=60 +DEFAULT_RENEW=60 DEFAULT_DNS_SLEEP=120 @@ -4267,8 +4267,8 @@ $_authorizations_map" Le_CertCreateTimeStr=$(date -u) _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr" - if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ] || [ "$Le_RenewalDays" -gt "$MAX_RENEW" ]; then - Le_RenewalDays="$MAX_RENEW" + if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then + Le_RenewalDays="$DEFAULT_RENEW" else _savedomainconf "Le_RenewalDays" "$Le_RenewalDays" fi @@ -5520,7 +5520,7 @@ Parameters: --useragent Specifies the user agent string. it will be saved for future use too. --accountemail Specifies the account email, only valid for the '--install' and '--update-account' command. --accountkey Specifies the account key path, only valid for the '--install' command. - --days Specifies the days to renew the cert when using '--issue' command. The max value is $MAX_RENEW days. + --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses. From c4094c68ee44cd9651fb1effe38ce4a1c97b1cee Mon Sep 17 00:00:00 2001 From: neilpang Date: Fri, 28 Dec 2018 23:04:40 +0800 Subject: [PATCH 54/64] Support BuyPass.com CA --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6a1cf3a4..50d2cfbe 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ For all build statuses, check our [weekly build project](https://github.com/Neil https://github.com/Neilpang/acmetest +# Supported CA + +- Letsencrypt.org CA(default) +- [BuyPass.com CA](https://github.com/Neilpang/acme.sh/wiki/BuyPass.com-CA) # Supported modes From c9baca79109de3d55a6ced143b01e4ef7a07729a Mon Sep 17 00:00:00 2001 From: neilpang Date: Fri, 28 Dec 2018 23:12:16 +0800 Subject: [PATCH 55/64] add tls-alpn mode --- README.md | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 50d2cfbe..2d31c678 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ https://github.com/Neilpang/acmetest - Webroot mode - Standalone mode +- Standalone tls-alpn mode - Apache mode - Nginx mode - DNS mode @@ -225,8 +226,20 @@ 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 -# 5. Use Apache mode +**(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)** @@ -246,7 +259,7 @@ 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 +# 7. Use Nginx mode **(requires you to be root/sudoer, since it is required to interact with Nginx server)** @@ -270,7 +283,7 @@ 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 +# 8. Automatic DNS API integration If your DNS provider supports API access, we can use that API to automatically issue the certs. @@ -348,7 +361,7 @@ If your DNS provider is not on the supported list above, you can write your own For more details: [How to use DNS API](dnsapi) -# 8. Use DNS manual mode: +# 9. Use DNS manual mode: See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first. @@ -384,7 +397,7 @@ Ok, it's done. **Please use dns api mode instead.** -# 9. Issue ECC certificates +# 10. Issue ECC certificates `Let's Encrypt` can now issue **ECDSA** certificates. @@ -416,7 +429,7 @@ Valid values are: -# 10. Issue Wildcard certificates +# 11. Issue Wildcard certificates It's simple, just give a wildcard domain as the `-d` parameter. @@ -426,7 +439,7 @@ acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf -# 11. How to renew the certs +# 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. @@ -443,7 +456,7 @@ acme.sh --renew -d example.com --force --ecc ``` -# 12. How to stop cert renewal +# 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: @@ -456,7 +469,7 @@ 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` +# 14. How to upgrade `acme.sh` acme.sh is in constant development, so it's strongly recommended to use the latest code. @@ -481,25 +494,25 @@ acme.sh --upgrade --auto-upgrade 0 ``` -# 14. Issue a cert from an existing CSR +# 15. Issue a cert from an existing CSR https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR -# 15. Under the Hood +# 16. Under the Hood Speak ACME language using shell, directly to "Let's Encrypt". TODO: -# 16. Acknowledgments +# 17. Acknowledgments 1. Acme-tiny: https://github.com/diafygi/acme-tiny 2. ACME protocol: https://github.com/ietf-wg-acme/acme -# 17. License & Others +# 18. License & Others License is GPLv3 @@ -508,7 +521,7 @@ 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 +# 19. Donate Your donation makes **acme.sh** better: 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) From 29a5311ae0cb82799d404efb8dc1b4c3cc7ff14d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20R=C3=B6ttsches?= Date: Fri, 28 Dec 2018 00:45:51 +0200 Subject: [PATCH 56/64] [dnsapi] Support adding / removing multiple TXT values for Gandi Gandi supports setting multiple entries by setting multiple array items for the rrset_values field in their API. Modify the dns_gandi_livedns.sh script so that it checks for existing entries, appends new ones if needed, and removes existing ones individually. This enabled wildcard certificate support on Gandi. Fixes the dns_gandi_livedns part of #1261. Tested for creating a multidomain, multiple wild-card certificate on Gandi and using a test script executing only the dns_gandi_livedns_add and dns_gandi_livedns_rm functions. --- dnsapi/dns_gandi_livedns.sh | 60 ++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) mode change 100755 => 100644 dnsapi/dns_gandi_livedns.sh diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh old mode 100755 new mode 100644 index 7a21aba6..cdda4775 --- a/dnsapi/dns_gandi_livedns.sh +++ b/dnsapi/dns_gandi_livedns.sh @@ -7,6 +7,7 @@ # Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable # #Author: Frédéric Crozat +# Dominik Röttsches #Report Bugs here: https://github.com/fcrozat/acme.sh # ######## Public functions ##################### @@ -36,9 +37,7 @@ dns_gandi_livedns_add() { _debug domain "$_domain" _debug sub_domain "$_sub_domain" - _gandi_livedns_rest PUT "domains/$_domain/records/$_sub_domain/TXT" "{\"rrset_ttl\": 300, \"rrset_values\":[\"$txtvalue\"]}" \ - && _contains "$response" '{"message": "DNS Record Created"}' \ - && _info "Add $(__green "success")" + _dns_gandi_append_record "$_domain" "$_sub_domain" "$txtvalue" } #Usage: fulldomain txtvalue @@ -56,9 +55,23 @@ dns_gandi_livedns_rm() { _debug fulldomain "$fulldomain" _debug domain "$_domain" _debug sub_domain "$_sub_domain" + _debug txtvalue "$txtvalue" - _gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT" "" + if ! _dns_gandi_existing_rrset_values "$_domain" "$_sub_domain"; then + return 1 + fi + _new_rrset_values=$(echo "$_rrset_values" | sed "s/...$txtvalue...//g") + # Cleanup dangling commata. + _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, ,/ ,/g") + _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, *\]/\]/g") + _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/\[ *,/\[/g") + _debug "New rrset_values" "$_new_rrset_values" + _gandi_livedns_rest PUT \ + "domains/$_domain/records/$_sub_domain/TXT" \ + "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" \ + && _contains "$response" '{"message": "DNS Record Created"}' \ + && _info "Removing record $(__green "success")" } #################### Private functions below ################################## @@ -98,6 +111,45 @@ _get_root() { return 1 } +_dns_gandi_append_record() { + domain=$1 + sub_domain=$2 + txtvalue=$3 + + if _dns_gandi_existing_rrset_values "$domain" "$sub_domain"; then + _debug "Appending new value" + _rrset_values=$(echo "$_rrset_values" | sed "s/\"]/\",\"$txtvalue\"]/") + else + _debug "Creating new record" "$_rrset_values" + _rrset_values="[\"$txtvalue\"]" + fi + _debug new_rrset_values "$_rrset_values" + _gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \ + "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" \ + && _contains "$response" '{"message": "DNS Record Created"}' \ + && _info "Adding record $(__green "success")" +} + +_dns_gandi_existing_rrset_values() { + domain=$1 + sub_domain=$2 + if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then + return 1 + fi + if ! _contains "$response" '"rrset_type": "TXT"'; then + _debug "Does not have a _acme-challenge TXT record yet." + return 1 + fi + if _contains "$response" '"rrset_values": \[\]'; then + _debug "Empty rrset_values for TXT record, no previous TXT record." + return 1 + fi + _debug "Already has TXT record." + _rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' \ + | _egrep_o '\[".*\"]') + return 0 +} + _gandi_livedns_rest() { m=$1 ep="$2" From 68c5c366f4bc0ab421bd2004e40718b7561c984f Mon Sep 17 00:00:00 2001 From: Ben Edmunds Date: Sun, 30 Dec 2018 03:13:23 +0000 Subject: [PATCH 57/64] dnsapi update dynu for api v2 --- dnsapi/dns_dynu.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/dnsapi/dns_dynu.sh b/dnsapi/dns_dynu.sh index 17a1cdb0..506ef53e 100644 --- a/dnsapi/dns_dynu.sh +++ b/dnsapi/dns_dynu.sh @@ -10,7 +10,7 @@ Dynu_Token="" # #Endpoint -Dynu_EndPoint="https://api.dynu.com/v1" +Dynu_EndPoint="https://api.dynu.com/v2" # #Author: Dynu Systems, Inc. #Report Bugs here: https://github.com/shar0119/acme.sh @@ -51,11 +51,11 @@ dns_dynu_add() { _debug _domain_name "$_domain_name" _info "Creating TXT record." - if ! _dynu_rest POST "dns/record/add" "{\"domain_name\":\"$_domain_name\",\"node_name\":\"$_node\",\"record_type\":\"TXT\",\"text_data\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then + if ! _dynu_rest POST "dns/$dnsId/record" "{\"domainId\":\"$dnsId\",\"nodeName\":\"$_node\",\"recordType\":\"TXT\",\"textData\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then return 1 fi - if ! _contains "$response" "text_data"; then + if ! _contains "$response" "200"; then _err "Could not add TXT record." return 1 fi @@ -132,11 +132,12 @@ _get_root() { return 1 fi - if ! _dynu_rest GET "dns/get/$h"; then + if ! _dynu_rest GET "dns/getroot/$h"; then return 1 fi - if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + if _contains "$response" "\"domainName\":\"$h\"" >/dev/null; then + dnsId=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 2 | cut -d : -f 2) _domain_name=$h _node=$(printf "%s" "$domain" | cut -d . -f 1-$p) return 0 @@ -152,7 +153,7 @@ _get_recordid() { fulldomain=$1 txtvalue=$2 - if ! _dynu_rest GET "dns/record/get?hostname=$fulldomain&rrtype=TXT"; then + if ! _dynu_rest GET "dns/$dnsId/record"; then return 1 fi @@ -161,19 +162,18 @@ _get_recordid() { return 0 fi - _dns_record_id=$(printf "%s" "$response" | _egrep_o "{[^}]*}" | grep "\"text_data\":\"$txtvalue\"" | _egrep_o ",[^,]*," | grep ',"id":' | tr -d ",," | cut -d : -f 2) - + _dns_record_id=$(printf "%s" "$response" | sed -e 's/[^{]*\({[^}]*}\)[^{]*/\1\n/g' | grep "\"textData\":\"$txtvalue\"" | sed -e 's/.*"id":\([^,]*\).*/\1/') return 0 } _delete_txt_record() { _dns_record_id=$1 - if ! _dynu_rest GET "dns/record/delete/$_dns_record_id"; then + if ! _dynu_rest DELETE "dns/$dnsId/record/$_dns_record_id"; then return 1 fi - if ! _contains "$response" "true"; then + if ! _contains "$response" "200"; then return 1 fi @@ -189,7 +189,7 @@ _dynu_rest() { export _H1="Authorization: Bearer $Dynu_Token" export _H2="Content-Type: application/json" - if [ "$data" ]; then + if [ "$data" ] || [ "$m" = "DELETE" ]; then _debug data "$data" response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")" else @@ -216,8 +216,8 @@ _dynu_authentication() { _err "Authentication failed." return 1 fi - if _contains "$response" "accessToken"; then - Dynu_Token=$(printf "%s" "$response" | tr -d "[]" | cut -d , -f 2 | cut -d : -f 2 | cut -d '"' -f 2) + if _contains "$response" "access_token"; then + Dynu_Token=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2) fi if _contains "$Dynu_Token" "null"; then Dynu_Token="" From cd4f29135b8f66d5dc8a34833712ad90eceefe9f Mon Sep 17 00:00:00 2001 From: Oliver Dick Date: Wed, 2 Jan 2019 16:44:11 +0100 Subject: [PATCH 58/64] waiting for API zoneStatus active --- dnsapi/dns_hostingde.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/dnsapi/dns_hostingde.sh b/dnsapi/dns_hostingde.sh index 39bcfb63..317ebeda 100644 --- a/dnsapi/dns_hostingde.sh +++ b/dnsapi/dns_hostingde.sh @@ -74,8 +74,26 @@ _hostingde_getZoneConfig() { return $returnCode } +_hostingde_getZoneStatus() { + _debug "Checking Zone status" + curData="{\"filter\":{\"field\":\"zoneConfigId\",\"value\":\"${zoneConfigId}\"},\"limit\":1,\"authToken\":\"${HOSTINGDE_APIKEY}\"}" + curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind")" + _debug "Calling zonesFind '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind'" + _debug "Result of zonesFind '$curResult'" + zoneStatus=$(echo "${curResult}" | grep -v success | _egrep_o '"status":.*' | cut -d ':' -f 2 | cut -d '"' -f 2) + _debug "zoneStatus '${zoneStatus}'" + return 0 +} + _hostingde_addRecord() { _info "Adding record to zone" + _hostingde_getZoneStatus + _debug "Result of zoneStatus: '${zoneStatus}'" + while [ "${zoneStatus}" != "active" ]; do + sleep 5 + _hostingde_getZoneStatus + _debug "Result of zoneStatus: '${zoneStatus}'" + done curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}" curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")" _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'" @@ -93,6 +111,13 @@ _hostingde_addRecord() { _hostingde_removeRecord() { _info "Removing record from zone" + _hostingde_getZoneStatus + _debug "Result of zoneStatus: '$zoneStatus'" + while [ "$zoneStatus" != "active" ]; do + sleep 5 + _hostingde_getZoneStatus + _debug "Result of zoneStatus: '$zoneStatus'" + done curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}" curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")" _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'" From ecf7dded07144d81aba47addee52eae0c072dbd0 Mon Sep 17 00:00:00 2001 From: Ivru Date: Thu, 3 Jan 2019 08:39:51 +0100 Subject: [PATCH 59/64] Fix typo --- dnsapi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 201deeec..9358eb4b 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -1122,7 +1122,7 @@ export EXOSCALE_SECRET_KEY='xxx' Now, let's issue a cert: ``` -acme.sh --issue --dns dns_netcup -d example.com -d www.example.com +acme.sh --issue --dns dns_exoscale -d example.com -d www.example.com ``` The `EXOSCALE_API_KEY` and `EXOSCALE_SECRET_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. From 089823785e1aa4b1cb4d3b4725bab2920a97ff02 Mon Sep 17 00:00:00 2001 From: Oliver Dick Date: Thu, 3 Jan 2019 10:32:59 +0100 Subject: [PATCH 60/64] Using _sleep() instead of sleep --- dnsapi/dns_hostingde.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_hostingde.sh b/dnsapi/dns_hostingde.sh index 317ebeda..74a472d2 100644 --- a/dnsapi/dns_hostingde.sh +++ b/dnsapi/dns_hostingde.sh @@ -90,7 +90,7 @@ _hostingde_addRecord() { _hostingde_getZoneStatus _debug "Result of zoneStatus: '${zoneStatus}'" while [ "${zoneStatus}" != "active" ]; do - sleep 5 + _sleep 5 _hostingde_getZoneStatus _debug "Result of zoneStatus: '${zoneStatus}'" done @@ -114,7 +114,7 @@ _hostingde_removeRecord() { _hostingde_getZoneStatus _debug "Result of zoneStatus: '$zoneStatus'" while [ "$zoneStatus" != "active" ]; do - sleep 5 + _sleep 5 _hostingde_getZoneStatus _debug "Result of zoneStatus: '$zoneStatus'" done From 40f0238bb79a14ed884a520b24cb5cbcdbd3aa08 Mon Sep 17 00:00:00 2001 From: Marton Szucs Date: Thu, 3 Jan 2019 11:32:43 +0100 Subject: [PATCH 61/64] fix dns_loopia wildcard certificate Checks if a subdomain already exists before creating one. The loopia API clears all records for a subdomain when adding it again. Adding TXT-records instead of updating the existing record when using the add method. Wildcard certificates require multiple TXT-records for the same subdomain. Now you can create wildcard certificates using: `acme.sh --issue -d example.com -d '*.example.com' --dns dns_loopia` Double quoting variables --- dnsapi/dns_loopia.sh | 77 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh index 5d761187..ece5ef8c 100644 --- a/dnsapi/dns_loopia.sh +++ b/dnsapi/dns_loopia.sh @@ -38,8 +38,8 @@ dns_loopia_add() { _info "Adding record" - _loopia_add_record "$_domain" "$_sub_domain" - _loopia_update_record "$_domain" "$_sub_domain" "$txtvalue" + _loopia_add_sub_domain "$_domain" "$_sub_domain" + _loopia_add_record "$_domain" "$_sub_domain" "$txtvalue" } @@ -96,6 +96,37 @@ dns_loopia_rm() { #################### Private functions below ################################## +_loopia_get_records() { + domain=$1 + sub_domain=$2 + + xml_content=$(printf ' + + getZoneRecords + + + %s + + + %s + + + %s + + + %s + + + ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain") + + response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" + if ! _contains "$response" ""; then + _err "Error" + return 1 + fi + return 0 +} + _get_root() { domain=$1 _debug "get root" @@ -137,14 +168,14 @@ _get_root() { } -_loopia_update_record() { +_loopia_add_record() { domain=$1 sub_domain=$2 txtval=$3 xml_content=$(printf ' - updateZoneRecord + addZoneRecord %s @@ -176,10 +207,6 @@ _loopia_update_record() { rdata %s - - record_id - 0 - @@ -194,10 +221,42 @@ _loopia_update_record() { return 0 } -_loopia_add_record() { +_sub_domain_exists() { domain=$1 sub_domain=$2 + xml_content=$(printf ' + + getSubdomains + + + %s + + + %s + + + %s + + + ' $LOOPIA_User $LOOPIA_Password "$domain") + + response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" + + if _contains "$response" "$sub_domain"; then + return 0 + fi + return 1 +} + +_loopia_add_sub_domain() { + domain=$1 + sub_domain=$2 + + if _sub_domain_exists "$domain" "$sub_domain"; then + return 0 + fi + xml_content=$(printf ' addSubdomain From 68d9aad3a26eacb0d57d9c5fe66f59571bddab69 Mon Sep 17 00:00:00 2001 From: hebbet Date: Thu, 3 Jan 2019 14:19:55 +0100 Subject: [PATCH 62/64] add link to profile of Cloudflare follow-up for #1893 --- dnsapi/dns_cf.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index 202385fe..532199f3 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -19,8 +19,8 @@ dns_cf_add() { if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then CF_Key="" CF_Email="" - _err "You didn't specify a cloudflare api key and email yet." - _err "Please create the key and try again." + _err "You didn't specify a Cloudflare api key and email yet." + _err "You can get yours from here https://dash.cloudflare.com/profile." return 1 fi @@ -97,8 +97,8 @@ dns_cf_rm() { if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then CF_Key="" CF_Email="" - _err "You didn't specify a cloudflare api key and email yet." - _err "Please create the key and try again." + _err "You didn't specify a Cloudflare api key and email yet." + _err "You can get yours from here https://dash.cloudflare.com/profile." return 1 fi From ad613e2437f0cb4cb476af6c61c5975cf1212db8 Mon Sep 17 00:00:00 2001 From: neilpang Date: Fri, 4 Jan 2019 22:40:59 +0800 Subject: [PATCH 63/64] fix alpn oid. https://github.com/Neilpang/acme.sh/issues/2005 --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index aae16f07..8b7fa70b 100755 --- a/acme.sh +++ b/acme.sh @@ -1089,7 +1089,7 @@ _createcsr() { fi if [ "$acmeValidationv1" ]; then - printf "\n1.3.6.1.5.5.7.1.30.1=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}" + printf "\n1.3.6.1.5.5.7.1.31=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}" fi _csr_cn="$(_idn "$domain")" From 0483d841e385626e6dc45bc24c8d88a3d5277b62 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 6 Jan 2019 21:05:33 +0800 Subject: [PATCH 64/64] Support Post as Get (#2009) * Support POST as GET https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380 * fix PAG, The newline '\n' in response is removed by _send_signed_request(), to keep it, we just use needbase64 * fix PAG, the cert is muti line * fix format * PAG is only for v2 --- acme.sh | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/acme.sh b/acme.sh index 8b7fa70b..872529f7 100755 --- a/acme.sh +++ b/acme.sh @@ -3651,7 +3651,7 @@ issue() { _authorizations_map="" for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do _debug2 "_authz_url" "$_authz_url" - if ! response="$(_get "$_authz_url")"; then + if ! _send_signed_request "$_authz_url"; then _err "get to authz error." _err "_authorizations_seg" "$_authorizations_seg" _err "_authz_url" "$_authz_url" @@ -4069,7 +4069,11 @@ $_authorizations_map" _debug "sleep 2 secs to verify" sleep 2 _debug "checking" - response="$(_get "$uri")" + if [ "$ACME_VERSION" = "2" ]; then + _send_signed_request "$uri" + else + response="$(_get "$uri")" + fi if [ "$?" != "0" ]; then _err "$d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" @@ -4145,13 +4149,16 @@ $_authorizations_map" fi Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)" - if ! _get "$Le_LinkCert" >"$CERT_PATH"; then + _tempSignedResponse="$response" + if ! _send_signed_request "$Le_LinkCert" "" "needbase64"; then _err "Sign failed, can not download cert:$Le_LinkCert." _err "$response" _on_issue_err "$_post_hook" return 1 fi + echo "$response" | _dbase64 "multiline" >"$CERT_PATH" + if [ "$(grep -- "$BEGIN_CERT" "$CERT_PATH" | wc -l)" -gt "1" ]; then _debug "Found cert chain" cat "$CERT_PATH" >"$CERT_FULLCHAIN_PATH" @@ -4161,6 +4168,7 @@ $_authorizations_map" _end_n="$(_math $_end_n + 1)" sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH" fi + response="$_tempSignedResponse" else if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then _err "Sign failed. $response" @@ -4231,7 +4239,8 @@ $_authorizations_map" while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do _debug _link_issuer_retry "$_link_issuer_retry" if [ "$ACME_VERSION" = "2" ]; then - if _get "$Le_LinkIssuer" >"$CA_CERT_PATH"; then + if _send_signed_request "$Le_LinkIssuer"; then + echo "$response" >"$CA_CERT_PATH" break fi else @@ -4957,7 +4966,7 @@ _deactivate() { authzUri="$_authorizations_seg" _debug2 "authzUri" "$authzUri" - if ! response="$(_get "$authzUri")"; then + if ! _send_signed_request "$authzUri"; then _err "get to authz error." _err "_authorizations_seg" "$_authorizations_seg" _err "authzUri" "$authzUri"