From 600a23514029477e794f356f4158ba36b3311a8f Mon Sep 17 00:00:00 2001 From: David Kerr Date: Tue, 31 Jan 2017 23:16:04 -0500 Subject: [PATCH 01/18] Add FreeDNS plugin --- dnsapi/dns_freedns.sh | 371 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100755 dnsapi/dns_freedns.sh diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh new file mode 100755 index 00000000..ad3f9da3 --- /dev/null +++ b/dnsapi/dns_freedns.sh @@ -0,0 +1,371 @@ +#!/usr/bin/env sh + +#This file name is "dns_freedns.sh" +#So, here must be a method dns_freedns_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: David Kerr +#Report Bugs here: https://github.com/dkerr64/acme.sh +# +######## Public functions ##################### + +# Export FreeDNS userid and password in folowing variables... +# FREEDNS_User=username +# FREEDNS_Password=password +# login cookie is saved in acme account config file so userid / pw +# need to be set only when changed. + +#Usage: dns_freedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_freedns_add() { + fulldomain="$1" + txtvalue="$2" + + _info "Add TXT record using FreeDNS" + _debug "fulldomain: $fulldomain" + _debug "txtvalue: $txtvalue" + + if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then + FREEDNS_User="" + FREEDNS_Password="" + if [ -z "$FREEDNS_COOKIE" ]; then + _err "You did not specify the FreeDNS username and password yet." + _err "Please export as FREEDNS_User / FREEDNS_Password and try again." + return 1 + fi + using_cached_cookies="true" + else + FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")" + if [ -z "$FREEDNS_COOKIE" ]; then + return 1 + fi + using_cached_cookies="false" + fi + + _debug "FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)" + + _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE" + + # split our full domain name into two parts... + i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" + i="$(_math "$i" - 1)" + top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)" + i="$(_math "$i" - 1)" + sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")" + + # Sometimes FreeDNS does not reurn the subdomain page but rather + # returns a page regarding becoming a premium member. This usually + # happens after a period of inactivity. Immediately trying again + # returns the correct subdomain page. So, we will try twice to + # load the page and obtain our domain ID + attempts=2 + while [ "$attempts" -gt "0" ]; do + attempts="$(_math "$attempts" - 1)" + + htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" + if [ "$?" != "0" ]; then + if [ "$using_cached_cookies" = "true" ]; then + _err "Has your FreeDNS username and password channged? If so..." + _err "Please export as FREEDNS_User / FREEDNS_Password and try again." + fi + return 1 + fi + + # Now convert the tables in the HTML to CSV. This litte gem from + # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv + subdomain_csv="$(echo "$htmlpage" \ + | grep -i -e ']*>/\n/Ig' \ + | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ + | sed 's/^]*>\|<\/\?T[DH][^>]*>$//Ig' \ + | sed 's/<\/T[DH][^>]*>]*>/,/Ig' \ + | grep 'edit.php?' \ + | grep "$top_domain")" + # The above beauty ends with striping out rows that do not have an + # href to edit.php and do not have the top domain we are looking for. + # So all we should be left with is CSV of table of subdomains we are + # interested in. + + # Now we have to read through this table and extract the data we need + lines="$(echo "$subdomain_csv" | wc -l)" + nl=' +' + i=0 + found=0 + while [ "$i" -lt "$lines" ]; do + i="$(_math "$i" + 1)" + line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" + tmp="$(echo "$line" | cut -d ',' -f 1)" + if [ $found = 0 ] && _startswith "$tmp" "$top_domain"; then + # this line will contain DNSdomainid for the top_domain + DNSdomainid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*domain_id=//;s/>.*//')" + found=1 + else + # lines contain DNS records for all subdomains + DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" + DNStype="$(echo "$line" | cut -d ',' -f 3)" + if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then + DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" + # Now get current value for the TXT record. This method may + # not produce accurate results as the value field is truncated + # on this webpage. To get full value we would need to load + # another page. However we don't really need this so long as + # there is only one TXT record for the acme chalenge subdomain. + DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" + if [ $found != 0 ]; then + break + # we are breaking out of the loop at the first match of DNS name + # and DNS type (if we are past finding the domainid). This assumes + # that there is only ever one TXT record for the LetsEncrypt/acme + # challenge subdomain. This seems to be a reasonable assumption + # as the acme client deletes the TXT record on successful validation. + fi + else + DNSname="" + DNStype="" + fi + fi + done + + _debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid" + _debug "DNSvalue: $DNSvalue" + + if [ -z "$DNSdomainid" ]; then + # If domain ID is empty then something went wrong (top level + # domain not found at FreeDNS). + if [ "$attempts" = "0" ]; then + # exhausted maximum retry attempts + _debug "$htmlpage" + _debug "$subdomain_csv" + _err "Domain $top_domain not found at FreeDNS" + return 1 + fi + else + # break out of the 'retry' loop... we have found our domain ID + break + fi + _info "Domain $top_domain not found at FreeDNS" + _info "Retry loading subdomain page ($attempts attempts remaining)" + done + + if [ -z "$DNSdataid" ]; then + # If data ID is empty then specific subdomain does not exist yet, need + # to create it this should always be the case as the acme client + # deletes the entry after domain is validated. + _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" + return $? + else + if [ "$txtvalue" = "$DNSvalue" ]; then + # if value in TXT record matches value requested then DNS record + # does not need to be updated. But... + # Testing value match fails. Website is truncating the value field. + # So for now we will always go down the else path. Though in theory + # should never come here anyway as the acme client deletes + # the TXT record on successful validation, so we should not even + # have found a TXT record !! + _info "No update necessary for $fulldomain at FreeDNS" + return 0 + else + # Delete the old TXT record (with the wrong value) + _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" + if [ "$?" = "0" ]; then + # And add in new TXT record with the value provided + _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" + fi + return $? + fi + fi + return 0 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_freedns_rm() { + fulldomain="$1" + txtvalue="$2" + + _info "Delete TXT record using FreeDNS" + _debug "fulldomain: $fulldomain" + _debug "txtvalue: $txtvalue" + + # Need to read cookie from conf file again in case new value set + # during login to FreeDNS when TXT record was created. + # acme.sh does not have a _readaccountconf() fuction + FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")" + _debug "FreeDNS login cookies: $FREEDNS_COOKIE" + + # Sometimes FreeDNS does not reurn the subdomain page but rather + # returns a page regarding becoming a premium member. This usually + # happens after a period of inactivity. Immediately trying again + # returns the correct subdomain page. So, we will try twice to + # load the page and obtain our TXT record. + attempts=2 + while [ "$attempts" -gt "0" ]; do + attempts="$(_math "$attempts" - 1)" + + htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" + if [ "$?" != "0" ]; then + return 1 + fi + + # Now convert the tables in the HTML to CSV. This litte gem from + # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv + subdomain_csv="$(echo "$htmlpage" \ + | grep -i -e ']*>/\n/Ig' \ + | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ + | sed 's/^]*>\|<\/\?T[DH][^>]*>$//Ig' \ + | sed 's/<\/T[DH][^>]*>]*>/,/Ig' \ + | grep 'edit.php?' \ + | grep "$fulldomain")" + # The above beauty ends with striping out rows that do not have an + # href to edit.php and do not have the domain name we are looking for. + # So all we should be left with is CSV of table of subdomains we are + # interested in. + + # Now we have to read through this table and extract the data we need + lines="$(echo "$subdomain_csv" | wc -l)" + nl=' +' + i=0 + found=0 + while [ "$i" -lt "$lines" ]; do + i="$(_math "$i" + 1)" + line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" + DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" + DNStype="$(echo "$line" | cut -d ',' -f 3)" + if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then + DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" + DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" + _debug "DNSvalue: $DNSvalue" + # if [ "$DNSvalue" = "$txtvalue" ]; then + # Testing value match fails. Website is truncating the value + # field. So for now we will assume that there is only one TXT + # field for the sub domain and just delete it. Currently this + # is a safe assumption. + _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" + return $? + # fi + fi + done + done + + # If we get this far we did not find a match (after two attempts) + # Not necessarily an error, but log anyway. + _debug2 "$subdomain_csv" + _info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS" + return 0 +} + +#################### Private functions below ################################## + +# usage: _freedns_login username password +# print string "cookie=value" etc. +# returns 0 success +_freedns_login() { + username="$1" + password="$2" + url="https://freedns.afraid.org/zc.php?step=2" + + _debug "Login to FreeDNS as user $username" + + htmlpage="$(_post "username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" "$url")" + + if [ "$?" != "0" ]; then + _err "FreeDNS login failed for user $username bad RC from _post" + return 1 + fi + + cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)" + + # if cookies is not empty then logon successful + if [ -z "$cookies" ]; then + _debug "$htmlpage" + _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file" + return 1 + fi + + printf "%s" "$cookies" + return 0 +} + +# usage _freedns_retrieve_subdomain_page login_cookies +# echo page retrieved (html) +# returns 0 success +_freedns_retrieve_subdomain_page() { + export _H1="Cookie:$1" + url="https://freedns.afraid.org/subdomain/" + + _debug "Retrieve subdmoain page from FreeDNS" + + htmlpage="$(_get "$url")" + + if [ "$?" != "0" ]; then + _err "FreeDNS retrieve subdomins failed bad RC from _get" + return 1 + fi + + if [ -z "$htmlpage" ]; then + _err "FreeDNS returned empty subdomain page" + return 1 + fi + + _debug2 "$htmlpage" + + printf "%s" "$htmlpage" + return 0 +} + +# usage _freedns_add_txt_record login_cookies domain_id subdomain value +# returns 0 success +_freedns_add_txt_record() { + export _H1="Cookie:$1" + domain_id="$2" + subdomain="$3" + value="$(printf '%s' "$4" | _url_encode)" + url="http://freedns.afraid.org/subdomain/save.php?step=2" + + htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")" + + if [ "$?" != "0" ]; then + _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" + return 1 + fi + + if ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then + _debug "$htmlpage" + _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" + return 1 + fi + _info "Added acme challenge TXT record for $fulldomain at FreeDNS" + return 0 +} + +# usage _freedns_delete_txt_record login_cookies data_id +# returns 0 success +_freedns_delete_txt_record() { + export _H1="Cookie:$1" + data_id="$2" + url="https://freedns.afraid.org/subdomain/delete2.php" + + htmlheader="$(_get "$url?data_id%5B%5D=$data_id&submit=delete+selected" "onlyheader")" + + if [ "$?" != "0" ]; then + _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" + return 1 + fi + + if ! _contains "$htmlheader" "200 OK"; then + _debug "$htmlheader" + _err "FreeDNS failed to delete TXT record $data_id" + return 1 + fi + + _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS" + return 0 +} + From 0aed065a75dc3be7c906a156ebad1b30344d09ba Mon Sep 17 00:00:00 2001 From: David Kerr Date: Wed, 1 Feb 2017 17:08:26 -0500 Subject: [PATCH 02/18] Updates to README.md --- README.md | 1 + dnsapi/README.md | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/README.md b/README.md index 90a64ee0..067e9b50 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,7 @@ You don't have to do anything manually! 1. ISPConfig 3.1 API 1. Alwaysdata.com API 1. Linode.com API +1. FreeDNS **More APIs coming soon...** diff --git a/dnsapi/README.md b/dnsapi/README.md index df728acc..9f06483e 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -278,6 +278,32 @@ acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.co The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +# 15. Use FreeDNS + +FreeDNS (https://freedns.afraid.org/) does not provide an API to update DNS records (other than IPv4 and IPv6 +dynamic DNS addresses). The acme.sh plugin therefore retrieves and updates domain TXT records by logging +into the FreeDNS website to read the HTML and posting updates as HTTP. The plugin needs to know your +userid and password for the FreeDNS website. + +```sh +export FREEDNS_User="..." +export FREEDNS_Password="..." +``` + +You need only provide this the first time you run the acme.sh client with FreeDNS validation and then again +whenever you change your password at the FreeDNS site. The acme.sh FreeDNS plugin does not store your userid +or password but rather saves an authentication token returned by FreeDNS in `~/.acme.sh/account.conf` and +reuses that when needed. + +Now you can issue a certificate. + +```sh +acme.sh --issue --dns dns_freedns --dnssleep 30 -d example.com -d www.example.com +``` + +FreeDNS updates records quite quickly so it is possible to reduce the dnssleep time, in the above example +to 30 seconds. + # Use custom API If your API is not supported yet, you can write your own DNS API. From 40e6ba1100e2ed61508131873fa99225748aded4 Mon Sep 17 00:00:00 2001 From: David Kerr Date: Wed, 1 Feb 2017 17:12:52 -0500 Subject: [PATCH 03/18] fix heading level to match others. --- dnsapi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 9f06483e..65acb7d9 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -278,7 +278,7 @@ acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.co The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -# 15. Use FreeDNS +## 15. Use FreeDNS FreeDNS (https://freedns.afraid.org/) does not provide an API to update DNS records (other than IPv4 and IPv6 dynamic DNS addresses). The acme.sh plugin therefore retrieves and updates domain TXT records by logging From 50a9680f17f0719450f3f1874d723d0db2c4a1fb Mon Sep 17 00:00:00 2001 From: David Kerr Date: Fri, 3 Feb 2017 11:13:12 -0500 Subject: [PATCH 04/18] Travis error... remove blank line at end of file. --- dnsapi/dns_freedns.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index ad3f9da3..28aaa77a 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -368,4 +368,3 @@ _freedns_delete_txt_record() { _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS" return 0 } - From e6b940e24754ede06e8b2618d50347bf8f39185b Mon Sep 17 00:00:00 2001 From: David Kerr Date: Fri, 3 Feb 2017 22:59:22 -0500 Subject: [PATCH 05/18] Minor edits to FreeDNS documentation --- README.md | 2 +- dnsapi/README.md | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 067e9b50..c6362ed7 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ You don't have to do anything manually! 1. ISPConfig 3.1 API 1. Alwaysdata.com API 1. Linode.com API -1. FreeDNS +1. FreeDNS (https://freedns.afraid.org/) **More APIs coming soon...** diff --git a/dnsapi/README.md b/dnsapi/README.md index 65acb7d9..fc613e2a 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -278,7 +278,7 @@ acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.co The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -## 15. Use FreeDNS +## 15. Use FreeDNS FreeDNS (https://freedns.afraid.org/) does not provide an API to update DNS records (other than IPv4 and IPv6 dynamic DNS addresses). The acme.sh plugin therefore retrieves and updates domain TXT records by logging @@ -298,12 +298,9 @@ reuses that when needed. Now you can issue a certificate. ```sh -acme.sh --issue --dns dns_freedns --dnssleep 30 -d example.com -d www.example.com +acme.sh --issue --dns dns_freedns -d example.com -d www.example.com ``` -FreeDNS updates records quite quickly so it is possible to reduce the dnssleep time, in the above example -to 30 seconds. - # Use custom API If your API is not supported yet, you can write your own DNS API. From f78b656f5f6c20bde86c10b1bfb40bf2f210d899 Mon Sep 17 00:00:00 2001 From: David Kerr Date: Sat, 4 Feb 2017 10:21:58 -0500 Subject: [PATCH 06/18] Add error message if fails to add TXT record for missing security code (probably a FreeDNS public domain) --- dnsapi/README.md | 4 ++++ dnsapi/dns_freedns.sh | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index fc613e2a..6a86bf4c 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -301,6 +301,10 @@ Now you can issue a certificate. acme.sh --issue --dns dns_freedns -d example.com -d www.example.com ``` +Note that you cannot use acme.sh automatic DNS validation for FreeDNS public domains or for a subdomain that +you create under a FreeDNS public domain. You must own the top level domain in order to automaitcally +validate with acme.sh at FreeDNS. + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 28aaa77a..8d519fd5 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -307,9 +307,7 @@ _freedns_retrieve_subdomain_page() { if [ "$?" != "0" ]; then _err "FreeDNS retrieve subdomins failed bad RC from _get" return 1 - fi - - if [ -z "$htmlpage" ]; then + elif [ -z "$htmlpage" ]; then _err "FreeDNS returned empty subdomain page" return 1 fi @@ -334,13 +332,18 @@ _freedns_add_txt_record() { if [ "$?" != "0" ]; then _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" return 1 - fi - - if ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then + elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then _debug "$htmlpage" _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" return 1 + elif _contains "$htmlpage" "security code was incorrect"; then + _debug "$htmlpage" + _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested seurity code" + _err "Note that you cannot use automatic DNS validation for FreeDNS public domains" + return 1 fi + + _debug2 "$htmlpage" _info "Added acme challenge TXT record for $fulldomain at FreeDNS" return 0 } @@ -357,9 +360,7 @@ _freedns_delete_txt_record() { if [ "$?" != "0" ]; then _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" return 1 - fi - - if ! _contains "$htmlheader" "200 OK"; then + elif ! _contains "$htmlheader" "200 OK"; then _debug "$htmlheader" _err "FreeDNS failed to delete TXT record $data_id" return 1 From 87f5ec5be52208c2daaf040c646bee69fdcb7771 Mon Sep 17 00:00:00 2001 From: David Kerr Date: Sat, 4 Feb 2017 10:36:51 -0500 Subject: [PATCH 07/18] Add Accept-Language:en-US to HTTP header as precaution against future multi-lingual FreeDNS pages. --- dnsapi/dns_freedns.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 8d519fd5..f30c8958 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -267,6 +267,7 @@ dns_freedns_rm() { # print string "cookie=value" etc. # returns 0 success _freedns_login() { + export _H1="Accept-Language:en-US" username="$1" password="$2" url="https://freedns.afraid.org/zc.php?step=2" @@ -298,6 +299,7 @@ _freedns_login() { # returns 0 success _freedns_retrieve_subdomain_page() { export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" url="https://freedns.afraid.org/subdomain/" _debug "Retrieve subdmoain page from FreeDNS" @@ -322,6 +324,7 @@ _freedns_retrieve_subdomain_page() { # returns 0 success _freedns_add_txt_record() { export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" domain_id="$2" subdomain="$3" value="$(printf '%s' "$4" | _url_encode)" @@ -352,6 +355,7 @@ _freedns_add_txt_record() { # returns 0 success _freedns_delete_txt_record() { export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" data_id="$2" url="https://freedns.afraid.org/subdomain/delete2.php" From 9bdb799b412bf86cddbe62d630e94a023fcc532d Mon Sep 17 00:00:00 2001 From: neilpang Date: Sun, 5 Feb 2017 13:16:51 +0800 Subject: [PATCH 08/18] fix bug when the od command is missing --- acme.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index bc211522..df4a0b4a 100755 --- a/acme.sh +++ b/acme.sh @@ -340,11 +340,29 @@ _is_solaris() { _contains "${__OS__:=$(uname -a)}" "solaris" || _contains "${__OS__:=$(uname -a)}" "SunOS" } +#_ascii_hex str +#this can only process ascii chars, should only be used when od command is missing as a backup way. +_ascii_hex() { + _debug2 "Using _ascii_hex" + _str="$1" + _str_len=${#_str} + _h_i=1 + while [ "$_h_i" -le "$_str_len" ]; do + _str_c="$(printf "%s" "$_str" | cut -c "$_h_i")" + printf " %02x" "'$_str_c" + _h_i="$(_math "$_h_i" + 1)" + done +} + #stdin output hexstr splited by one space #input:"abc" #output: " 61 62 63" _hex_dump() { - od -A n -v -t x1 | tr -d "\r\t" | tr -s " " | sed "s/ $//" | tr -d "\n" + #in wired some system, the od command is missing. + if ! od -A n -v -t x1 | tr -d "\r\t" | tr -s " " | sed "s/ $//" | tr -d "\n" 2>/dev/null; then + str=$(cat) + _ascii_hex "$str" + fi } #url encode, no-preserved chars From b22b085b508c745030b9ecd4ce931fca19f43455 Mon Sep 17 00:00:00 2001 From: neilpang Date: Sun, 5 Feb 2017 22:08:52 +0800 Subject: [PATCH 09/18] fix https://github.com/Neilpang/acme.sh/issues/578 support openssl 1.1.0 --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index df4a0b4a..ec41b60f 100755 --- a/acme.sh +++ b/acme.sh @@ -914,7 +914,7 @@ _readSubjectFromCSR() { _usage "_readSubjectFromCSR mycsr.csr" return 1 fi - $OPENSSL_BIN req -noout -in "$_csrfile" -subject | _egrep_o "CN=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n' + $OPENSSL_BIN req -noout -in "$_csrfile" -subject | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n' } #_csrfile From 562a4c056ea208e85e6f0551a188d55cee8050d5 Mon Sep 17 00:00:00 2001 From: neilpang Date: Sun, 5 Feb 2017 23:06:06 +0800 Subject: [PATCH 10/18] add note info if netcat-openbsd is required. --- acme.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/acme.sh b/acme.sh index ec41b60f..b22b2e69 100755 --- a/acme.sh +++ b/acme.sh @@ -61,6 +61,8 @@ DEFAULT_LOG_LEVEL="$LOG_LEVEL_1" _DEBUG_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh" +_PREPARE_LINK="https://github.com/Neilpang/acme.sh/wiki/Install-preparations" + __INTERACTIVE="" if [ -t 1 ]; then __INTERACTIVE="1" @@ -1684,6 +1686,14 @@ _startserver() { _NC="$_NC -6" fi + if [ "$Le_Listen_V4$Le_Listen_V6$ncaddr" ]; then + if ! _contains "$nchelp" "OpenBSD"; then + _err "The nc doesn't support '-4', '-6' or local-address, please install 'netcat-openbsd' and try again." + _err "See $(__green $_PREPARE_LINK)" + return 1 + fi + fi + if echo "$nchelp" | grep "\-q[ ,]" >/dev/null; then _NC="$_NC -q 1 -l $ncaddr" else From b4325026b17374efdbcc7726369085e4373f23ef Mon Sep 17 00:00:00 2001 From: neilpang Date: Sun, 5 Feb 2017 23:14:25 +0800 Subject: [PATCH 11/18] exe --- deploy/kong.sh | 0 deploy/myapi.sh | 0 dnsapi/dns_ad.sh | 0 dnsapi/dns_ali.sh | 0 dnsapi/dns_aws.sh | 0 5 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 deploy/kong.sh mode change 100644 => 100755 deploy/myapi.sh mode change 100644 => 100755 dnsapi/dns_ad.sh mode change 100644 => 100755 dnsapi/dns_ali.sh mode change 100644 => 100755 dnsapi/dns_aws.sh diff --git a/deploy/kong.sh b/deploy/kong.sh old mode 100644 new mode 100755 diff --git a/deploy/myapi.sh b/deploy/myapi.sh old mode 100644 new mode 100755 diff --git a/dnsapi/dns_ad.sh b/dnsapi/dns_ad.sh old mode 100644 new mode 100755 diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh old mode 100644 new mode 100755 diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh old mode 100644 new mode 100755 From 0ca5b7996cddab0e471efcb25313c3d6301cdfb1 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 6 Feb 2017 09:28:30 +0800 Subject: [PATCH 12/18] minor clear account conf --- acme.sh | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/acme.sh b/acme.sh index b22b2e69..08e06fbd 100755 --- a/acme.sh +++ b/acme.sh @@ -3906,12 +3906,7 @@ _detect_profile() { _initconf() { _initpath if [ ! -f "$ACCOUNT_CONF_PATH" ]; then - echo "#ACCOUNT_CONF_PATH=xxxx - -#ACCOUNT_EMAIL=aaa@example.com # the account email used to register account. -#ACCOUNT_KEY_PATH=\"/path/to/account.key\" -#CERT_HOME=\"/path/to/cert/home\" - + echo " #LOG_FILE=\"$DEFAULT_LOG_FILE\" #LOG_LEVEL=1 @@ -3919,12 +3914,6 @@ _initconf() { #AUTO_UPGRADE=\"1\" #NO_TIMESTAMP=1 -#OPENSSL_BIN=openssl - -#USER_AGENT=\"$USER_AGENT\" - -#USER_PATH= - " >"$ACCOUNT_CONF_PATH" fi From dba26c3240efaa6cdd9c6f163f8294242c2bdbfd Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 6 Feb 2017 13:27:58 +0800 Subject: [PATCH 13/18] fix check for Mac nc command, it doesn't contain "openbsd", but it works. --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 08e06fbd..d3d46b59 100755 --- a/acme.sh +++ b/acme.sh @@ -1687,7 +1687,7 @@ _startserver() { fi if [ "$Le_Listen_V4$Le_Listen_V6$ncaddr" ]; then - if ! _contains "$nchelp" "OpenBSD"; then + if ! _contains "$nchelp" "-4"; then _err "The nc doesn't support '-4', '-6' or local-address, please install 'netcat-openbsd' and try again." _err "See $(__green $_PREPARE_LINK)" return 1 From d6edff3182e3e3fe672652aa02324590e7f2057d Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 6 Feb 2017 14:20:37 +0800 Subject: [PATCH 14/18] fix ci --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8d6d30ad..1d54faed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,6 @@ install: fi script: - - echo "TEST_LOCAL=$TEST_LOCAL" - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)" - command -V openssl && openssl version - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt ; fi @@ -44,8 +43,8 @@ script: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck **/*.sh && echo "shellcheck OK" ; fi - cd .. - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest - - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi - - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" OPENSSL_BIN="$OPENSSL_BIN" ./letest.sh ; fi + - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi + - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" OPENSSL_BIN="$OPENSSL_BIN" ./letest.sh ; fi matrix: From 5d2c5b01a80c2cdc555673926e7b865e33297ddd Mon Sep 17 00:00:00 2001 From: neilpang Date: Mon, 6 Feb 2017 19:30:53 +0800 Subject: [PATCH 15/18] add _utc_date function --- acme.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/acme.sh b/acme.sh index d3d46b59..0b234953 100755 --- a/acme.sh +++ b/acme.sh @@ -1265,6 +1265,10 @@ _time() { date -u "+%s" } +_utc_date() { + date -u "+%Y-%m-%d %H:%M:%S" +} + _mktemp() { if _exists mktemp; then if mktemp 2>/dev/null; then From 339a8ad61041591d6cc2bb3af77ed855e13e735d Mon Sep 17 00:00:00 2001 From: neilpang Date: Mon, 6 Feb 2017 19:53:12 +0800 Subject: [PATCH 16/18] minor, output thumbprint --- acme.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/acme.sh b/acme.sh index 0b234953..7ad5897d 100755 --- a/acme.sh +++ b/acme.sh @@ -2489,6 +2489,10 @@ __calcAccountKeyHash() { [ -f "$ACCOUNT_KEY_PATH" ] && _digest sha256 <"$ACCOUNT_KEY_PATH" } +__calc_account_thumbprint() { + printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace +} + #keylength _regAccount() { _initpath @@ -2579,6 +2583,8 @@ _regAccount() { return 1 fi fi + ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)" + _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT" return 0 done @@ -2810,8 +2816,7 @@ issue() { fi if [ -z "$thumbprint" ]; then - accountkey_json=$(printf "%s" "$jwk" | tr -d ' ') - thumbprint=$(printf "%s" "$accountkey_json" | _digest "sha256" | _url_replace) + thumbprint="$(__calc_account_thumbprint)" fi entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" From 0e44f587a5052ece05ad09674c0384809c95dd7e Mon Sep 17 00:00:00 2001 From: neilpang Date: Mon, 6 Feb 2017 20:42:54 +0800 Subject: [PATCH 17/18] add stateless mode --- acme.sh | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index 7ad5897d..cb99b5ab 100755 --- a/acme.sh +++ b/acme.sh @@ -41,6 +41,8 @@ NO_VALUE="no" W_TLS="tls" +MODE_STATELESS="stateless" + STATE_VERIFIED="verified_ok" BEGIN_CSR="-----BEGIN CERTIFICATE REQUEST-----" @@ -63,6 +65,8 @@ _DEBUG_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh" _PREPARE_LINK="https://github.com/Neilpang/acme.sh/wiki/Install-preparations" +_STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode" + __INTERACTIVE="" if [ -t 1 ]; then __INTERACTIVE="1" @@ -2973,7 +2977,9 @@ issue() { serverproc="$!" sleep 1 _debug serverproc "$serverproc" - + elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then + _info "Stateless mode for domain:$d" + _sleep 1 else if [ "$_currentRoot" = "apache" ]; then wellknown_path="$ACME_DIR" @@ -4258,6 +4264,7 @@ Parameters: --webroot, -w /path/to/webroot Specifies the web root folder for web root mode. --standalone Use standalone mode. + --stateless Use stateless mode, see: $_STATELESS_WIKI --tls Use standalone tls mode. --apache Use apache mode. --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file] Use dns mode or dns api. @@ -4563,6 +4570,14 @@ _process() { _webroot="$_webroot,$wvalue" fi ;; + --stateless) + wvalue="$MODE_STATELESS" + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; --local-address) lvalue="$2" _local_address="$_local_address$lvalue," From 7c488b5913f47aecfcc61d8e6d329a4b374387c3 Mon Sep 17 00:00:00 2001 From: neilpang Date: Mon, 6 Feb 2017 21:37:21 +0800 Subject: [PATCH 18/18] doc for stateless mode --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c6362ed7..2dd178d8 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ https://github.com/Neilpang/acmetest - Standalone mode - Apache mode - DNS mode +- [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode) # 1. How to install