Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e1af303
commit ce87321
Showing
20 changed files
with
1,686 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package hexonet | ||
|
||
// EnsureDomainExists returns an error | ||
// * if access to dnszone is not allowed (not authorized) or | ||
// * if it doesn't exist and creating it fails | ||
func (n *HXClient) EnsureDomainExists(domain string) error { | ||
r := n.client.Request(map[string]string{ | ||
"COMMAND": "StatusDNSZone", | ||
"DNSZONE": domain + ".", | ||
}) | ||
code := r.Code() | ||
if code == 545 { | ||
r = n.client.Request(map[string]string{ | ||
"COMMAND": "CreateDNSZone", | ||
"DNSZONE": domain + ".", | ||
}) | ||
if !r.IsSuccess() { | ||
return n.GetHXApiError("Failed to create not existing zone for domain", domain, r) | ||
} | ||
} else if code == 531 { | ||
return n.GetHXApiError("Not authorized to manage dnszone", domain, r) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package hexonet | ||
|
||
import ( | ||
lr "github.com/hexonet/go-sdk/response/listresponse" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// GetHXApiError returns an error including API error code and error description. | ||
func (n *HXClient) GetHXApiError(format string, objectid string, r *lr.ListResponse) error { | ||
return errors.Errorf(format+" %s. [%s %s]", objectid, r.Code(), r.Description()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Package hexonet implements a registrar that uses the hexonet api to set name servers. It will self register it's providers when imported. | ||
package hexonet | ||
|
||
import ( | ||
"encoding/json" | ||
|
||
"github.com/StackExchange/dnscontrol/providers" | ||
hxcl "github.com/hexonet/go-sdk/client" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// HXClient describes a connection to the hexonet API. | ||
type HXClient struct { | ||
APILogin string | ||
APIPassword string | ||
APIEntity string | ||
client *hxcl.Client | ||
} | ||
|
||
var features = providers.DocumentationNotes{ | ||
providers.CanUseAlias: providers.Can(), | ||
providers.CanUseCAA: providers.Cannot(), | ||
providers.CanUsePTR: providers.Can(), | ||
providers.CanUseRoute53Alias: providers.Cannot(), | ||
providers.CanUseSRV: providers.Can(), | ||
providers.CanUseTLSA: providers.Cannot(), | ||
providers.CanUseTXTMulti: providers.Can(), | ||
providers.CantUseNOPURGE: providers.Cannot(), | ||
providers.DocCreateDomains: providers.Can(), | ||
providers.DocDualHost: providers.Cannot("Apex NS records not editable"), | ||
providers.DocOfficiallySupported: providers.Can(), | ||
} | ||
|
||
func newProvider(conf map[string]string) (*HXClient, error) { | ||
api := &HXClient{ | ||
client: hxcl.NewClient(), | ||
} | ||
api.APILogin, api.APIPassword, api.APIEntity = conf["apilogin"], conf["apipassword"], conf["apientity"] | ||
if api.APIEntity != "1234" && api.APIEntity != "54cd" { | ||
return nil, errors.Errorf("wrong api system entity used. use \"1234\" for OT&E system or \"54cd\" for Live system") | ||
} | ||
if api.APIEntity == "1234" { | ||
api.client.UseOTESystem() | ||
} | ||
if api.APILogin == "" || api.APIPassword == "" { | ||
return nil, errors.Errorf("missing login credentials apilogin or apipassword") | ||
} | ||
api.client.SetCredentials(api.APILogin, api.APIPassword, "") | ||
return api, nil | ||
} | ||
|
||
func newReg(conf map[string]string) (providers.Registrar, error) { | ||
return newProvider(conf) | ||
} | ||
|
||
func newDsp(conf map[string]string, meta json.RawMessage) (providers.DNSServiceProvider, error) { | ||
return newProvider(conf) | ||
} | ||
|
||
func init() { | ||
providers.RegisterRegistrarType("HEXONET", newReg) | ||
providers.RegisterDomainServiceProviderType("HEXONET", newDsp, features) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package hexonet | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"regexp" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/StackExchange/dnscontrol/models" | ||
) | ||
|
||
var defaultNameservers = []*models.Nameserver{ | ||
{Name: "ns1.ispapi.net"}, | ||
{Name: "ns2.ispapi.net"}, | ||
{Name: "ns3.ispapi.net"}, | ||
} | ||
|
||
var nsRegex = regexp.MustCompile(`ns([1-3]{1})[0-9]+\.ispapi\.net`) | ||
|
||
// GetNameservers gets the nameservers set on a domain. | ||
func (n *HXClient) GetNameservers(domain string) ([]*models.Nameserver, error) { | ||
// This is an interesting edge case. hexonet expects you to SET the nameservers to ns[1-3].ispapi.net, | ||
// but it will internally set it to (ns1xyz|ns2uvw|ns3asd).ispapi.net, where xyz/uvw/asd is a uniqueish number. | ||
// In order to avoid endless loops, we will use the unique nameservers if present, or else the generic ones if not. | ||
nss, err := n.getNameserversRaw(domain) | ||
if err != nil { | ||
return nil, err | ||
} | ||
toUse := []string{ | ||
defaultNameservers[0].Name, | ||
defaultNameservers[1].Name, | ||
defaultNameservers[2].Name, | ||
} | ||
for _, ns := range nss { | ||
if matches := nsRegex.FindStringSubmatch(ns); len(matches) == 2 && len(matches[1]) == 1 { | ||
idx := matches[1][0] - '1' // regex ensures proper range | ||
toUse[idx] = matches[0] | ||
} | ||
} | ||
return models.StringsToNameservers(toUse), nil | ||
} | ||
|
||
func (n *HXClient) getNameserversRaw(domain string) ([]string, error) { | ||
r := n.client.Request(map[string]string{ | ||
"COMMAND": "StatusDomain", | ||
"DOMAIN": domain, | ||
}) | ||
code := r.Code() | ||
if code != 200 { | ||
return nil, n.GetHXApiError("Could not get status for domain", domain, r) | ||
} | ||
ns := r.GetColumn("NAMESERVER") | ||
sort.Strings(ns) | ||
return ns, nil | ||
} | ||
|
||
// GetRegistrarCorrections gathers corrections that would being n to match dc. | ||
func (n *HXClient) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { | ||
nss, err := n.getNameserversRaw(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: n.updateNameservers(expected, dc.Name), | ||
}, | ||
}, nil | ||
} | ||
return nil, nil | ||
} | ||
|
||
func (n *HXClient) updateNameservers(ns []string, domain string) func() error { | ||
return func() error { | ||
cmd := map[string]string{ | ||
"COMMAND": "ModifyDomain", | ||
"DOMAIN": domain, | ||
} | ||
for idx, ns := range ns { | ||
cmd["NAMESERVER"+strconv.Itoa(idx)] = ns | ||
} | ||
response := n.client.Request(cmd) | ||
code := response.Code() | ||
if code != 200 { | ||
return errors.New(strconv.Itoa(code) + " " + response.Description()) | ||
} | ||
return nil | ||
} | ||
} |
Oops, something went wrong.