Skip to content

Instantly share code, notes, and snippets.

@dankrause
Last active March 8, 2024 18:31
Show Gist options
  • Save dankrause/5585907 to your computer and use it in GitHub Desktop.
Save dankrause/5585907 to your computer and use it in GitHub Desktop.
Example code to use the (unofficial, unsupported, undocumented) hover.com DNS API.
import requests
class HoverException(Exception):
pass
class HoverAPI(object):
def __init__(self, username, password):
params = {"username": username, "password": password}
r = requests.post("https://www.hover.com/api/login", params=params)
if not r.ok or "hoverauth" not in r.cookies:
raise HoverException(r)
self.cookies = {"hoverauth": r.cookies["hoverauth"]}
def call(self, method, resource, data=None):
url = "https://www.hover.com/api/{0}".format(resource)
r = requests.request(method, url, data=data, cookies=self.cookies)
if not r.ok:
raise HoverException(r)
if r.content:
body = r.json()
if "succeeded" not in body or body["succeeded"] is not True:
raise HoverException(body)
return body
# connect to the API using your account
client = HoverAPI("myusername", "mypassword")
# get details of a domains without DNS records
client.call("get", "domains")
# get all domains and DNS records
client.call("get", "dns")
# notice the "id" field of domains in response to the above calls - that's needed
# to address the domains individually, like so:
# get details of a specific domain without DNS records
client.call("get", "domains/dom123456")
# get DNS records of a specific domain:
client.call("get", "domains/dom123456/dns")
# create a new A record:
record = {"name": "mysubdomain", "type": "A", "content": "127.0.0.1"}
client.call("post", "domains/dom123456/dns", record)
# create a new SRV record
# note that content is "{priority} {weight} {port} {target}"
record = {"name": "mysubdomain", "type": "SRV", "content": "10 10 123 __service"}
client.call("post", "domains/dom123456/dns", record)
# create a new MX record
# note that content is "{priority} {host}"
record = {"name": "mysubdomain", "type": "MX", "content": "10 mail"}
client.call("post", "domains/dom123456/dns", record)
# notice the "id" field of DNS records in the above calls - that's
# needed to address the DNS records individually, like so:
# update an existing DNS record
client.call("put", "dns/dns1234567", {"content": "127.0.0.1"})
# delete a DNS record:
client.call("delete", "dns/dns1234567")
#!/usr/bin/python
"""
bulkhover.py 1.1
This is a command-line script to import and export DNS records for a single
domain into or out of a hover account.
Usage:
bulkhover.py [options] (import|export) <domain> <dnsfile>
bulkhover.py (-h | --help)
bulkhover.py --version
Options:
-h --help Show this screen
--version Show version
-c --conf=<conf> Path to conf
-u --username=<user> Your hover username
-p --password=<pass> Your hover password
-f --flush Delete all existing records before importing
Examples:
The DNS file should have one record per line, in the format:
{name} {type} {content}
For example:
www A 127.0.0.1
@ MX 10 example.com
Since the script output is in the same format as its input, you can use shell
pipelines to do complex operations.
Copy all DNS records from one domain to another:
bulkhover.py -c my.conf export example.com - | ./bulkhover.py -c my.conf -f import other.com -
Copy only MX records from one domain to another:
./bulkhover.py -c my.conf export foo.com - | awk '$2 == "MX" {print $0}' | ./bulkhover.py -c my.conf import bar.com -
To avoid passing your username and password in the command-line, you can use
a conf file that contains them instead:
[hover]
username=YOUR_USERNAME
password=YOUR_PASSWORD
"""
import ConfigParser
import docopt
import requests
import sys
class HoverException(Exception):
pass
class HoverAPI(object):
def __init__(self, username, password):
params = {"username": username, "password": password}
r = requests.post("https://www.hover.com/api/login", params=params)
if not r.ok or "hoverauth" not in r.cookies:
raise HoverException(r)
self.cookies = {"hoverauth": r.cookies["hoverauth"]}
def call(self, method, resource, data=None):
url = "https://www.hover.com/api/{0}".format(resource)
r = requests.request(method, url, data=data, cookies=self.cookies)
if not r.ok:
raise HoverException(r)
if r.content:
body = r.json()
if "succeeded" not in body or body["succeeded"] is not True:
raise HoverException(body)
return body
def import_dns(username, password, domain, filename, flush=False):
try:
client = HoverAPI(username, password)
except HoverException as e:
raise HoverException("Authentication failed")
if flush:
records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"]
for record in records:
client.call("delete", "dns/{0}".format(record["id"]))
print "Deleted {name} {type} {content}".format(**record)
domain_id = client.call("get", "domains/{0}".format(domain))["domain"]["id"]
if filename == "-": filename = "/dev/stdin"
with open(filename, "r") as f:
for line in f:
parts = line.strip().split(" ", 2)
record = {"name": parts[0], "type": parts[1], "content": parts[2]}
client.call("post", "domains/{0}/dns".format(domain), record)
print "Created {name} {type} {content}".format(**record)
def export_dns(username, password, domain, filename):
try:
client = HoverAPI(username, password)
except HoverException as e:
raise HoverException("Authentication failed")
records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"]
if filename == "-": filename = "/dev/stdout"
with open(filename, "w") as f:
for record in records:
f.write("{name} {type} {content}\n".format(**record))
def main(args):
def get_conf(filename):
config = ConfigParser.ConfigParser()
config.read(filename)
items = dict(config.items("hover"))
return items["username"], items["password"]
if args["--conf"] is None:
if not all((args["--username"], args["--password"])):
print("You must specifiy either a conf file, or a username and password")
return 1
else:
username, password = args["--username"], args["--password"]
else:
username, password = get_conf(args["--conf"])
try:
if args["import"]:
import_dns(username, password, args["<domain>"], args["<dnsfile>"], args["--flush"])
elif args["export"]:
export_dns(username, password, args["<domain>"], args["<dnsfile>"])
except HoverException as e:
print "Unable to update DNS: {0}".format(e)
return 1
if __name__ == "__main__":
version = __doc__.strip().split("\n")[0]
args = docopt.docopt(__doc__, version=version)
status = main(args)
sys.exit(status)
#!/usr/bin/env python
"""
dynhover.py 1.2
This tool will update an A record for given (sub)domain in your hover.com
with your IP, or an IP that you specify
Usage:
dynhover.py (-c <conf> | -u <user> -p <password>) <domain>
dynhover.py (-h | --help)
dynhover.py --version
Options:
-h --help Show this screen
--version Show version
-c --conf=<conf> Path to conf
-u --username=<user> Your hover username
-p --password=<pass> Your hover password
-i --ip=<ip> An IP to set (auto-detected by default)
"""
import ConfigParser
import docopt
import requests
import sys
class HoverException(Exception):
pass
class HoverAPI(object):
def __init__(self, username, password):
params = {"username": username, "password": password}
r = requests.post("https://www.hover.com/api/login", params=params)
if not r.ok or "hoverauth" not in r.cookies:
raise HoverException(r)
self.cookies = {"hoverauth": r.cookies["hoverauth"]}
def call(self, method, resource, data=None):
url = "https://www.hover.com/api/{0}".format(resource)
r = requests.request(method, url, data=data, cookies=self.cookies)
if not r.ok:
raise HoverException(r)
if r.content:
body = r.json()
if "succeeded" not in body or body["succeeded"] is not True:
raise HoverException(body)
return body
def get_public_ip():
return requests.get("http://ifconfig.me/ip").content
def update_dns(username, password, fqdn, ip):
try:
client = HoverAPI(username, password)
except HoverException as e:
raise HoverException("Authentication failed")
dns = client.call("get", "dns")
dns_id = None
for domain in dns["domains"]:
if fqdn == domain["domain_name"]:
fqdn = "@.{domain_name}".format(**domain)
for entry in domain["entries"]:
if entry["type"] != "A": continue
if "{0}.{1}".format(entry["name"], domain["domain_name"]) == fqdn:
dns_id = entry["id"]
break
if dns_id is None:
raise HoverException("No DNS record found for {0}".format(fqdn))
response = client.call("put", "dns/{0}".format(dns_id), {"content": my_ip})
if "succeeded" not in response or response["succeeded"] is not True:
raise HoverException(response)
def main(args):
if args["--username"]:
username, password = args["--username"], args["--password"]
else:
config = ConfigParser.ConfigParser()
config.read(args["--conf"])
items = dict(config.items("hover"))
username, password = items["username"], items["password"]
domain = args["<domain>"]
ip = args["--ip"] or get_public_ip()
try:
update_dns(username, password, domain, ip)
except HoverException as e:
print "Unable to update DNS: {0}".format(e)
return 1
return 0
if __name__ == "__main__":
version = __doc__.strip().split("\n")[0]
args = docopt.docopt(__doc__, version=version)
status = main(args)
sys.exit(status)
#!/bin/bash
[[ $# -lt 3 ]] && echo "Usage: $0 USERNAME PASSWORD DNS_ID"
USERNAME=${1}
PASSWORD=${2}
DNS_ID=${3}
# find your DNS ID here: https://www.hover.com/api/domains/yourdomain.com/dns/
# (replace "yourdomain.com" with your actual domain, and look for the record
# you want to change. The ID looks like: dns1234567)
IP=$(curl "http://ifconfig.me/ip" -s)
curl "https://www.hover.com/api/dns/${DNS_ID}" \
-X PUT \
-d "content=${IP}" \
-s \
-b <(curl "https://www.hover.com/api/login" \
-X POST \
-G \
-d "username=${USERNAME}" \
-d "password=${PASSWORD}" \
-s \
-o /dev/null \
-c -)
echo
@wayneconnolly
Copy link

Sorry for the delay in getting back to you... slipped through the cracks!

a 404 error is normal to that URL when doing a GET request... you have use the POST method. Make sure you are doing that by using the "-X POST" command line parameter with curl.

Thank you - curl "https://www.hover.com/api/login" -H "Content-type: application/json" -X POST -d "{"password": "$PASSWORD", "username": "$USERNAME"}" -s -S -c $COOKIEJAR -o /dev/null

I was using -X POST.

I contacted hover.com and it's definately been disabled even with 2FA

Lei (Hover Help Center)
Jul 6, 2023, 12:05 EDT

Hello!
 
Thanks for your reply - API is no longer usable to log in to Hover. 
If using an API is critical to your setup, I may suggest our sister company, [OpenSRS.com](http://opensrs.com/).
 
Best,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment