Browse Source

Merge pull request #1138 from Neilpang/dev

Dev
neil 7 years ago
parent
commit
7a88d80a10
5 changed files with 647 additions and 92 deletions
  1. 2 0
      README.md
  2. 55 92
      acme.sh
  3. 33 0
      dnsapi/README.md
  4. 355 0
      dnsapi/dns_inwx.sh
  5. 202 0
      dnsapi/dns_unoeuro.sh

+ 2 - 0
README.md

@@ -339,6 +339,8 @@ You don't have to do anything manually!
 1. Dyn Managed DNS API
 1. Dyn Managed DNS API
 1. Yandex PDD API (https://pdd.yandex.ru)
 1. Yandex PDD API (https://pdd.yandex.ru)
 1. Hurricane Electric DNS service (https://dns.he.net)
 1. Hurricane Electric DNS service (https://dns.he.net)
+1. UnoEuro API (https://www.unoeuro.com/)
+1. INWX (https://www.inwx.de/)
 
 
 
 
 And: 
 And: 

+ 55 - 92
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-VER=2.7.5
+VER=2.7.6
 
 
 PROJECT_NAME="acme.sh"
 PROJECT_NAME="acme.sh"
 
 
@@ -15,7 +15,6 @@ _SUB_FOLDERS="dnsapi deploy"
 
 
 _OLD_CA_HOST="https://acme-v01.api.letsencrypt.org"
 _OLD_CA_HOST="https://acme-v01.api.letsencrypt.org"
 DEFAULT_CA="https://acme-v01.api.letsencrypt.org/directory"
 DEFAULT_CA="https://acme-v01.api.letsencrypt.org/directory"
-DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
 
 
 DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
 DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
 DEFAULT_ACCOUNT_EMAIL=""
 DEFAULT_ACCOUNT_EMAIL=""
@@ -900,17 +899,11 @@ _sign() {
   fi
   fi
 
 
   _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
   _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
-  if [ "$alg" = "sha256" ]; then
-    _sign_openssl="$_sign_openssl -$alg"
-  else
-    _err "$alg is not supported yet"
-    return 1
-  fi
 
 
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
-    $_sign_openssl | _base64
+    $_sign_openssl -$alg | _base64
   elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
   elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
-    if ! _signedECText="$($_sign_openssl | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
+    if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
       _err "Sign failed: $_sign_openssl"
       _err "Sign failed: $_sign_openssl"
       _err "Key file: $keyfile"
       _err "Key file: $keyfile"
       _err "Key content:$(wc -l <"$keyfile") lines"
       _err "Key content:$(wc -l <"$keyfile") lines"
@@ -1433,7 +1426,11 @@ _calcjwk() {
     _debug "EC key"
     _debug "EC key"
     crv="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
     crv="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
     _debug3 crv "$crv"
     _debug3 crv "$crv"
-
+    __ECC_KEY_LEN=$(echo "$crv" | cut -d "-" -f 2)
+    if [ "$__ECC_KEY_LEN" = "521" ]; then
+      __ECC_KEY_LEN=512
+    fi
+    _debug3 __ECC_KEY_LEN "$__ECC_KEY_LEN"
     if [ -z "$crv" ]; then
     if [ -z "$crv" ]; then
       _debug "Let's try ASN1 OID"
       _debug "Let's try ASN1 OID"
       crv_oid="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")"
       crv_oid="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")"
@@ -1441,12 +1438,15 @@ _calcjwk() {
       case "${crv_oid}" in
       case "${crv_oid}" in
         "prime256v1")
         "prime256v1")
           crv="P-256"
           crv="P-256"
+          __ECC_KEY_LEN=256
           ;;
           ;;
         "secp384r1")
         "secp384r1")
           crv="P-384"
           crv="P-384"
+          __ECC_KEY_LEN=384
           ;;
           ;;
         "secp521r1")
         "secp521r1")
           crv="P-521"
           crv="P-521"
+          __ECC_KEY_LEN=512
           ;;
           ;;
         *)
         *)
           _err "ECC oid : $crv_oid"
           _err "ECC oid : $crv_oid"
