Browse Source

Merge pull request #953 from angel333/master

Add DNS API script for Hurricane Electric DNS service
neil 7 years ago
parent
commit
4af339424a
3 changed files with 195 additions and 0 deletions
  1. 1 0
      README.md
  2. 19 0
      dnsapi/README.md
  3. 175 0
      dnsapi/dns_he.sh

+ 1 - 0
README.md

@@ -338,6 +338,7 @@ You don't have to do anything manually!
 1. Name.com API
 1. Dyn Managed DNS API
 1. Yandex PDD API (https://pdd.yandex.ru)
+1. Hurricane Electric DNS service (https://dns.he.net)
 
 
 And: 

+ 19 - 0
dnsapi/README.md

@@ -585,6 +585,25 @@ acme.sh --issue --dns dns_yandex -d mydomain.example.org
 
 For issues, please report to https://github.com/non7top/acme.sh/issues.
 
+# 31. Use Hurricane Electric
+
+Hurricane Electric doesn't have an API so just set your login credentials like so:
+
+```
+export HE_Username="yourusername"
+export HE_Password="password"
+```
+
+Then you can issue your certificate:
+
+```
+acme.sh --issue --dns dns_he -d example.com -d www.example.com
+```
+
+The `HE_Username` and `HE_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+Please report any issues to https://github.com/angel333/acme.sh or to <me@ondrejsimek.com>.
+
 # Use custom API
 
 If your API is not supported yet, you can write your own DNS API.

+ 175 - 0
dnsapi/dns_he.sh

@@ -0,0 +1,175 @@
+#!/usr/bin/env sh
+
+########################################################################
+# Hurricane Electric hook script for acme.sh
+#
+# Environment variables:
+#
+#  - $HE_Username  (your dns.he.net username)
+#  - $HE_Password  (your dns.he.net password)
+#
+# Author: Ondrej Simek <me@ondrejsimek.com>
+# Git repo: https://github.com/angel333/acme.sh
+
+#-- dns_he_add() - Add TXT record --------------------------------------
+# Usage: dns_he_add _acme-challenge.subdomain.domain.com "XyZ123..."
+
+dns_he_add() {
+  _full_domain=$1
+  _txt_value=$2
+  _info "Using DNS-01 Hurricane Electric hook"
+
+  if [ -z "$HE_Username" ] || [ -z "$HE_Password" ]; then
+    HE_Username=
+    HE_Password=
+    _err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password envoronment variables."
+    return 1
+  fi
+  _saveaccountconf HE_Username "$HE_Username"
+  _saveaccountconf HE_Password "$HE_Password"
+
+  # Fills in the $_zone_id
+  _find_zone "$_full_domain" || return 1
+  _debug "Zone id \"$_zone_id\" will be used."
+
+  body="email=${HE_Username}&pass=${HE_Password}"
+  body="$body&account="
+  body="$body&menu=edit_zone"
+  body="$body&Type=TXT"
+  body="$body&hosted_dns_zoneid=$_zone_id"
+  body="$body&hosted_dns_recordid="
+  body="$body&hosted_dns_editzone=1"
+  body="$body&Priority="
+  body="$body&Name=$_full_domain"
+  body="$body&Content=$_txt_value"
+  body="$body&TTL=300"
+  body="$body&hosted_dns_editrecord=Submit"
+  response="$(_post "$body" "https://dns.he.net/")"
+  exit_code="$?"
+  if [ "$exit_code" -eq 0 ]; then
+    _info "TXT record added successfuly."
+  else
+    _err "Couldn't add the TXT record."
+  fi
+  _debug2 response "$response"
+  return "$exit_code"
+}
+
+#-- dns_he_rm() - Remove TXT record ------------------------------------
+# Usage: dns_he_rm _acme-challenge.subdomain.domain.com "XyZ123..."
+
+dns_he_rm() {
+  _full_domain=$1
+  _txt_value=$2
+  _info "Cleaning up after DNS-01 Hurricane Electric hook"
+
+  # fills in the $_zone_id
+  _find_zone "$_full_domain" || return 1
+  _debug "Zone id \"$_zone_id\" will be used."
+
+  # Find the record id to clean
+  body="email=${HE_Username}&pass=${HE_Password}"
+  body="$body&hosted_dns_zoneid=$_zone_id"
+  body="$body&menu=edit_zone"
+  body="$body&hosted_dns_editzone="
+  domain_regex="$(echo "$_full_domain" | sed 's/\./\\./g')" # escape dots
+  _record_id=$(_post "$body" "https://dns.he.net/" \
+    | tr -d '\n' \
+    | _egrep_o "data=\"&quot;${_txt_value}&quot;([^>]+>){6}[^<]+<[^;]+;deleteRecord\('[0-9]+','${domain_regex}','TXT'\)" \
+    | _egrep_o "[0-9]+','${domain_regex}','TXT'\)$" \
+    | _egrep_o "^[0-9]+"
+  )
+  # The series of egreps above could have been done a bit shorter but
+  #  I wanted to double-check whether it's the correct record (in case
+  #  HE changes their website somehow).
+
+  # Remove the record
+  body="email=${HE_Username}&pass=${HE_Password}"
+  body="$body&menu=edit_zone"
+  body="$body&hosted_dns_zoneid=$_zone_id"
+  body="$body&hosted_dns_recordid=$_record_id"
+  body="$body&hosted_dns_editzone=1"
+  body="$body&hosted_dns_delrecord=1"
+  body="$body&hosted_dns_delconfirm=delete"
+  _post "$body" "https://dns.he.net/" \
+    | grep '<div id="dns_status" onClick="hideThis(this);">Successfully removed record.</div>' \
+      >/dev/null
+  exit_code="$?"
+  if [ "$exit_code" -eq 0 ]; then
+    _info "Record removed successfuly."
+  else
+    _err "Could not clean (remove) up the record. Please go to HE administration interface and clean it by hand."
+    return "$exit_code"
+  fi
+}
+
+########################## PRIVATE FUNCTIONS ###########################
+
+#-- _find_zone() -------------------------------------------------------
+# Returns the most specific zone found in administration interface.
+#
+# Example:
+#
+# _find_zone first.second.third.co.uk
+#
+# ... will return the first zone that exists in admin out of these:
+# - "first.second.third.co.uk"
+# - "second.third.co.uk"
+# - "third.co.uk"
+# - "co.uk" <-- unlikely
+# - "uk"    <-'
+#
+# (another approach would be something like this:
+#   https://github.com/hlandau/acme/blob/master/_doc/dns.hook
+#   - that's better if there are multiple pages. It's so much simpler.
+# )
+
+_find_zone() {
+
+  _domain="$1"
+
+  body="email=${HE_Username}&pass=${HE_Password}"
+  _matches=$(_post "$body" "https://dns.he.net/" \
+    | _egrep_o "delete_dom.*name=\"[^\"]+\" value=\"[0-9]+"
+  )
+  # Zone names and zone IDs are in same order
+  _zone_ids=$(echo "$_matches" | cut -d '"' -f 5)
+  _zone_names=$(echo "$_matches" | cut -d '"' -f 3)
+  _debug2 "These are the zones on this HE account:"
+  _debug2 "$_zone_names"
+  _debug2 "And these are their respective IDs:"
+  _debug2 "$_zone_ids"
+
+  # Walk through all possible zone names
+  _strip_counter=1
+  while true; do
+    _attempted_zone=$(echo "$_domain" | cut -d . -f ${_strip_counter}-)
+
+    # All possible zone names have been tried
+    if [ -z "$_attempted_zone" ]; then
+      _err "No zone for domain \"$_domain\" found."
+      return 1
+    fi
+
+    _debug "Looking for zone \"${_attempted_zone}\""
+
+    # Take care of "." and only match whole lines. Note that grep -F
+    # cannot be used because there's no way to make it match whole
+    # lines.
+    regex="^$(echo "$_attempted_zone" | sed 's/\./\\./g')$"
+    line_num=$(echo "$_zone_names" \
+      | grep -n "$regex" \
+      | cut -d : -f 1
+    )
+
+    if [ -n "$line_num" ]; then
+      _zone_id=$(echo "$_zone_ids" | sed "${line_num}q;d")
+      _debug "Found relevant zone \"$_attempted_zone\" with id \"$_zone_id\" - will be used for domain \"$_domain\"."
+      return 0
+    fi
+
+    _debug "Zone \"$_attempted_zone\" doesn't exist, let's try a less specific zone."
+    _strip_counter=$(_math "$_strip_counter" + 1)
+  done
+}
+# vim: et:ts=2:sw=2: