Browse Source

Merge pull request #2319 from Neilpang/dev

sync
neil 5 years ago
parent
commit
34617270a5
7 changed files with 643 additions and 192 deletions
  1. 1 0
      Dockerfile
  2. 22 0
      acme.sh
  3. 285 0
      deploy/docker.sh
  4. 176 131
      dnsapi/dns_freedns.sh
  5. 2 2
      dnsapi/dns_gcloud.sh
  6. 99 59
      dnsapi/dns_one.sh
  7. 58 0
      notify/postmark.sh

+ 1 - 0
Dockerfile

@@ -8,6 +8,7 @@ RUN apk update -f \
   curl \
   socat \
   tzdata \
+  tar \
   && rm -rf /var/cache/apk/*
 
 ENV LE_CONFIG_HOME /acme.sh

+ 22 - 0
acme.sh

@@ -2078,6 +2078,28 @@ _readdomainconf() {
   _read_conf "$DOMAIN_CONF" "$1"
 }
 
+#key  value  base64encode
+_savedeployconf() {
+  _savedomainconf "SAVED_$1" "$2" "$3"
+  #remove later
+  _cleardomainconf "$1"
+}
+
+#key
+_getdeployconf() {
+  _rac_key="$1"
+  _rac_value="$(eval echo \$"$_rac_key")"
+  if [ "$_rac_value" ]; then
+    if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then
+      _debug2 "trim quotation marks"
+      eval "export $_rac_key=$_rac_value" 
+    fi
+    return 0 # do nothing
+  fi
+  _saved=$(_readdomainconf "SAVED_$_rac_key")
+  eval "export $_rac_key=$_saved"
+}
+
 #_saveaccountconf  key  value  base64encode
 _saveaccountconf() {
   _save_conf "$ACCOUNT_CONF_PATH" "$@"

+ 285 - 0
deploy/docker.sh

@@ -0,0 +1,285 @@
+#!/usr/bin/env sh
+
+#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"
+
+#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
+#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
+#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
+#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
+#DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"
+
+_DEPLOY_DOCKER_WIKI="https://github.com/Neilpang/acme.sh/wiki/deploy-to-docker-containers"
+
+_DOCKER_HOST_DEFAULT="/var/run/docker.sock"
+
+docker_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+  _debug _cdomain "$_cdomain"
+  _getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL
+  _debug2 DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
+  if [ -z "$DEPLOY_DOCKER_CONTAINER_LABEL" ]; then
+    _err "The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container."
+    _err "See: $_DEPLOY_DOCKER_WIKI"
+  fi
+
+  _savedeployconf DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
+
+  if [ "$DOCKER_HOST" ]; then
+    _saveaccountconf DOCKER_HOST "$DOCKER_HOST"
+  fi
+
+  if _exists docker && docker version | grep -i docker >/dev/null; then
+    _info "Using docker command"
+    export _USE_DOCKER_COMMAND=1
+  else
+    export _USE_DOCKER_COMMAND=
+  fi
+
+  export _USE_UNIX_SOCKET=
+  if [ -z "$_USE_DOCKER_COMMAND" ]; then
+    export _USE_REST=
+    if [ "$DOCKER_HOST" ]; then
+      _debug "Try use docker host: $DOCKER_HOST"
+      export _USE_REST=1
+    else
+      export _DOCKER_SOCK="$_DOCKER_HOST_DEFAULT"
+      _debug "Try use $_DOCKER_SOCK"
+      if [ ! -e "$_DOCKER_SOCK" ] || [ ! -w "$_DOCKER_SOCK" ]; then
+        _err "$_DOCKER_SOCK is not available"
+        return 1
+      fi
+      export _USE_UNIX_SOCKET=1
+      if ! _exists "curl"; then
+        _err "Please install curl first."
+        _err "We need curl to work."
+        return 1
+      fi
+      if ! _check_curl_version; then
+        return 1
+      fi
+    fi
+  fi
+
+  _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE
+  _debug2 DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
+  if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
+    _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
+  fi
+
+  _getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
+  _debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
+  if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
+    _savedeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
+  fi
+
+  _getdeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE
+  _debug2 DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
+  if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
+    _savedeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
+  fi
+
+  _getdeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE
+  _debug2 DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
+  if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
+    _savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
+  fi
+
+  _getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
+  _debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
+  if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
+    _savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
+  fi
+
+  _cid="$(_get_id "$DEPLOY_DOCKER_CONTAINER_LABEL")"
+  _info "Container id: $_cid"
+  if [ -z "$_cid" ]; then
+    _err "can not find container id"
+    return 1
+  fi
+
+  if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
+    if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
+      return 1
+    fi
+  fi
+
+  if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
+    if ! _docker_cp "$_cid" "$_ccert" "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"; then
+      return 1
+    fi
+  fi
+
+  if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
+    if ! _docker_cp "$_cid" "$_cca" "$DEPLOY_DOCKER_CONTAINER_CA_FILE"; then
+      return 1
+    fi
+  fi
+
+  if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
+    if ! _docker_cp "$_cid" "$_cfullchain" "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"; then
+      return 1
+    fi
+  fi
+
+  if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
+    if ! _docker_exec "$_cid" "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"; then
+      return 1
+    fi
+  fi
+  return 0
+}
+
+#label
+_get_id() {
+  _label="$1"
+  if [ "$_USE_DOCKER_COMMAND" ]; then
+    docker ps -f label="$_label" --format "{{.ID}}"
+  elif [ "$_USE_REST" ]; then
+    _err "Not implemented yet."
+    return 1
+  elif [ "$_USE_UNIX_SOCKET" ]; then
+    _req="{\"label\":[\"$_label\"]}"
+    _debug2 _req "$_req"
+    _req="$(printf "%s" "$_req" | _url_encode)"
+    _debug2 _req "$_req"
+    listjson="$(_curl_unix_sock "${_DOCKER_SOCK:-$_DOCKER_HOST_DEFAULT}" GET "/containers/json?filters=$_req")"
+    _debug2 "listjson" "$listjson"
+    echo "$listjson" | tr '{,' '\n' | grep -i '"id":' | _head_n 1 | cut -d '"' -f 4
+  else
+    _err "Not implemented yet."
+    return 1
+  fi
+}
+
+#id  cmd
+_docker_exec() {
+  _eargs="$*"
+  _debug2 "_docker_exec $_eargs"
+  _dcid="$1"
+  shift
+  if [ "$_USE_DOCKER_COMMAND" ]; then
+    docker exec -i "$_dcid" sh -c "$*"
+  elif [ "$_USE_REST" ]; then
+    _err "Not implemented yet."
+    return 1
+  elif [ "$_USE_UNIX_SOCKET" ]; then
+    _cmd="$*"
+    #_cmd="$(printf "%s" "$_cmd" | sed 's/ /","/g')"
+    _debug2 _cmd "$_cmd"
+    #create exec instance:
+    cjson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/containers/$_dcid/exec" "{\"Cmd\": [\"sh\", \"-c\", \"$_cmd\"]}")"
+    _debug2 cjson "$cjson"
+    execid="$(echo "$cjson" | cut -d '"' -f 4)"
+    _debug execid "$execid"
+    ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")"
+    _debug2 ejson "$ejson"
+    if [ "$ejson" ]; then
+      _err "$ejson"
+      return 1
+    fi
+  else
+    _err "Not implemented yet."
+    return 1
+  fi
+}
+
+#id from  to
+_docker_cp() {
+  _dcid="$1"
+  _from="$2"
+  _to="$3"
+  _info "Copying file from $_from to $_to"
+  _dir="$(dirname "$_to")"
+  _debug2 _dir "$_dir"
+  if ! _docker_exec "$_dcid" mkdir -p "$_dir"; then
+    _err "Can not create dir: $_dir"
+    return 1
+  fi
+  if [ "$_USE_DOCKER_COMMAND" ]; then
+    if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+      _docker_exec "$_dcid" tee "$_to" <"$_from"
+    else
+      _docker_exec "$_dcid" tee "$_to" <"$_from" >/dev/null
+    fi
+    if [ "$?" = "0" ]; then
+      _info "Success"
+      return 0
+    else
+      _info "Error"
+      return 1
+    fi
+  elif [ "$_USE_REST" ]; then
+    _err "Not implemented yet."
+    return 1
+  elif [ "$_USE_UNIX_SOCKET" ]; then
+    _frompath="$_from"
+    if _startswith "$_frompath" '/'; then
+      _frompath="$(echo "$_from" | cut -b 2-)" #remove the first '/' char
+    fi
+    _debug2 "_frompath" "$_frompath"
+    _toname="$(basename "$_to")"
+    _debug2 "_toname" "$_toname"
+    if ! tar --transform="s,$_frompath,$_toname," -cz "$_from" 2>/dev/null | _curl_unix_sock "$_DOCKER_SOCK" PUT "/containers/$_dcid/archive?noOverwriteDirNonDir=1&path=$(printf "%s" "$_dir" | _url_encode)" '@-' "Content-Type: application/octet-stream"; then
+      _err "copy error"
+      return 1
+    fi
+    return 0
+  else
+    _err "Not implemented yet."
+    return 1
+  fi
+
+}
+
+#sock method  endpoint data content-type
+_curl_unix_sock() {
+  _socket="$1"
+  _method="$2"
+  _endpoint="$3"
+  _data="$4"
+  _ctype="$5"
+  if [ -z "$_ctype" ]; then
+    _ctype="Content-Type: application/json"
+  fi
+  _debug _data "$_data"
+  _debug2 "url" "http://localhost$_endpoint"
+  if [ "$_CURL_NO_HOST" ]; then
+    _cux_url="http:$_endpoint"
+  else
+    _cux_url="http://localhost$_endpoint"
+  fi
+
+  if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+    curl -vvv --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
+  else
+    curl --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
+  fi
+
+}
+
+_check_curl_version() {
+  _cversion="$(curl -V | grep '^curl ' | cut -d ' ' -f 2)"
+  _debug2 "_cversion" "$_cversion"
+
+  _major="$(_getfield "$_cversion" 1 '.')"
+  _debug2 "_major" "$_major"
+
+  _minor="$(_getfield "$_cversion" 2 '.')"
+  _debug2 "_minor" "$_minor"
+
+  if [ "$_major$_minor" -lt "740" ]; then
+    _err "curl v$_cversion doesn't support unit socket"
+    return 1
+  fi
+  if [ "$_major$_minor" -lt "750" ]; then
+    _debug "Use short host name"
+    export _CURL_NO_HOST=1
+  else
+    export _CURL_NO_HOST=
+  fi
+  return 0
+}

+ 176 - 131
dnsapi/dns_freedns.sh

@@ -7,6 +7,7 @@
 #
 #Author: David Kerr
 #Report Bugs here: https://github.com/dkerr64/acme.sh
+#or here... https://github.com/Neilpang/acme.sh/issues/2305
 #
 ########  Public functions #####################
 
@@ -46,76 +47,34 @@ dns_freedns_add() {
 
   _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")"
-
-  _debug "top_domain: $top_domain"
-  _debug "sub_domain: $sub_domain"
-
-  # Sometimes FreeDNS does not return 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 changed?  If so..."
-        _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
-      fi
-      return 1
-    fi
-
-    subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '<form .*</form>' | sed 's/<tr>/@<tr>/g' | tr '@' '\n' | grep edit.php | grep "$top_domain")"
-    _debug3 "subdomain_csv: $subdomain_csv"
-
-    # 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)"
-    i=0
-    found=0
-    DNSdomainid=""
-    while [ "$i" -lt "$lines" ]; do
-      i="$(_math "$i" + 1)"
-      line="$(echo "$subdomain_csv" | sed -n "${i}p")"
-      _debug2 "line: $line"
-      if [ $found = 0 ] && _contains "$line" "<td>$top_domain</td>"; then
-        # this line will contain DNSdomainid for the top_domain
-        DNSdomainid="$(echo "$line" | _egrep_o "edit_domain_id *= *.*>" | cut -d = -f 2 | cut -d '>' -f 1)"
-        _debug2 "DNSdomainid: $DNSdomainid"
-        found=1
-        break
-      fi
-    done
-
-    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
-        _err "Domain $top_domain not found at FreeDNS"
-        return 1
-      fi
-    else
-      # break out of the 'retry' loop... we have found our domain ID
+  # We may have to cycle through the domain name to find the
+  # TLD that we own...
+  i=1
+  wmax="$(echo "$fulldomain" | tr '.' ' ' | wc -w)"
+  while [ "$i" -lt "$wmax" ]; do
+    # split our full domain name into two parts...
+    sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")"
+    i="$(_math "$i" + 1)"
+    top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)"
+    _debug "sub_domain: $sub_domain"
+    _debug "top_domain: $top_domain"
+
+    DNSdomainid="$(_freedns_domain_id "$top_domain")"
+    if [ "$?" = "0" ]; then
+      _info "Domain $top_domain found at FreeDNS, domain_id $DNSdomainid"
       break
+    else
+      _info "Domain $top_domain not found at FreeDNS, try with next level of TLD"
     fi
-    _info "Domain $top_domain not found at FreeDNS"
-    _info "Retry loading subdomain page ($attempts attempts remaining)"
   done
 
+  if [ -z "$DNSdomainid" ]; then
+    # If domain ID is empty then something went wrong (top level
+    # domain not found at FreeDNS).
+    _err "Domain $top_domain not found at FreeDNS"
+    return 1
+  fi
+
   # Add in new TXT record with the value provided
   _debug "Adding TXT record for $fulldomain, $txtvalue"
   _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue"
@@ -134,80 +93,47 @@ dns_freedns_rm() {
 
   # 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() function
-  FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")"
+  FREEDNS_COOKIE="$(_readaccountconf "FREEDNS_COOKIE")"
   _debug "FreeDNS login cookies: $FREEDNS_COOKIE"
 
-  # Sometimes FreeDNS does not return 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")"
+  TXTdataid="$(_freedns_data_id "$fulldomain" "TXT")"
+  if [ "$?" != "0" ]; then
+    _info "Cannot delete TXT record for $fulldomain, record does not exist at FreeDNS"
+    return 1
+  fi
+  _debug "Data ID's found, $TXTdataid"
+
+  # now we have one (or more) TXT record data ID's. Load the page
+  # for that record and search for the record txt value.  If match
+  # then we can delete it.
+  lines="$(echo "$TXTdataid" | wc -l)"
+  _debug "Found $lines TXT data records for $fulldomain"
+  i=0
+  while [ "$i" -lt "$lines" ]; do
+    i="$(_math "$i" + 1)"
+    dataid="$(echo "$TXTdataid" | sed -n "${i}p")"
+    _debug "$dataid"
+
+    htmlpage="$(_freedns_retrieve_data_page "$FREEDNS_COOKIE" "$dataid")"
     if [ "$?" != "0" ]; then
+      if [ "$using_cached_cookies" = "true" ]; then
+        _err "Has your FreeDNS username and password changed?  If so..."
+        _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
+      fi
       return 1
     fi
 
-    subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '<form .*</form>' | sed 's/<tr>/@<tr>/g' | tr '@' '\n' | grep edit.php | grep "$fulldomain")"
-    _debug3 "subdomain_csv: $subdomain_csv"
-
-    # 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)"
-    i=0
-    found=0
-    DNSdataid=""
-    while [ "$i" -lt "$lines" ]; do
-      i="$(_math "$i" + 1)"
-      line="$(echo "$subdomain_csv" | sed -n "${i}p")"
-      _debug3 "line: $line"
-      DNSname="$(echo "$line" | _egrep_o 'edit.php.*</a>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
-      _debug2 "DNSname: $DNSname"
-      if [ "$DNSname" = "$fulldomain" ]; then
-        DNStype="$(echo "$line" | sed 's/<td/@<td/g' | tr '@' '\n' | sed -n '4p' | cut -d '>' -f 2 | cut -d '<' -f 1)"
-        _debug2 "DNStype: $DNStype"
-        if [ "$DNStype" = "TXT" ]; then
-          DNSdataid="$(echo "$line" | _egrep_o 'data_id=.*' | cut -d = -f 2 | cut -d '>' -f 1)"
-          _debug2 "DNSdataid: $DNSdataid"
-          DNSvalue="$(echo "$line" | sed 's/<td/@<td/g' | tr '@' '\n' | sed -n '5p' | cut -d '>' -f 2 | cut -d '<' -f 1)"
-          if _startswith "$DNSvalue" "&quot;"; then
-            # remove the quotation from the start
-            DNSvalue="$(echo "$DNSvalue" | cut -c 7-)"
-          fi
-          if _endswith "$DNSvalue" "..."; then
-            # value was truncated, remove the dot dot dot from the end
-            DNSvalue="$(echo "$DNSvalue" | sed 's/...$//')"
-          elif _endswith "$DNSvalue" "&quot;"; then
-            # else remove the closing quotation from the end
-            DNSvalue="$(echo "$DNSvalue" | sed 's/......$//')"
-          fi
-          _debug2 "DNSvalue: $DNSvalue"
-
-          if [ -n "$DNSdataid" ] && _startswith "$txtvalue" "$DNSvalue"; then
-            # Found a match. But note... Website is truncating the
-            # value field so we are only testing that part that is not 
-            # truncated.  This should be accurate enough.
-            _debug "Deleting TXT record for $fulldomain, $txtvalue"
-            _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid"
-            return $?
-          fi
-
-        fi
-      fi
-    done
+    echo "$htmlpage" | grep "value=\"&quot;$txtvalue&quot;\"" >/dev/null
+    if [ "$?" = "0" ]; then
+      # Found a match... delete the record and return
+      _info "Deleting TXT record for $fulldomain, $txtvalue"
+      _freedns_delete_txt_record "$FREEDNS_COOKIE" "$dataid"
+      return $?
+    fi
   done
 
-  # If we get this far we did not find a match (after two attempts)
+  # If we get this far we did not find a match
   # Not necessarily an error, but log anyway.
-  _debug3 "$subdomain_csv"
   _info "Cannot delete TXT record for $fulldomain, $txtvalue. Does not exist at FreeDNS"
   return 0
 }
@@ -271,6 +197,33 @@ _freedns_retrieve_subdomain_page() {
   return 0
 }
 
+# usage _freedns_retrieve_data_page login_cookies data_id
+# echo page retrieved (html)
+# returns 0 success
+_freedns_retrieve_data_page() {
+  export _H1="Cookie:$1"
+  export _H2="Accept-Language:en-US"
+  data_id="$2"
+  url="https://freedns.afraid.org/subdomain/edit.php?data_id=$2"
+
+  _debug "Retrieve data page for ID $data_id from FreeDNS"
+
+  htmlpage="$(_get "$url")"
+
+  if [ "$?" != "0" ]; then
+    _err "FreeDNS retrieve data page failed bad RC from _get"
+    return 1
+  elif [ -z "$htmlpage" ]; then
+    _err "FreeDNS returned empty data page"
+    return 1
+  fi
+
+  _debug3 "htmlpage: $htmlpage"
+
+  printf "%s" "$htmlpage"
+  return 0
+}
+
 # usage _freedns_add_txt_record login_cookies domain_id subdomain value
 # returns 0 success
 _freedns_add_txt_record() {
@@ -324,3 +277,95 @@ _freedns_delete_txt_record() {
   _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS"
   return 0
 }
+
+# usage _freedns_domain_id domain_name
+# echo the domain_id if found
+# return 0 success
+_freedns_domain_id() {
+  # Start by escaping the dots in the domain name
+  search_domain="$(echo "$1" | sed 's/\./\\./g')"
+
+  # Sometimes FreeDNS does not return 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 changed?  If so..."
+        _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
+      fi
+      return 1
+    fi
+
+    domain_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
+      | grep "<td>$search_domain</td>\|<td>$search_domain(.*)</td>" \
+      | _egrep_o "edit\.php\?edit_domain_id=[0-9a-zA-Z]+" \
+      | cut -d = -f 2)"
+    # The above beauty extracts domain ID from the html page...
+    # strip out all blank space and new lines. Then insert newlines
+    # before each table row <tr>
+    # search for the domain within each row (which may or may not have
+    # a text string in brackets (.*) after it.
+    # And finally extract the domain ID.
+    if [ -n "$domain_id" ]; then
+      printf "%s" "$domain_id"
+      return 0
+    fi
+    _debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)"
+  done
+  _debug "Domain $search_domain not found after retry"
+  return 1
+}
+
+# usage _freedns_data_id domain_name record_type
+# echo the data_id(s) if found
+# return 0 success
+_freedns_data_id() {
+  # Start by escaping the dots in the domain name
+  search_domain="$(echo "$1" | sed 's/\./\\./g')"
+  record_type="$2"
+
+  # Sometimes FreeDNS does not return 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 changed?  If so..."
+        _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
+      fi
+      return 1
+    fi
+
+    data_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
+      | grep "<td[a-zA-Z=#]*>$record_type</td>" \
+      | grep "<ahref.*>$search_domain</a>" \
+      | _egrep_o "edit\.php\?data_id=[0-9a-zA-Z]+" \
+      | cut -d = -f 2)"
+    # The above beauty extracts data ID from the html page...
+    # strip out all blank space and new lines. Then insert newlines
+    # before each table row <tr>
+    # search for the record type withing each row (e.g. TXT)
+    # search for the domain within each row (which is within a <a..>
+    # </a> anchor. And finally extract the domain ID.         
+    if [ -n "$data_id" ]; then
+      printf "%s" "$data_id"
+      return 0
+    fi
+    _debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)"
+  done
+  _debug "Domain $search_domain not found after retry"
+  return 1
+}

+ 2 - 2
dnsapi/dns_gcloud.sh

@@ -93,7 +93,7 @@ _dns_gcloud_execute_tr() {
 }
 
 _dns_gcloud_remove_rrs() {
-  if ! xargs --no-run-if-empty gcloud dns record-sets transaction remove \
+  if ! xargs -r gcloud dns record-sets transaction remove \
     --name="$fulldomain." \
     --ttl="$ttl" \
     --type=TXT \
@@ -108,7 +108,7 @@ _dns_gcloud_remove_rrs() {
 
 _dns_gcloud_add_rrs() {
   ttl=60
-  if ! xargs --no-run-if-empty gcloud dns record-sets transaction add \
+  if ! xargs -r gcloud dns record-sets transaction add \
     --name="$fulldomain." \
     --ttl="$ttl" \
     --type=TXT \

+ 99 - 59
dnsapi/dns_one.sh

@@ -4,6 +4,8 @@
 # one.com ui wrapper for acme.sh
 # Author: github: @diseq
 # Created: 2019-02-17
+# Fixed by: @der-berni
+# Modified: 2019-05-31
 #
 #     export ONECOM_User="username"
 #     export ONECOM_Password="password"
@@ -14,49 +16,29 @@
 #     only single domain supported atm
 
 dns_one_add() {
-  mysubdomain=$(printf -- "%s" "$1" | rev | cut -d"." -f3- | rev)
-  mydomain=$(printf -- "%s" "$1" | rev | cut -d"." -f1-2 | rev)
+  fulldomain=$1
   txtvalue=$2
 
-  # get credentials
-  ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
-  ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
-  if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
-    ONECOM_User=""
-    ONECOM_Password=""
-    _err "You didn't specify a one.com username and password yet."
-    _err "Please create the key and try again."
+  if ! _dns_one_login; then
+    _err "login failed"
     return 1
   fi
 
-  #save the api key and email to the account conf file.
-  _saveaccountconf_mutable ONECOM_User "$ONECOM_User"
-  _saveaccountconf_mutable ONECOM_Password "$ONECOM_Password"
-
-  # Login with user and password
-  postdata="loginDomain=true"
-  postdata="$postdata&displayUsername=$ONECOM_User"
-  postdata="$postdata&username=$ONECOM_User"
-  postdata="$postdata&targetDomain=$mydomain"
-  postdata="$postdata&password1=$ONECOM_Password"
-  postdata="$postdata&loginTarget="
-  #_debug postdata "$postdata"
-
-  response="$(_post "$postdata" "https://www.one.com/admin/login.do" "" "POST" "application/x-www-form-urlencoded")"
-  #_debug response "$response"
-
-  JSESSIONID="$(grep "JSESSIONID" "$HTTP_HEADER" | grep "^[Ss]et-[Cc]ookie:" | _tail_n 1 | _egrep_o 'JSESSIONID=[^;]*;' | tr -d ';')"
-  _debug jsessionid "$JSESSIONID"
+  _debug "detect the root domain"
+  if ! _get_root "$fulldomain"; then
+    _err "root domain not found"
+    return 1
+  fi
 
-  export _H1="Cookie: ${JSESSIONID}"
+  mysubdomain=$_sub_domain
+  mydomain=$_domain
+  _debug mysubdomain "$mysubdomain"
+  _debug mydomain "$mydomain"
 
   # get entries
   response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
   _debug response "$response"
 
-  CSRF_G_TOKEN="$(grep "CSRF_G_TOKEN=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'CSRF_G_TOKEN=[^;]*;' | tr -d ';')"
-  export _H2="Cookie: ${CSRF_G_TOKEN}"
-
   # Update the IP address for domain entry
   postdata="{\"type\":\"dns_custom_records\",\"attributes\":{\"priority\":0,\"ttl\":600,\"type\":\"TXT\",\"prefix\":\"$mysubdomain\",\"content\":\"$txtvalue\"}}"
   _debug postdata "$postdata"
@@ -77,45 +59,30 @@ dns_one_add() {
 }
 
 dns_one_rm() {
-  mysubdomain=$(printf -- "%s" "$1" | rev | cut -d"." -f3- | rev)
-  mydomain=$(printf -- "%s" "$1" | rev | cut -d"." -f1-2 | rev)
+  fulldomain=$1
   txtvalue=$2
 
-  # get credentials
-  ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
-  ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
-  if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
-    ONECOM_User=""
-    ONECOM_Password=""
-    _err "You didn't specify a one.com username and password yet."
-    _err "Please create the key and try again."
+  if ! _dns_one_login; then
+    _err "login failed"
     return 1
   fi
 
-  # Login with user and password
-  postdata="loginDomain=true"
-  postdata="$postdata&displayUsername=$ONECOM_User"
-  postdata="$postdata&username=$ONECOM_User"
-  postdata="$postdata&targetDomain=$mydomain"
-  postdata="$postdata&password1=$ONECOM_Password"
-  postdata="$postdata&loginTarget="
-
-  response="$(_post "$postdata" "https://www.one.com/admin/login.do" "" "POST" "application/x-www-form-urlencoded")"
-  #_debug response "$response"
-
-  JSESSIONID="$(grep "JSESSIONID" "$HTTP_HEADER" | grep "^[Ss]et-[Cc]ookie:" | _tail_n 1 | _egrep_o 'JSESSIONID=[^;]*;' | tr -d ';')"
-  _debug jsessionid "$JSESSIONID"
+  _debug "detect the root domain"
+  if ! _get_root "$fulldomain"; then
+    _err "root domain not found"
+    return 1
+  fi
 
-  export _H1="Cookie: ${JSESSIONID}"
+  mysubdomain=$_sub_domain
+  mydomain=$_domain
+  _debug mysubdomain "$mysubdomain"
+  _debug mydomain "$mydomain"
 
   # get entries
   response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
   response="$(echo "$response" | _normalizeJson)"
   _debug response "$response"
 
-  CSRF_G_TOKEN="$(grep "CSRF_G_TOKEN=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'CSRF_G_TOKEN=[^;]*;' | tr -d ';')"
-  export _H2="Cookie: ${CSRF_G_TOKEN}"
-
   id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}.*/\1/p")
 
   if [ -z "$id" ]; then