@@ -1488,9 +1488,9 @@ _calcjwk() {
     jwk='{"crv": "'$crv'", "kty": "EC", "x": "'$x64'", "y": "'$y64'"}'
     jwk='{"crv": "'$crv'", "kty": "EC", "x": "'$x64'", "y": "'$y64'"}'
     _debug3 jwk "$jwk"
     _debug3 jwk "$jwk"
 
 
-    JWK_HEADER='{"alg": "ES256", "jwk": '$jwk'}'
+    JWK_HEADER='{"alg": "ES'$__ECC_KEY_LEN'", "jwk": '$jwk'}'
     JWK_HEADERPLACE_PART1='{"nonce": "'
     JWK_HEADERPLACE_PART1='{"nonce": "'
-    JWK_HEADERPLACE_PART2='", "alg": "ES256", "jwk": '$jwk'}'
+    JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'", "jwk": '$jwk'}'
   else
   else
     _err "Only RSA or EC key is supported."
     _err "Only RSA or EC key is supported."
     return 1
     return 1
@@ -2160,17 +2160,6 @@ _initAPI() {
   _api_server="${1:-$ACME_DIRECTORY}"
   _api_server="${1:-$ACME_DIRECTORY}"
   _debug "_init api for server: $_api_server"
   _debug "_init api for server: $_api_server"
 
 
-  if [ "$_api_server" = "$DEFAULT_CA" ]; then
-    #just for performance, hardcode the default entry points
-    export ACME_KEY_CHANGE="https://acme-v01.api.letsencrypt.org/acme/key-change"
-    export ACME_NEW_AUTHZ="https://acme-v01.api.letsencrypt.org/acme/new-authz"
-    export ACME_NEW_ORDER="https://acme-v01.api.letsencrypt.org/acme/new-cert"
-    export ACME_NEW_ORDER_RES="new-cert"
-    export ACME_NEW_ACCOUNT="https://acme-v01.api.letsencrypt.org/acme/new-reg"
-    export ACME_NEW_ACCOUNT_RES="new-reg"
-    export ACME_REVOKE_CERT="https://acme-v01.api.letsencrypt.org/acme/revoke-cert"
-  fi
-
   if [ -z "$ACME_NEW_ACCOUNT" ]; then
   if [ -z "$ACME_NEW_ACCOUNT" ]; then
     response=$(_get "$_api_server")
     response=$(_get "$_api_server")
     if [ "$?" != "0" ]; then
     if [ "$?" != "0" ]; then
@@ -2210,13 +2199,17 @@ _initAPI() {
     ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'new-nonce" *: *"[^"]*"' | cut -d '"' -f 3)
     ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'new-nonce" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_NEW_NONCE
     export ACME_NEW_NONCE
 
 
-  fi
+    ACME_AGREEMENT=$(echo "$response" | _egrep_o 'terms-of-service" *: *"[^"]*"' | cut -d '"' -f 3)
+    export ACME_AGREEMENT
+
+    _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE"
+    _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ"
+    _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER"
+    _debug "ACME_NEW_ACCOUNT" "$ACME_NEW_ACCOUNT"
+    _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
+    _debug "ACME_AGREEMENT" "$ACME_AGREEMENT"
 
 
-  _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE"
-  _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ"
-  _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER"
-  _debug "ACME_NEW_ACCOUNT" "$ACME_NEW_ACCOUNT"
-  _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
+  fi
 }
 }
 
 
 #[domain]  [keylength or isEcc flag]
 #[domain]  [keylength or isEcc flag]
