Browse Source

Merge pull request #1494 from Neilpang/dev

sync
neil 7 years ago
parent
commit
09fed60dec
7 changed files with 330 additions and 22 deletions
  1. 1 1
      README.md
  2. 1 1
      acme.sh
  3. 22 0
      dnsapi/README.md
  4. 12 7
      dnsapi/dns_azure.sh
  5. 13 4
      dnsapi/dns_he.sh
  6. 227 0
      dnsapi/dns_loopia.sh
  7. 54 9
      dnsapi/dns_pdns.sh

+ 1 - 1
README.md

@@ -317,7 +317,7 @@ You don't have to do anything manually!
 1. DirectAdmin API
 1. DirectAdmin API
 1. KingHost (https://www.kinghost.com.br/)
 1. KingHost (https://www.kinghost.com.br/)
 1. Zilore (https://zilore.com)
 1. Zilore (https://zilore.com)
-
+1. Loopia.se API
 
 
 And: 
 And: 
 
 

+ 1 - 1
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-VER=2.7.8
+VER=2.7.9
 
 
 PROJECT_NAME="acme.sh"
 PROJECT_NAME="acme.sh"
 
 

+ 22 - 0
dnsapi/README.md

@@ -814,6 +814,28 @@ acme.sh --issue --dns dns_zilore -d example.com -d *.example.com
 
 
 The `Zilore_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 The `Zilore_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
+## 44. Use Loopia.se API
+User must provide login credentials to the Loopia API.
+The user needs the following permissions:
+
+- addSubdomain
+- updateZoneRecord
+- getDomains
+- removeSubdomain
+
+Set the login credentials:
+```
+export LOOPIA_User="user@loopiaapi"
+export LOOPIA_Password="password"
+```
+
+And to issue a cert:
+```
+acme.sh --issue --dns dns_loopia -d example.com -d *.example.com
+```
+
+The username and password 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.

+ 12 - 7
dnsapi/dns_azure.sh

@@ -76,10 +76,10 @@ dns_azure_add() {
   values="{\"value\":[\"$txtvalue\"]}"
   values="{\"value\":[\"$txtvalue\"]}"
   timestamp="$(_time)"
   timestamp="$(_time)"
   if [ "$_code" = "200" ]; then
   if [ "$_code" = "200" ]; then
-    vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"")"
+    vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"")"
     _debug "existing TXT found"
     _debug "existing TXT found"
     _debug "$vlist"
     _debug "$vlist"
-    existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\s*:\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")"
+    existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")"
     if [ -z "$existingts" ]; then
     if [ -z "$existingts" ]; then
       # the record was not created by acme.sh. Copy the exisiting entires
       # the record was not created by acme.sh. Copy the exisiting entires
       existingts=$timestamp
       existingts=$timestamp
@@ -172,7 +172,7 @@ dns_azure_rm() {
   _azure_rest GET "$acmeRecordURI" "" "$accesstoken"
   _azure_rest GET "$acmeRecordURI" "" "$accesstoken"
   timestamp="$(_time)"
   timestamp="$(_time)"
   if [ "$_code" = "200" ]; then
   if [ "$_code" = "200" ]; then
-    vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")"
+    vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")"
     values=""
     values=""
     comma=""
     comma=""
     for v in $vlist; do
     for v in $vlist; do
@@ -230,7 +230,7 @@ _azure_rest() {
     fi
     fi
     _ret="$?"
     _ret="$?"
     _secure_debug2 "response $response"
     _secure_debug2 "response $response"
-    _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
+    _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
     _debug "http response code $_code"
     _debug "http response code $_code"
     if [ "$_code" = "401" ]; then
     if [ "$_code" = "401" ]; then
       # we have an invalid access token set to expired
       # we have an invalid access token set to expired
@@ -308,7 +308,7 @@ _get_root() {
   domain=$1
   domain=$1
   subscriptionId=$2
   subscriptionId=$2
   accesstoken=$3
   accesstoken=$3
-  i=2
+  i=1
   p=1
   p=1
 
 
   ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
   ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
@@ -328,9 +328,14 @@ _get_root() {
     fi
     fi
 
 
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
-      _domain_id=$(echo "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+      _domain_id=$(echo "$response" | _egrep_o "\\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
       if [ "$_domain_id" ]; then
       if [ "$_domain_id" ]; then
-        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        if [ "$i" = 1 ]; then
+          #create the record at the domain apex (@) if only the domain name was provided as --domain-alias
+          _sub_domain="@"
+        else
+          _sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
+        fi
         _domain=$h
         _domain=$h
         return 0
         return 0
       fi
       fi

+ 13 - 4
dnsapi/dns_he.sh

@@ -33,8 +33,9 @@ dns_he_add() {
   # Fills in the $_zone_id
   # Fills in the $_zone_id
   _find_zone "$_full_domain" || return 1
   _find_zone "$_full_domain" || return 1
   _debug "Zone id \"$_zone_id\" will be used."
   _debug "Zone id \"$_zone_id\" will be used."
-
-  body="email=${HE_Username}&pass=${HE_Password}"
+  username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
+  password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
+  body="email=${username_encoded}&pass=${password_encoded}"
   body="$body&account="
   body="$body&account="
   body="$body&menu=edit_zone"
   body="$body&menu=edit_zone"
   body="$body&Type=TXT"
   body="$body&Type=TXT"
@@ -71,7 +72,9 @@ dns_he_rm() {
   _debug "Zone id \"$_zone_id\" will be used."
   _debug "Zone id \"$_zone_id\" will be used."
 
 
   # Find the record id to clean
   # Find the record id to clean
-  body="email=${HE_Username}&pass=${HE_Password}"
+  username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
+  password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
+  body="email=${username_encoded}&pass=${password_encoded}"
   body="$body&hosted_dns_zoneid=$_zone_id"
   body="$body&hosted_dns_zoneid=$_zone_id"
   body="$body&menu=edit_zone"
   body="$body&menu=edit_zone"
   body="$body&hosted_dns_editzone="
   body="$body&hosted_dns_editzone="
@@ -112,9 +115,15 @@ dns_he_rm() {
 
 
 _find_zone() {
 _find_zone() {
   _domain="$1"
   _domain="$1"
-  body="email=${HE_Username}&pass=${HE_Password}"
+  username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
+  password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
+  body="email=${username_encoded}&pass=${password_encoded}"
   response="$(_post "$body" "https://dns.he.net/")"
   response="$(_post "$body" "https://dns.he.net/")"
   _debug2 response "$response"
   _debug2 response "$response"
+  if _contains "$response" '>Incorrect<'; then
+    _err "Unable to login to dns.he.net please check username and password"
+    return 1
+  fi
   _table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')"
   _table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')"
   _debug2 _table "$_table"
   _debug2 _table "$_table"
   _matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')"
   _matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')"

+ 227 - 0
dnsapi/dns_loopia.sh

@@ -0,0 +1,227 @@
+#!/usr/bin/env sh
+
+#
+#LOOPIA_User="username"
+#
+#LOOPIA_Password="password"
+
+LOOPIA_Api="https://api.loopia.se/RPCSERV"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_loopia_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
+  LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
+  if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
+    LOOPIA_User=""
+    LOOPIA_Password=""
+    _err "You don't specify loopia 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 LOOPIA_User "$LOOPIA_User"
+  _saveaccountconf_mutable LOOPIA_Password "$LOOPIA_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"
+
+  _info "Adding record"
+
+  _loopia_add_record "$_domain" "$_sub_domain"
+  _loopia_update_record "$_domain" "$_sub_domain" "$txtvalue"
+
+}
+
+dns_loopia_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
+  LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
+  if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
+    LOOPIA_User=""
+    LOOPIA_Password=""
+    _err "You don't specify LOOPIA 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 LOOPIA_User "$LOOPIA_User"
+  _saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>removeSubdomain</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$_domain" "$_sub_domain")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+
+  if ! _contains "$response" "OK"; then
+    _err "Error could not get txt records"
+    return 1
+  fi
+}
+
+####################  Private functions below ##################################
+
+_get_root() {
+  domain=$1
+  _debug "get root"
+
+  domain=$1
+  i=2
+  p=1
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>getDomains</methodName>
+  <params>
+   <param>
+    <value><string>%s</string></value>
+   </param>
+   <param>
+    <value><string>%s</string></value>
+   </param>
+  </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password)
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+  while true; do
+    h=$(echo "$domain" | cut -d . -f $i-100)
+    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
+
+}
+
+_loopia_update_record() {
+  domain=$1
+  sub_domain=$2
+  txtval=$3
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>updateZoneRecord</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <struct>
+          <member>
+            <name>type</name>
+            <value><string>TXT</string></value>
+          </member>
+          <member>
+            <name>priority</name>
+            <value><int>0</int></value>
+          </member>
+          <member>
+            <name>ttl</name>
+            <value><int>60</int></value>
+          </member>
+          <member>
+            <name>rdata</name>
+            <value><string>%s</string></value>
+          </member>
+          <member>
+            <name>record_id</name>
+            <value><int>0</int></value>
+          </member>
+        </struct>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain" "$txtval")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+
+  if ! _contains "$response" "OK"; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+}
+
+_loopia_add_record() {
+  domain=$1
+  sub_domain=$2
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>addSubdomain</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+
+  if ! _contains "$response" "OK"; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+}

+ 54 - 9
dnsapi/dns_pdns.sh

@@ -69,15 +69,21 @@ dns_pdns_add() {
 #fulldomain
 #fulldomain
 dns_pdns_rm() {
 dns_pdns_rm() {
   fulldomain=$1
   fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$PDNS_Ttl" ]; then
+    PDNS_Ttl="$DEFAULT_PDNS_TTL"
+  fi
 
 
   _debug "Detect root zone"
   _debug "Detect root zone"
   if ! _get_root "$fulldomain"; then
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"
     _err "invalid domain"
     return 1
     return 1
   fi
   fi
+
   _debug _domain "$_domain"
   _debug _domain "$_domain"
 
 
-  if ! rm_record "$_domain" "$fulldomain"; then
+  if ! rm_record "$_domain" "$fulldomain" "$txtvalue"; then
     return 1
     return 1
   fi
   fi
 
 
@@ -88,9 +94,16 @@ set_record() {
   _info "Adding record"
   _info "Adding record"
   root=$1
   root=$1
   full=$2
   full=$2
-  txtvalue=$3
+  new_challenge=$3
 
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then
+  _record_string=""
+  _build_record_string "$new_challenge"
+  _list_existingchallenges
+  for oldchallenge in $_existing_challenges; do
+    _build_record_string "$oldchallenge"
+  done
+
+  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
     _err "Set txt record error."
     _err "Set txt record error."
     return 1
     return 1
   fi
   fi
@@ -106,14 +119,37 @@ rm_record() {
   _info "Remove record"
   _info "Remove record"
   root=$1
   root=$1
   full=$2
   full=$2
+  txtvalue=$3
 
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
-    _err "Delete txt record error."
-    return 1
-  fi
+  #Enumerate existing acme challenges
+  _list_existingchallenges
 
 
-  if ! notify_slaves "$root"; then
-    return 1
+  if _contains "$_existing_challenges" "$txtvalue"; then
+    #Delete all challenges (PowerDNS API does not allow to delete content)
+    if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
+      _err "Delete txt record error."
+      return 1
+    fi
+    _record_string=""
+    #If the only existing challenge was the challenge to delete: nothing to do
+    if ! [ "$_existing_challenges" = "$txtvalue" ]; then
+      for oldchallenge in $_existing_challenges; do
+        #Build up the challenges to re-add, ommitting the one what should be deleted
+        if ! [ "$oldchallenge" = "$txtvalue" ]; then
+          _build_record_string "$oldchallenge"
+        fi
+      done
+      #Recreate the existing challenges
+      if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+        _err "Set txt record error."
+        return 1
+      fi
+    fi
+    if ! notify_slaves "$root"; then
+      return 1
+    fi
+  else
+    _info "Record not found, nothing to remove"
   fi
   fi
 
 
   return 0
   return 0
@@ -185,3 +221,12 @@ _pdns_rest() {
 
 
   return 0
   return 0
 }
 }
+
+_build_record_string() {
+  _record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}"
+}
+
+_list_existingchallenges() {
+  _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones/$root"
+  _existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p')
+}