@@ -137,3 +104,76 @@ dns_one_rm() {
   fi
 
 }
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+  domain="$1"
+  i=2
+  p=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    response="$(_get "https://www.one.com/admin/api/domains/$h/dns/custom_records")"
+
+    if ! _contains "$response" "CRMRST_000302"; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain="$h"
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  _err "Unable to parse this domain"
+  return 1
+}
+
+_dns_one_login() {
+
+  # get credentials
+  ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
+  ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
+  if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
+    ONECOM_User=""
+    ONECOM_Password=""
+    _err "You didn't specify a one.com username and password yet."
+    _err "Please create the key and try again."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable ONECOM_User "$ONECOM_User"
+  _saveaccountconf_mutable ONECOM_Password "$ONECOM_Password"
+
+  # Login with user and password
+  postdata="loginDomain=true"
+  postdata="$postdata&displayUsername=$ONECOM_User"
+  postdata="$postdata&username=$ONECOM_User"
+  postdata="$postdata&targetDomain="
+  postdata="$postdata&password1=$ONECOM_Password"
+  postdata="$postdata&loginTarget="
+  #_debug postdata "$postdata"
+
+  response="$(_post "$postdata" "https://www.one.com/admin/login.do" "" "POST" "application/x-www-form-urlencoded")"
+  #_debug response "$response"
+
+  # Get SessionID
+  JSESSIONID="$(grep "OneSIDCrmAdmin" "$HTTP_HEADER" | grep "^[Ss]et-[Cc]ookie:" | _head_n 1 | _egrep_o 'OneSIDCrmAdmin=[^;]*;' | tr -d ';')"
+  _debug jsessionid "$JSESSIONID"
+
+  if [ -z "$JSESSIONID" ]; then
+    _err "error sessionid cookie not found"
+    return 1
+  fi
+
+  export _H1="Cookie: ${JSESSIONID}"
+
+  return 0
+}