@@ -3058,7 +3051,7 @@ __calc_account_thumbprint() {
 _regAccount() {
 _regAccount() {
   _initpath
   _initpath
   _reg_length="$1"
   _reg_length="$1"
-
+  _debug3 _regAccount "$_regAccount"
   mkdir -p "$CA_DIR"
   mkdir -p "$CA_DIR"
   if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
   if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
     _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
     _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
@@ -3081,75 +3074,45 @@ _regAccount() {
     return 1
     return 1
   fi
   fi
   _initAPI
   _initAPI
-  _updateTos=""
   _reg_res="$ACME_NEW_ACCOUNT_RES"
   _reg_res="$ACME_NEW_ACCOUNT_RES"
-  while true; do
-    _debug AGREEMENT "$AGREEMENT"
+  regjson='{"resource": "'$_reg_res'", "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
+  if [ "$ACCOUNT_EMAIL" ]; then
+    regjson='{"resource": "'$_reg_res'", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
+  fi
 
 
-    regjson='{"resource": "'$_reg_res'", "agreement": "'$AGREEMENT'"}'
+  _info "Registering account"
 
 
-    if [ "$ACCOUNT_EMAIL" ]; then
-      regjson='{"resource": "'$_reg_res'", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}'
-    fi
+  if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then
+    _err "Register account Error: $response"
+    return 1
+  fi
 
 
-    if [ -z "$_updateTos" ]; then
-      _info "Registering account"
+  if [ "$code" = "" ] || [ "$code" = '201' ]; then
+    echo "$response" >"$ACCOUNT_JSON_PATH"
+    _info "Registered"
+  elif [ "$code" = '409' ]; then
+    _info "Already registered"
+  else
+    _err "Register account Error: $response"
+    return 1
+  fi
 
 
-      if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then
-        _err "Register account Error: $response"
-        return 1
-      fi
+  _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+  _debug "_accUri" "$_accUri"
+  _savecaconf "ACCOUNT_URL" "$_accUri"
 
 
-      if [ "$code" = "" ] || [ "$code" = '201' ]; then
-        echo "$response" >"$ACCOUNT_JSON_PATH"
-        _info "Registered"
-      elif [ "$code" = '409' ]; then
-        _info "Already registered"
-      else
-        _err "Register account Error: $response"
-        return 1
-      fi
+  echo "$response" >"$ACCOUNT_JSON_PATH"
+  CA_KEY_HASH="$(__calcAccountKeyHash)"
+  _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
+  _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
 
 
-      _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
-      _debug "_accUri" "$_accUri"
-      _savecaconf "ACCOUNT_URL" "$_accUri"
-      _tos="$(echo "$responseHeaders" | grep "^Link:.*rel=\"terms-of-service\"" | _head_n 1 | _egrep_o "<.*>" | tr -d '<>')"
-      _debug "_tos" "$_tos"
-      if [ -z "$_tos" ]; then
-        _debug "Use default tos: $DEFAULT_AGREEMENT"
-        _tos="$DEFAULT_AGREEMENT"
-      fi
-      if [ "$_tos" != "$AGREEMENT" ]; then
-        _updateTos=1
-        AGREEMENT="$_tos"
-        _reg_res="reg"
-        continue
-      fi
+  if [ "$code" = '403' ]; then
+    _err "It seems that the account key is already deactivated, please use a new account key."
+    return 1
+  fi
 
 
-    else
-      _debug "Update tos: $_tos"
-      if ! _send_signed_request "$_accUri" "$regjson"; then
-        _err "Update tos error."
-        return 1
-      fi
-      if [ "$code" = '202' ]; then
-        _info "Update account tos info success."
-        echo "$response" >"$ACCOUNT_JSON_PATH"
-        CA_KEY_HASH="$(__calcAccountKeyHash)"
-        _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
-        _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
-      elif [ "$code" = '403' ]; then
-        _err "It seems that the account key is already deactivated, please use a new account key."
-        return 1
-      else
-        _err "Update account error."
-        return 1
-      fi
-    fi
-    ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
-    _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
-    return 0
-  done
+  ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
+  _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
 
 
 }
 }
 
 

+ 33 - 0
dnsapi/README.md

@@ -602,6 +602,39 @@ The `HE_Username` and `HE_Password` settings will be saved in `~/.acme.sh/accoun
 
 
 Please report any issues to https://github.com/angel333/acme.sh or to <me@ondrejsimek.com>.
 Please report any issues to https://github.com/angel333/acme.sh or to <me@ondrejsimek.com>.
 
 
+## 32. Use UnoEuro API to automatically issue cert
+
+First you need to login to your UnoEuro account to get your API key.
+
+```
+export UNO_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+export UNO_User="UExxxxxx"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_unoeuro -d example.com -d www.example.com
+```
+
+The `UNO_Key` and `UNO_User` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 33. Use INWX
+
+[INWX](https://www.inwx.de/) offers an [xmlrpc api](https://www.inwx.de/de/help/apidoc)  with your standard login credentials, set them like so:
+
+```
+export INWX_User="yourusername"
+export INWX_Password="password"
+```
+
+Then you can issue your certificates with:
+
+```
+acme.sh --issue --dns dns_inwx -d example.com -d www.example.com
+```
+
+The `INWX_User` and `INWX_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
 # Use custom API
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS API.
 If your API is not supported yet, you can write your own DNS API.

+ 355 - 0
dnsapi/dns_inwx.sh

@@ -0,0 +1,355 @@
+#!/usr/bin/env sh
+
+#
+#INWX_User="username"
+#
+#INWX_Password="password"
+
+INWX_Api="https://api.domrobot.com/xmlrpc/"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_inwx_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
+  INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
+  if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
+    INWX_User=""
+    INWX_Password=""
+    _err "You don't specify inwx user and password yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable INWX_User "$INWX_User"
+  _saveaccountconf_mutable INWX_Password "$INWX_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"
+  _debug "Getting txt records"
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>nameserver.info</methodName>
+  <params>
+   <param>
+    <value>
+     <struct>
+      <member>
+       <name>domain</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>type</name>
+       <value>
+        <string>TXT</string>
+       </value>
+      </member>
+      <member>
+       <name>name</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+     </struct>
+    </value>
+   </param>
+  </params>
+  </methodCall>' "$_domain" "$_sub_domain")
+  response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+  if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then
+    _err "Error could net get txt records"
+    return 1
+  fi
+
+  if ! printf "%s" "$response" | grep "count" >/dev/null; then
+    _info "Adding record"
+    _inwx_add_record "$_domain" "$_sub_domain" "$txtvalue"
+  else
+    _record_id=$(printf '%s' "$response" | _egrep_o '.*(<member><name>record){1}(.*)([0-9]+){1}' | _egrep_o '<name>id<\/name><value><int>[0-9]+' | _egrep_o '[0-9]+')
+    _info "Updating record"
+    _inwx_update_record "$_record_id" "$txtvalue"
+  fi
+
+}
+
+#fulldomain txtvalue
+dns_inwx_rm() {
+
+  fulldomain=$1
+  txtvalue=$2
+
+  INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
+  INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
+  if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
+    INWX_User=""
+    INWX_Password=""
+    _err "You don't specify inwx user and password yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable INWX_User "$INWX_User"
+  _saveaccountconf_mutable INWX_Password "$INWX_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"
+
+  _debug "Getting txt records"
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>nameserver.info</methodName>
+  <params>
+   <param>
+    <value>
+     <struct>
+      <member>
+       <name>domain</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>type</name>
+       <value>
+        <string>TXT</string>
+       </value>
+      </member>
+      <member>
+       <name>name</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+     </struct>
+    </value>
+   </param>
+  </params>
+  </methodCall>' "$_domain" "$_sub_domain")
+  response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+  if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then
+    _err "Error could not get txt records"
+    return 1
+  fi
+
+  if ! printf "%s" "$response" | grep "count" >/dev/null; then
+    _info "Do not need to delete record"
+  else
+    _record_id=$(printf '%s' "$response" | _egrep_o '.*(<member><name>record){1}(.*)([0-9]+){1}' | _egrep_o '<name>id<\/name><value><int>[0-9]+' | _egrep_o '[0-9]+')
+    _info "Deleting record"
+    _inwx_delete_record "$_record_id"
+  fi
+
+}
+
+####################  Private functions below ##################################
+
+_inwx_login() {
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>account.login</methodName>
+  <params>
+   <param>
+    <value>
+     <struct>
+      <member>
+       <name>user</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>pass</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+     </struct>
+    </value>
+   </param>
+  </params>
+  </methodCall>' $INWX_User $INWX_Password)
+
+  response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+  printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')"
+
+}
+
+_get_root() {
+  domain=$1
+  _debug "get root"
+
+  domain=$1
+  i=2
+  p=1
+
+  _H1=$(_inwx_login)
+  export _H1
+  xml_content='<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>nameserver.list</methodName>
+  </methodCall>'
+
+  response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+  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
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain="$h"
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+
+}
+
+_inwx_delete_record() {
+  record_id=$1
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>nameserver.deleteRecord</methodName>
+  <params>
+   <param>
+    <value>
+     <struct>
+      <member>
+       <name>id</name>
+       <value>
+        <int>%s</int>
+       </value>
+      </member>
+     </struct>
+    </value>
+   </param>
+  </params>
+  </methodCall>' "$record_id")
+
+  response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+  if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+
+}
+
+_inwx_update_record() {
+  record_id=$1
+  txtval=$2
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>nameserver.updateRecord</methodName>
+  <params>
+   <param>
+    <value>
+     <struct>
+      <member>
+       <name>content</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>id</name>
+       <value>
+        <int>%s</int>
+       </value>
+      </member>
+     </struct>
+    </value>
+   </param>
+  </params>
+  </methodCall>' "$txtval" "$record_id")
+
+  response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+  if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+
+}
+
+_inwx_add_record() {
+
+  domain=$1
+  sub_domain=$2
+  txtval=$3
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>nameserver.createRecord</methodName>
+  <params>
+   <param>
+    <value>
+     <struct>
+      <member>
+       <name>domain</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>type</name>
+       <value>
+        <string>TXT</string>
+       </value>
+      </member>
+      <member>
+       <name>content</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>name</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+     </struct>
+    </value>
+   </param>
+  </params>
+  </methodCall>' "$domain" "$txtval" "$sub_domain")
+
+  response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+  if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+}

