mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
NEW REGISTRAR: Dynadot (DYNADOT) (#2753)
Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
@ -35,7 +35,7 @@ changelog:
|
||||
regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: 'Provider-specific changes:'
|
||||
regexp: "(?i)((akamaiedge|autodns|axfrd|azure|azure_private_dns|bind|cloudflare|cloudflareapi_old|cloudns|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|easyname|exoscale|gandi|gcloud|gcore|hedns|hetzner|hexonet|hostingde|inwx|linode|loopia|luadns|msdns|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|route53|rwth|softlayer|transip|vultr).*:)+.*"
|
||||
regexp: "(?i)((akamaiedge|autodns|axfrd|azure|azure_private_dns|bind|cloudflare|cloudflareapi_old|cloudns|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|dynadot|easyname|exoscale|gandi|gcloud|gcore|hedns|hetzner|hexonet|hostingde|inwx|linode|loopia|luadns|msdns|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|route53|rwth|softlayer|transip|vultr).*:)+.*"
|
||||
order: 2
|
||||
- title: 'Documentation:'
|
||||
regexp: "(?i)^.*(docs)[(\\w)]*:+.*$"
|
||||
|
1
OWNERS
1
OWNERS
@ -13,6 +13,7 @@ providers/dnsimple @onlyhavecans
|
||||
providers/dnsmadeeasy @vojtad
|
||||
providers/doh @mikenz
|
||||
providers/domainnameshop @SimenBai
|
||||
providers/dynadot @e-im
|
||||
providers/easyname @tresni
|
||||
providers/exoscale @pierre-emmanuelJ
|
||||
providers/gandiv5 @TomOnTime
|
||||
|
@ -64,6 +64,7 @@ Currently supported Domain Registrars:
|
||||
- AWS Route 53
|
||||
- CSC Global
|
||||
- DNSOVERHTTPS
|
||||
- Dynadot
|
||||
- easyname
|
||||
- Gandi
|
||||
- HEXONET
|
||||
|
@ -113,6 +113,7 @@
|
||||
* [DNSimple](providers/dnsimple.md)
|
||||
* [DNS-over-HTTPS](providers/dnsoverhttps.md)
|
||||
* [DOMAINNAMESHOP](providers/domainnameshop.md)
|
||||
* [Dynadot](providers/dynadot.md)
|
||||
* [easyname](providers/easyname.md)
|
||||
* [Exoscale](providers/exoscale.md)
|
||||
* [Gandi_v5](providers/gandi_v5.md)
|
||||
|
@ -29,6 +29,7 @@ If a feature is definitively not supported for whatever reason, we would also li
|
||||
| [`DNSMADEEASY`](providers/dnsmadeeasy.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❌ | ❌ | ❌ | ❔ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`DNSOVERHTTPS`](providers/dnsoverhttps.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
||||
| [`DOMAINNAMESHOP`](providers/domainnameshop.md) | ❌ | ✅ | ❌ | ❔ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ |
|
||||
| [`DYNADOT`](providers/dynadot.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
||||
| [`EASYNAME`](providers/easyname.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
||||
| [`EXOSCALE`](providers/exoscale.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ | ❔ |
|
||||
| [`GANDI_V5`](providers/gandi_v5.md) | ❌ | ✅ | ✅ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ |
|
||||
|
41
documentation/providers/dynadot.md
Normal file
41
documentation/providers/dynadot.md
Normal file
@ -0,0 +1,41 @@
|
||||
DNSControl's Dynadot provider supports being a Registrar. Support for being a DNS Provider is not included, but could be added in the future.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use this provider, add an entry to `creds.json` with `TYPE` set to `DYNADOT`
|
||||
along with `key` from the [Dynadot API](https://www.dynadot.com/account/domain/setting/api.html).
|
||||
|
||||
Example:
|
||||
|
||||
{% code title="creds.json" %}
|
||||
```json
|
||||
{
|
||||
"easyname": {
|
||||
"TYPE": "DYNADOT",
|
||||
"key": "API Key",
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
## Metadata
|
||||
This provider does not recognize any special metadata fields unique to Dynadot.
|
||||
|
||||
## Usage
|
||||
An example configuration:
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```javascript
|
||||
var REG_DYNADOT = NewRegistrar("dynadot");
|
||||
|
||||
DOMAIN_ELSEWHERE("example.com", REG_DYNADOT, [
|
||||
"ns1.example.net.",
|
||||
"ns2.example.net.",
|
||||
"ns3.example.net.",
|
||||
]);
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
## Activation
|
||||
|
||||
You must [enable the Dynadot API](https://www.dynadot.com/account/domain/setting/api.html) for your account and whitelist the IP address of the machine that will run DNSControl.
|
@ -18,6 +18,7 @@ import (
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/dnsmadeeasy"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/doh"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/domainnameshop"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/dynadot"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/easyname"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/exoscale"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/gandiv5"
|
||||
|
135
providers/dynadot/api.go
Normal file
135
providers/dynadot/api.go
Normal file
@ -0,0 +1,135 @@
|
||||
package dynadot
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// API layer for Dynadot
|
||||
|
||||
type dynadotProvider struct {
|
||||
key string
|
||||
}
|
||||
|
||||
type requestParams map[string]string
|
||||
|
||||
type header struct {
|
||||
SuccessCode int `xml:"SuccessCode"`
|
||||
Status string `xml:"Status"`
|
||||
Error string `xml:"Error,omitempty"`
|
||||
}
|
||||
|
||||
type addNsResponse struct {
|
||||
XMLName xml.Name `xml:"AddNsResponse"`
|
||||
AddNsHeader header `xml:"AddNsHeader"`
|
||||
}
|
||||
|
||||
type setNsResponse struct {
|
||||
XMLName xml.Name `xml:"SetNsResponse"`
|
||||
SetNsHeader header `xml:"SetNsHeader"`
|
||||
}
|
||||
|
||||
type getNsResponse struct {
|
||||
XMLName xml.Name `xml:"GetNsResponse"`
|
||||
NsContent nsContent `xml:"NsContent"`
|
||||
GetNsHeader header `xml:"GetNsHeader"`
|
||||
}
|
||||
|
||||
type nsContent struct {
|
||||
Host []string `xml:"Host"`
|
||||
NsName string `xml:"NsName"`
|
||||
}
|
||||
|
||||
func (c *dynadotProvider) getNameservers(domain string) ([]string, error) {
|
||||
var bodyString, err = c.get("get_ns", requestParams{"domain": domain})
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("failed NS list (Dynadot): %s", err)
|
||||
}
|
||||
var ns getNsResponse
|
||||
xml.Unmarshal(bodyString, &ns)
|
||||
|
||||
if ns.GetNsHeader.SuccessCode != 0 {
|
||||
return []string{}, fmt.Errorf("failed NS list (Dynadot): %s", ns.GetNsHeader.Error)
|
||||
}
|
||||
|
||||
hosts := []string{}
|
||||
hosts = append(hosts, ns.NsContent.Host...)
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (c *dynadotProvider) updateNameservers(ns []string, domain string) error {
|
||||
if len(ns) > 13 {
|
||||
return fmt.Errorf("failed NS update (Dynadot): only up to 13 nameservers are supported")
|
||||
}
|
||||
|
||||
// Nameservers must first be added to the Dynadot account
|
||||
for _, host := range ns {
|
||||
b, err := c.get("add_ns", requestParams{"host": host})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed NS add (Dynadot): %s", err)
|
||||
}
|
||||
var resp addNsResponse
|
||||
err = xml.Unmarshal(b, &resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed NS add (Dynadot): %s", err)
|
||||
}
|
||||
|
||||
if resp.AddNsHeader.SuccessCode != 0 {
|
||||
// No apparent way to get all existing entries on an account, so filter
|
||||
if strings.Contains(resp.AddNsHeader.Error, "already exists") {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed NS add (Dynadot): %s", resp.AddNsHeader.Error)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
rec := requestParams{}
|
||||
rec["domain"] = domain
|
||||
// supported prams: ns0 - ns12
|
||||
for i, h := range ns {
|
||||
rec[fmt.Sprintf("%s%d", "ns", i)] = h
|
||||
}
|
||||
|
||||
b, err := c.get("set_ns", rec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed NS set (Dynadot): %s", err)
|
||||
}
|
||||
|
||||
var resp setNsResponse
|
||||
err = xml.Unmarshal(b, &resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed NS add (Dynadot): %s", err)
|
||||
}
|
||||
|
||||
if resp.SetNsHeader.SuccessCode != 0 {
|
||||
return fmt.Errorf("failed NS add (Dynadot): %s", resp.SetNsHeader.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *dynadotProvider) get(command string, params requestParams) ([]byte, error) {
|
||||
client := &http.Client{}
|
||||
req, _ := http.NewRequest("GET", "https://api.dynadot.com/api3.xml", nil)
|
||||
q := req.URL.Query()
|
||||
|
||||
q.Add("key", c.key)
|
||||
q.Add("command", command)
|
||||
|
||||
for pName, pValue := range params {
|
||||
q.Add(pName, pValue)
|
||||
}
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
62
providers/dynadot/dynadotProvider.go
Normal file
62
providers/dynadot/dynadotProvider.go
Normal file
@ -0,0 +1,62 @@
|
||||
package dynadot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
Dynadot Registrator:
|
||||
|
||||
Info required in `creds.json`:
|
||||
- key API Key
|
||||
|
||||
*/
|
||||
|
||||
func init() {
|
||||
providers.RegisterRegistrarType("DYNADOT", newDynadot)
|
||||
}
|
||||
|
||||
func newDynadot(m map[string]string) (providers.Registrar, error) {
|
||||
d := &dynadotProvider{}
|
||||
|
||||
d.key = m["key"]
|
||||
if d.key == "" {
|
||||
return nil, fmt.Errorf("missing Dynadot key")
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (c *dynadotProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
nss, err := c.getNameservers(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
foundNameservers := strings.Join(nss, ",")
|
||||
|
||||
expected := []string{}
|
||||
for _, ns := range dc.Nameservers {
|
||||
name := strings.TrimRight(ns.Name, ".")
|
||||
expected = append(expected, name)
|
||||
}
|
||||
sort.Strings(expected)
|
||||
expectedNameservers := strings.Join(expected, ",")
|
||||
|
||||
if foundNameservers != expectedNameservers {
|
||||
return []*models.Correction{
|
||||
{
|
||||
Msg: fmt.Sprintf("Update nameservers (%s) -> (%s)", foundNameservers, expectedNameservers),
|
||||
F: func() error {
|
||||
return c.updateNameservers(expected, dc.Name)
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
Reference in New Issue
Block a user