+ 58 - 0
notify/postmark.sh

@@ -0,0 +1,58 @@
+#!/usr/bin/env sh
+
+#Support postmarkapp.com API (https://postmarkapp.com/developer/user-guide/sending-email/sending-with-api)
+
+#POSTMARK_TOKEN=""
+#POSTMARK_TO="xxxx@xxx.com"
+#POSTMARK_FROM="xxxx@cccc.com"
+
+postmark_send() {
+  _subject="$1"
+  _content="$2"
+  _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+  _debug "_statusCode" "$_statusCode"
+
+  POSTMARK_TOKEN="${POSTMARK_TOKEN:-$(_readaccountconf_mutable POSTMARK_TOKEN)}"
+  if [ -z "$POSTMARK_TOKEN" ]; then
+    POSTMARK_TOKEN=""
+    _err "You didn't specify a POSTMARK api token POSTMARK_TOKEN yet ."
+    _err "You can get yours from here https://account.postmarkapp.com"
+    return 1
+  fi
+  _saveaccountconf_mutable POSTMARK_TOKEN "$POSTMARK_TOKEN"
+
+  POSTMARK_TO="${POSTMARK_TO:-$(_readaccountconf_mutable POSTMARK_TO)}"
+  if [ -z "$POSTMARK_TO" ]; then
+    POSTMARK_TO=""
+    _err "You didn't specify an email to POSTMARK_TO receive messages."
+    return 1
+  fi
+  _saveaccountconf_mutable POSTMARK_TO "$POSTMARK_TO"
+
+  POSTMARK_FROM="${POSTMARK_FROM:-$(_readaccountconf_mutable POSTMARK_FROM)}"
+  if [ -z "$POSTMARK_FROM" ]; then
+    POSTMARK_FROM=""
+    _err "You didn't specify an email from POSTMARK_FROM receive messages."
+    return 1
+  fi
+  _saveaccountconf_mutable POSTMARK_FROM "$POSTMARK_FROM"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+  export _H3="X-Postmark-Server-Token: $POSTMARK_TOKEN"
+
+  _content="$(echo "$_content" | _json_encode)"
+  _data="{\"To\": \"$POSTMARK_TO\", \"From\": \"$POSTMARK_FROM\", \"Subject\": \"$_subject\", \"TextBody\": \"$_content\"}"
+  if _post "$_data" "https://api.postmarkapp.com/email"; then
+    # shellcheck disable=SC2154
+    _message=$(printf "%s\n" "$response" | _lower_case | _egrep_o "\"message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
+    if [ "$_message" = "ok" ]; then
+      _info "postmark send success."
+      return 0
+    fi
+  fi
+  _err "postmark send error."
+  _err "$response"
+  return 1
+
+}