+ 202 - 0
dnsapi/dns_unoeuro.sh

@@ -0,0 +1,202 @@
+#!/usr/bin/env sh
+
+#
+#UNO_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+#UNO_User="UExxxxxx"
+
+Uno_Api="https://api.unoeuro.com/1"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_unoeuro_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  UNO_Key="${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}"
+  UNO_User="${UNO_User:-$(_readaccountconf_mutable UNO_User)}"
+  if [ -z "$UNO_Key" ] || [ -z "$UNO_User" ]; then
+    UNO_Key=""
+    UNO_User=""
+    _err "You haven't specified a UnoEuro api key and account yet."
+    _err "Please create your key and try again."
+    return 1
+  fi
+
+  if ! _contains "$UNO_User" "UE"; then
+    _err "It seems that the UNO_User=$UNO_User is not a valid username."
+    _err "Please check and retry."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable UNO_Key "$UNO_Key"
+  _saveaccountconf_mutable UNO_User "$UNO_User"
+
+  _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"
+  _uno_rest GET "my/products/$h/dns/records"
+
+  if ! _contains "$response" "\"status\": 200" >/dev/null; then
+    _err "Error"
+    return 1
+  fi
+
+  if ! _contains "$response" "$_sub_domain" >/dev/null; then
+    _info "Adding record"
+
+    if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then
+      if _contains "$response" "\"status\": 200" >/dev/null; then
+        _info "Added, OK"
+        return 0
+      else
+        _err "Add txt record error."
+        return 1
+      fi
+    fi
+    _err "Add txt record error."
+  else
+    _info "Updating record"
+    record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1)
+    record_line_number=$(_math "$record_line_number" - 1)
+    record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
+    _debug "record_id" "$record_id"
+
+    _uno_rest PUT "my/products/$h/dns/records/$record_id" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"
+    if _contains "$response" "\"status\": 200" >/dev/null; then
+      _info "Updated, OK"
+      return 0
+    fi
+    _err "Update error"
+    return 1
+  fi
+}
+
+#fulldomain txtvalue
+dns_unoeuro_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  UNO_Key="${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}"
+  UNO_User="${UNO_User:-$(_readaccountconf_mutable UNO_User)}"
+  if [ -z "$UNO_Key" ] || [ -z "$UNO_User" ]; then
+    UNO_Key=""
+    UNO_User=""
+    _err "You haven't specified a UnoEuro api key and account yet."
+    _err "Please create your key and try again."
+    return 1
+  fi
+
+  if ! _contains "$UNO_User" "UE"; then
+    _err "It seems that the UNO_User=$UNO_User is not a valid username."
+    _err "Please check and retry."
+    return 1
+  fi
+
+  _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"
+  _uno_rest GET "my/products/$h/dns/records"
+
+  if ! _contains "$response" "\"status\": 200"; then
+    _err "Error"
+    return 1
+  fi
+
+  if ! _contains "$response" "$_sub_domain"; then
+    _info "Don't need to remove."
+  else
+    record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1)
+    record_line_number=$(_math "$record_line_number" - 1)
+    record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
+    _debug "record_id" "$record_id"
+
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+
+    if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then
+      _err "Delete record error."
+      return 1
+    fi
+    _contains "$response" "\"status\": 200"
+  fi
+
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_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 ! _uno_rest GET "my/products/$h/dns/records"; then
+      return 1
+    fi
+
+    if _contains "$response" "\"status\": 200"; then
+      _domain_id=$h
+      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
+}
+
+_uno_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="Content-Type: application/json"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$Uno_Api/$UNO_User/$UNO_Key/$ep" "" "$m")"
+  else
+    response="$(_get "$Uno_Api/$UNO_User/$UNO_Key/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}