Browse Source

Merge pull request #1883 from Neilpang/dev

Dev
neil 6 years ago
parent
commit
4f59a821d3
6 changed files with 348 additions and 9 deletions
  1. 1 0
      README.md
  2. 2 2
      acme.sh
  3. 9 3
      deploy/fritzbox.sh
  4. 26 2
      dnsapi/README.md
  5. 2 2
      dnsapi/dns_cx.sh
  6. 308 0
      dnsapi/dns_namecheap.sh

+ 1 - 0
README.md

@@ -326,6 +326,7 @@ You don't have to do anything manually!
 1. ConoHa (https://www.conoha.jp)
 1. netcup DNS API (https://www.netcup.de)
 1. GratisDNS.dk (https://gratisdns.dk)
+1. Namecheap API (https://www.namecheap.com/)
 
 And: 
 

+ 2 - 2
acme.sh

@@ -1810,14 +1810,14 @@ _send_signed_request() {
     if [ -z "$_CACHED_NONCE" ]; then
       _headers=""
       if [ "$ACME_NEW_NONCE" ]; then
-        _debug2 "Get nonce. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
+        _debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
         nonceurl="$ACME_NEW_NONCE"
         if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then
           _headers="$(cat "$HTTP_HEADER")"
         fi
       fi
       if [ -z "$_headers" ]; then
-        _debug2 "Get nonce. ACME_DIRECTORY" "$ACME_DIRECTORY"
+        _debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY"
         nonceurl="$ACME_DIRECTORY"
         _headers="$(_get "$nonceurl" "onlyheader")"
       fi

+ 9 - 3
deploy/fritzbox.sh

@@ -28,8 +28,10 @@ fritzbox_deploy() {
   _debug _cfullchain "$_cfullchain"
 
   if ! _exists iconv; then
-    _err "iconv not found"
-    return 1
+    if ! _exists perl; then
+      _err "iconv or perl not found"
+      return 1
+    fi
   fi
 
   _fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}"
@@ -61,7 +63,11 @@ fritzbox_deploy() {
 
   _info "Log in to the FRITZ!Box"
   _fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*<Challenge>//' -e 's/<\/Challenge>.*$//')"
-  _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')"
+  if _exists iconv; then
+    _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')"
+  else
+    _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | perl -p -e 'use Encode qw/encode/; print encode("UTF-16LE","$_"); $_="";' | md5sum | awk '{print $1}')"
+  fi
   _fritzbox_sid="$(_get "${_fritzbox_url}/login_sid.lua?sid=0000000000000000&username=${_fritzbox_username}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*<SID>//' -e 's/<\/SID>.*$//')"
 
   if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then

+ 26 - 2
dnsapi/README.md

@@ -972,7 +972,7 @@ The `NC_Apikey`,`NC_Apipw` and `NC_CID` will be saved in `~/.acme.sh/account.con
 
 ## 52. Use GratisDNS.dk
 
-GratisDNS.dk (https://gratisdns.dj/) does not provide an API to update DNS records (other than IPv4 and IPv6
+GratisDNS.dk (https://gratisdns.dk/) 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 GratisDNS website to read the HTML and posting updates as HTTP.  The plugin needs to know your
 userid and password for the GratisDNS website.
@@ -984,10 +984,34 @@ export GDNSDK_Password="..."
 The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
+Now you can issue a certificate.
+
+Note: It usually takes a few minutes (usually 3-4 minutes) before the changes propagates to gratisdns.dk nameservers (ns3.gratisdns.dk often are slow),
+and in rare cases I have seen over 5 minutes before google DNS catches it. Therefor a DNS sleep of at least 300 seconds are recommended-
+
+```sh
+acme.sh --issue --dns dns_gdnsdk --dnssleep 300 -d example.com -d *.example.com
+```
+
+## 53. Use Namecheap
+
+You will need your namecheap username, API KEY (https://www.namecheap.com/support/api/intro.aspx) and your external IP address (or an URL to get it), this IP will need to be whitelisted at Namecheap.
+Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
+
+```sh
+export NAMECHEAP_USERNAME="..."
+export NAMECHEAP_API_KEY="..."
+export NAMECHEAP_SOURCEIP="..."
+```
+
+NAMECHEAP_SOURCEIP can either be an IP address or an URL to provide it (e.g. https://ifconfig.co/ip).
+
+The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
 Now you can issue a certificate.
 
 ```sh
-acme.sh --issue --dns dns_gdnsdk -d example.com -d *.example.com
+acme.sh --issue --dns dns_namecheap -d example.com -d *.example.com
 ```
 
 # Use custom API

+ 2 - 2
dnsapi/dns_cx.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-# Cloudxns.com Domain api
+# CloudXNS Domain api
 #
 #CX_Key="1234"
 #
@@ -19,7 +19,7 @@ dns_cx_add() {
   if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ]; then
     CX_Key=""
     CX_Secret=""
-    _err "You don't specify cloudxns.com  api key or secret yet."
+    _err "You don't specify cloudxns.net  api key or secret yet."
     _err "Please create you key and try again."
     return 1
   fi

+ 308 - 0
dnsapi/dns_namecheap.sh

@@ -0,0 +1,308 @@
+#!/usr/bin/env sh
+
+# Namecheap API
+# https://www.namecheap.com/support/api/intro.aspx
+#
+# Requires Namecheap API key set in NAMECHEAP_API_KEY, NAMECHEAP_SOURCEIP and NAMECHEAP_USERNAME set as environment variable
+# Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
+
+########  Public functions #####################
+
+if [ "$STAGE" -eq 1 ]; then
+  NAMECHEAP_API="https://api.sandbox.namecheap.com/xml.response"
+else
+  NAMECHEAP_API="https://api.namecheap.com/xml.response"
+fi
+
+#Usage: dns_namecheap_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_namecheap_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _namecheap_check_config; then
+    _err "$error"
+    return 1
+  fi
+
+  if ! _namecheap_set_publicip; then
+    return 1
+  fi
+
+  _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"
+
+  _set_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue"
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_namecheap_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _namecheap_set_publicip; then
+    return 1
+  fi
+
+  if ! _namecheap_check_config; then
+    _err "$error"
+    return 1
+  fi
+
+  _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"
+
+  _del_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue"
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+  domain=$1
+
+  if ! _namecheap_post "namecheap.domains.getList"; then
+    _err "$error"
+    return 1
+  fi
+
+  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" "$h"; then
+      _debug "$h not found"
+    else
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain="$h"
+      return 0
+    fi
+    p="$i"
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_namecheap_set_publicip() {
+
+  if [ -z "$NAMECHEAP_SOURCEIP" ]; then
+    _err "No Source IP specified for Namecheap API."
+    _err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
+    return 1
+  else
+    _saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
+    _debug sourceip "$NAMECHEAP_SOURCEIP"
+
+    ip=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
+    addr=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '(http|https)://.*')
+
+    _debug2 ip "$ip"
+    _debug2 addr "$addr"
+
+    if [ -n "$ip" ]; then
+      _publicip="$ip"
+    elif [ -n "$addr" ]; then
+      _publicip=$(_get "$addr")
+    else
+      _err "No Source IP specified for Namecheap API."
+      _err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
+      return 1
+    fi
+  fi
+
+  _debug publicip "$_publicip"
+
+  return 0
+}
+
+_namecheap_post() {
+  command=$1
+  data="ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}"
+
+  response="$(_post "$data" "$NAMECHEAP_API" "" "POST")"
+  _debug2 response "$response"
+
+  if _contains "$response" "Status=\"ERROR\"" >/dev/null; then
+    error=$(echo "$response" | _egrep_o ">.*<\\/Error>" | cut -d '<' -f 1 | tr -d '>')
+    _err "error $error"
+    return 1
+  fi
+
+  return 0
+}
+
+_namecheap_parse_host() {
+  _host=$1
+  _debug _host "$_host"
+
+  _hostid=$(echo "$_host" | _egrep_o '\sHostId="[^"]*' | cut -d '"' -f 2)
+  _hostname=$(echo "$_host" | _egrep_o '\sName="[^"]*' | cut -d '"' -f 2)
+  _hosttype=$(echo "$_host" | _egrep_o '\sType="[^"]*' | cut -d '"' -f 2)
+  _hostaddress=$(echo "$_host" | _egrep_o '\sAddress="[^"]*' | cut -d '"' -f 2)
+  _hostmxpref=$(echo "$_host" | _egrep_o '\sMXPref="[^"]*' | cut -d '"' -f 2)
+  _hostttl=$(echo "$_host" | _egrep_o '\sTTL="[^"]*' | cut -d '"' -f 2)
+
+  _debug hostid "$_hostid"
+  _debug hostname "$_hostname"
+  _debug hosttype "$_hosttype"
+  _debug hostaddress "$_hostaddress"
+  _debug hostmxpref "$_hostmxpref"
+  _debug hostttl "$_hostttl"
+}
+
+_namecheap_check_config() {
+
+  if [ -z "$NAMECHEAP_API_KEY" ]; then
+    _err "No API key specified for Namecheap API."
+    _err "Create your key and export it as NAMECHEAP_API_KEY"
+    return 1
+  fi
+
+  if [ -z "$NAMECHEAP_USERNAME" ]; then
+    _err "No username key specified for Namecheap API."
+    _err "Create your key and export it as NAMECHEAP_USERNAME"
+    return 1
+  fi
+
+  _saveaccountconf NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY"
+  _saveaccountconf NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME"
+
+  return 0
+}
+
+_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_post "$request"; then
+    _err "$error"
+    return 1
+  fi
+
+  hosts=$(echo "$response" | _egrep_o '<host[^>]*')
+  _debug hosts "$hosts"
+
+  if [ -z "$hosts" ]; then
+    _error "Hosts not found"
+    return 1
+  fi
+
+  _namecheap_reset_hostList
+
+  while read -r host; do
+    if _contains "$host" "<host"; then
+      _namecheap_parse_host "$host"
+      _namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
+    fi
+  done <<EOT
+echo "$hosts"
+EOT
+
+  _namecheap_add_host "$subdomain" "TXT" "$txt" 10 120
+
+  _debug hostrequestfinal "$_hostrequest"
+
+  request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}"
+
+  if ! _namecheap_post "$request"; then
+    _err "$error"
+    return 1
+  fi
+
+  return 0
+}
+
+_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_post "$request"; then
+    _err "$error"
+    return 1
+  fi
+
+  hosts=$(echo "$response" | _egrep_o '<host[^>]*')
+  _debug hosts "$hosts"
+
+  if [ -z "$hosts" ]; then
+    _error "Hosts not found"
+    return 1
+  fi
+
+  _namecheap_reset_hostList
+
+  found=0
+
+  while read -r host; do
+    if _contains "$host" "<host"; then
+      _namecheap_parse_host "$host"
+      if [ "$_hosttype" = "TXT" ] && [ "$_hostname" = "$subdomain" ] && [ "$_hostaddress" = "$txt" ]; then
+        _debug "TXT entry found"
+        found=1
+      else
+        _namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
+      fi
+    fi
+  done <<EOT
+echo "$hosts"
+EOT
+
+  if [ $found -eq 0 ]; then
+    _debug "TXT entry not found"
+    return 0
+  fi
+
+  _debug hostrequestfinal "$_hostrequest"
+
+  request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}"
+
+  if ! _namecheap_post "$request"; then
+    _err "$error"
+    return 1
+  fi
+
+  return 0
+}
+
+_namecheap_reset_hostList() {
+  _hostindex=0
+  _hostrequest=""
+}
+
+#Usage: _namecheap_add_host HostName RecordType Address MxPref TTL
+_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")
+}