mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-09-20 06:46:19 +08:00
NEW DNS PROVIDER: Realtime Register (REALTIMEREGISTER) (#2741)
Co-authored-by: pieterjan.eilers <pieterjan.eilers@realtimeregister.com> Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
parent
f46004eff9
commit
3d570ead31
|
@ -36,7 +36,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|bunnydns|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).*:)+.*"
|
||||
regexp: "(?i)((akamaiedge|autodns|axfrd|azure|azure_private_dns|bind|bunnydns|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|realtimeregister|route53|rwth|softlayer|transip|vultr).*:)+.*"
|
||||
order: 2
|
||||
- title: 'Documentation:'
|
||||
regexp: "(?i)^.*(docs)[(\\w)]*:+.*$"
|
||||
|
|
1
OWNERS
1
OWNERS
|
@ -42,6 +42,7 @@ providers/ovh @masterzen
|
|||
providers/packetframe @hamptonmoore
|
||||
providers/porkbun @imlonghao
|
||||
providers/powerdns @jpbede
|
||||
providers/realtimeregister @PJEilers
|
||||
providers/route53 @tresni
|
||||
providers/rwth @mistererwin
|
||||
# providers/softlayer NEEDS VOLUNTEER
|
||||
|
|
|
@ -55,6 +55,7 @@ Currently supported DNS providers:
|
|||
- Packetframe
|
||||
- Porkbun
|
||||
- PowerDNS
|
||||
- Realtime Register
|
||||
- RWTH DNS-Admin
|
||||
- SoftLayer
|
||||
- TransIP
|
||||
|
@ -76,6 +77,7 @@ Currently supported Domain Registrars:
|
|||
- Name.com
|
||||
- OpenSRS
|
||||
- OVH
|
||||
- Realtime Register
|
||||
|
||||
At Stack Overflow, we use this system to manage hundreds of domains
|
||||
and subdomains across multiple registrars and DNS providers.
|
||||
|
|
|
@ -142,6 +142,7 @@
|
|||
* [Packetframe](providers/packetframe.md)
|
||||
* [Porkbun](providers/porkbun.md)
|
||||
* [PowerDNS](providers/powerdns.md)
|
||||
* [Realtime Register](providers/realtimeregister.md)
|
||||
* [RWTH DNS-Admin](providers/rwth.md)
|
||||
* [SoftLayer DNS](providers/softlayer.md)
|
||||
* [TransIP](providers/transip.md)
|
||||
|
|
|
@ -58,6 +58,7 @@ If a feature is definitively not supported for whatever reason, we would also li
|
|||
| [`PACKETFRAME`](providers/packetframe.md) | ❌ | ✅ | ❌ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❌ | ❌ | ✅ | ❔ |
|
||||
| [`PORKBUN`](providers/porkbun.md) | ❌ | ✅ | ✅ | ✅ | ❔ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❔ | ❌ | ❌ | ✅ | ✅ |
|
||||
| [`POWERDNS`](providers/powerdns.md) | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`REALTIMEREGISTER`](providers/realtimeregister.md) | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
|
||||
| [`ROUTE53`](providers/route53.md) | ✅ | ✅ | ✅ | ❌ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`RWTH`](providers/rwth.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ❔ | ❌ | ❌ | ✅ | ❔ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ | ✅ |
|
||||
| [`SOFTLAYER`](providers/softlayer.md) | ❌ | ✅ | ❌ | ❔ | ❔ | ❔ | ❌ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
|
||||
|
@ -143,6 +144,7 @@ Providers in this category and their maintainers are:
|
|||
|[`OVH`](providers/ovh.md)|@masterzen|
|
||||
|[`PACKETFRAME`](providers/packetframe.md)|@hamptonmoore|
|
||||
|[`POWERDNS`](providers/powerdns.md)|@jpbede|
|
||||
|[`REALTIMEREGISTER`](providers/realtimeregister.md)|@PJEilers|
|
||||
|[`ROUTE53`](providers/route53.md)|@tresni|
|
||||
|[`RWTH`](providers/rwth.md)|@MisterErwin|
|
||||
|[`SOFTLAYER`](providers/softlayer.md)|@jamielennox|
|
||||
|
|
46
documentation/providers/realtimeregister.md
Normal file
46
documentation/providers/realtimeregister.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
[realtimeregister.com](https://realtimeregister.com) is a domain registrar based in the Netherlands.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use this provider, add an entry to `creds.json` with `TYPE` set to `REALTIMEREGISTER`
|
||||
along with your API-key. Further configuration includes a flag indicating BASIC or PREMIUM DNS-service and a flag
|
||||
indicating the use of the sandbox environment
|
||||
|
||||
**Example:**
|
||||
|
||||
{% code title="creds.json" %}
|
||||
```json
|
||||
{
|
||||
"realtimeregister": {
|
||||
"TYPE": "REALTIMEREGISTER",
|
||||
"apikey": "abcdefghijklmnopqrstuvwxyz1234567890",
|
||||
"sandbox" : "0",
|
||||
"premium" : "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
If sandbox is omitted or set to any other value than "1" the production API will be used.
|
||||
If premium is set to "1", you will only be able to update zones using Premium DNS. If it is omitted or set to any other value, you
|
||||
will only be able to update zones using Basic DNS.
|
||||
|
||||
**Important Notes**:
|
||||
* Anyone with access to this `creds.json` file will have *full* access to your RTR account and will be able to transfer or delete your domains
|
||||
|
||||
## Metadata
|
||||
This provider does not recognize any special metadata fields unique to Realtime Register.
|
||||
|
||||
## Usage
|
||||
An example `dnsconfig.js` configuration file
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```javascript
|
||||
var REG_RTR = NewRegistrar("realtimeregister");
|
||||
var DSP_RTR = NewDnsProvider("realtimeregister");
|
||||
|
||||
D("example.com", REG_RTR, DnsProvider(DSP_RTR),
|
||||
A("test", "1.2.3.4")
|
||||
);
|
||||
```
|
||||
{% endcode %}
|
|
@ -258,6 +258,13 @@
|
|||
"domain": "$POWERDNS_DOMAIN",
|
||||
"serverName": "$POWERDNS_SERVERNAME"
|
||||
},
|
||||
"REALTIMEREGISTER": {
|
||||
"TYPE": "REALTIMEREGISTER",
|
||||
"apikey": "$REALTIMEREGISTER_APIKEY",
|
||||
"sandbox" : "$REALTIMEREGISTER_SANDBOX",
|
||||
"domain": "$REALTIMEREGISTER_DOMAIN",
|
||||
"premium": "$REALTIMEREGISTER_PREMIUM"
|
||||
},
|
||||
"ROUTE53": {
|
||||
"KeyId": "$ROUTE53_KEY_ID",
|
||||
"SecretKey": "$ROUTE53_KEY",
|
||||
|
|
|
@ -47,6 +47,7 @@ import (
|
|||
_ "github.com/StackExchange/dnscontrol/v4/providers/packetframe"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/porkbun"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/powerdns"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/realtimeregister"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/route53"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/rwth"
|
||||
_ "github.com/StackExchange/dnscontrol/v4/providers/softlayer"
|
||||
|
|
221
providers/realtimeregister/api.go
Normal file
221
providers/realtimeregister/api.go
Normal file
|
@ -0,0 +1,221 @@
|
|||
package realtimeregister
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type realtimeregisterAPI struct {
|
||||
apikey string
|
||||
endpoint string
|
||||
Zones map[string]*Zone //cache
|
||||
ServiceType string
|
||||
}
|
||||
|
||||
type Zones struct {
|
||||
Entities []Zone `json:"entities"`
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
Nameservers []string `json:"ns"`
|
||||
}
|
||||
|
||||
type Zone struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Service string `json:"service,omitempty"`
|
||||
ID int `json:"id,omitempty"`
|
||||
Records []Record `json:"records"`
|
||||
Dnssec bool `json:"dnssec"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
Priority int `json:"prio,omitempty"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
const (
|
||||
endpoint = "https://api.yoursrs.com/v2"
|
||||
endpointSandbox = "https://api.yoursrs-ote.com/v2"
|
||||
)
|
||||
|
||||
func (api *realtimeregisterAPI) request(method string, url string, body io.Reader) ([]byte, error) {
|
||||
client := &http.Client{}
|
||||
req, _ := http.NewRequest(
|
||||
method,
|
||||
url,
|
||||
body,
|
||||
)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", "ApiKey "+api.apikey)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bodyString, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("realtime Register API error on request to %s: %d, %s", url, resp.StatusCode,
|
||||
string(bodyString))
|
||||
}
|
||||
|
||||
return bodyString, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) getZone(domain string) (*Zone, error) {
|
||||
zones, err := api.getDomainZones(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(zones.Entities) == 0 {
|
||||
return nil, fmt.Errorf("zone %s does not exist", domain)
|
||||
}
|
||||
|
||||
api.Zones[domain] = &zones.Entities[0]
|
||||
|
||||
return &zones.Entities[0], nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) getDomainZones(domain string) (*Zones, error) {
|
||||
|
||||
url := fmt.Sprintf(api.endpoint+"/dns/zones?name=%s&service=%s", domain, api.ServiceType)
|
||||
|
||||
return api.getZones(url)
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) getAllZones() ([]string, error) {
|
||||
url := fmt.Sprintf(api.endpoint+"/dns/zones?service=%s&export=true&fields=id,name", api.ServiceType)
|
||||
|
||||
zones, err := api.getZones(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zoneNames := make([]string, len(zones.Entities))
|
||||
|
||||
for i, zone := range zones.Entities {
|
||||
zoneNames[i] = zone.Name
|
||||
}
|
||||
|
||||
return zoneNames, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) getZones(url string) (*Zones, error) {
|
||||
bodyBytes, err := api.request(
|
||||
"GET",
|
||||
url,
|
||||
nil,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respData := &Zones{}
|
||||
err = json.Unmarshal(bodyBytes, &respData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) createZone(domain string) error {
|
||||
zone := &Zone{
|
||||
Records: []Record{},
|
||||
Name: domain,
|
||||
Service: api.ServiceType,
|
||||
}
|
||||
|
||||
err := api.createOrUpdateZone(zone, api.endpoint+"/dns/zones")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) zoneExists(domain string) (bool, error) {
|
||||
if api.Zones[domain] != nil {
|
||||
return true, nil
|
||||
}
|
||||
zones, err := api.getDomainZones(domain)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(zones.Entities) > 0, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) getDomainNameservers(domainName string) ([]string, error) {
|
||||
respData, err := api.request(
|
||||
"GET",
|
||||
fmt.Sprintf(api.endpoint+"/domains/%s", domainName),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domain := &Domain{}
|
||||
err = json.Unmarshal(respData, &domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domain.Nameservers, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) updateZone(domain string, body *Zone) error {
|
||||
return api.createOrUpdateZone(
|
||||
body,
|
||||
fmt.Sprintf(api.endpoint+"/dns/zones/%d/update", api.Zones[domain].ID),
|
||||
)
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) updateNameservers(domainName string, nameservers []string) error {
|
||||
domain := &Domain{
|
||||
Nameservers: nameservers,
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = api.request(
|
||||
"POST",
|
||||
fmt.Sprintf(api.endpoint+"/domains/%s/update", domainName),
|
||||
bytes.NewReader(bodyBytes),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) createOrUpdateZone(body *Zone, url string) error {
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Ugly hack for MX records with null target
|
||||
requestBody := strings.Replace(string(bodyBytes), "\"prio\":-1", "\"prio\":0", -1)
|
||||
|
||||
_, err = api.request("POST", url, strings.NewReader(requestBody))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
19
providers/realtimeregister/auditrecords.go
Normal file
19
providers/realtimeregister/auditrecords.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package realtimeregister
|
||||
|
||||
import (
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/rejectif"
|
||||
)
|
||||
|
||||
// AuditRecords returns a list of errors corresponding to the records
|
||||
// that aren't supported by this provider. If all records are
|
||||
// supported, an empty list is returned.
|
||||
func AuditRecords(records []*models.RecordConfig) []error {
|
||||
auditor := rejectif.Auditor{}
|
||||
|
||||
auditor.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2024-01-03
|
||||
|
||||
auditor.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2024-01-03
|
||||
|
||||
return auditor.Audit(records)
|
||||
}
|
335
providers/realtimeregister/realtimeregisterProvider.go
Normal file
335
providers/realtimeregister/realtimeregisterProvider.go
Normal file
|
@ -0,0 +1,335 @@
|
|||
package realtimeregister
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
"golang.org/x/exp/slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Realtime Register DNS provider
|
||||
|
||||
Info required in `creds.json`:
|
||||
- apikey
|
||||
- premium: (0 for BASIC or 1 for PREMIUM)
|
||||
|
||||
Additional settings available in `creds.json`:
|
||||
- sandbox (set to 1 to use the sandbox API from realtime register)
|
||||
*/
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.CanAutoDNSSEC: providers.Can(),
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseAlias: providers.Can(),
|
||||
providers.CanUseCAA: providers.Can(),
|
||||
providers.CanUseDHCID: providers.Cannot(),
|
||||
providers.CanUseDS: providers.Cannot("Only for subdomains"),
|
||||
providers.CanUseDSForChildren: providers.Can(),
|
||||
providers.CanUseLOC: providers.Can(),
|
||||
providers.CanUseNAPTR: providers.Can(),
|
||||
providers.CanUsePTR: providers.Cannot(),
|
||||
providers.CanUseSRV: providers.Can(),
|
||||
providers.CanUseSSHFP: providers.Can(),
|
||||
providers.CanUseSOA: providers.Cannot(),
|
||||
providers.CanUseTLSA: providers.Can(),
|
||||
providers.DocCreateDomains: providers.Can(),
|
||||
providers.DocDualHost: providers.Cannot(),
|
||||
providers.DocOfficiallySupported: providers.Cannot(),
|
||||
}
|
||||
|
||||
// init registers the domain service provider with dnscontrol.
|
||||
func init() {
|
||||
fns := providers.DspFuncs{
|
||||
Initializer: newRtrDsp,
|
||||
RecordAuditor: AuditRecords,
|
||||
}
|
||||
providers.RegisterDomainServiceProviderType("REALTIMEREGISTER", fns, features)
|
||||
providers.RegisterRegistrarType("REALTIMEREGISTER", newRtrReg)
|
||||
}
|
||||
|
||||
func newRtr(config map[string]string, metadata json.RawMessage) (*realtimeregisterAPI, error) {
|
||||
apikey := config["apikey"]
|
||||
sandbox := config["sandbox"] == "1"
|
||||
|
||||
if apikey == "" {
|
||||
return nil, fmt.Errorf("realtime register: apikey must be provided")
|
||||
}
|
||||
|
||||
api := &realtimeregisterAPI{
|
||||
apikey: apikey,
|
||||
endpoint: getEndpoint(sandbox),
|
||||
Zones: make(map[string]*Zone),
|
||||
ServiceType: getServiceType(config["premium"] == "1"),
|
||||
}
|
||||
|
||||
return api, nil
|
||||
}
|
||||
|
||||
func newRtrDsp(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
return newRtr(config, metadata)
|
||||
}
|
||||
|
||||
func newRtrReg(config map[string]string) (providers.Registrar, error) {
|
||||
return newRtr(config, nil)
|
||||
}
|
||||
|
||||
// GetNameservers Default name servers should not be included in the update
|
||||
func (api *realtimeregisterAPI) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||
return []*models.Nameserver{}, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
|
||||
response, err := api.getZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records := response.Records
|
||||
recordConfigs := make([]*models.RecordConfig, len(records))
|
||||
for i := range records {
|
||||
recordConfigs[i] = toRecordConfig(domain, &records[i])
|
||||
}
|
||||
|
||||
return recordConfigs, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) {
|
||||
msgs, changes, err := diff2.ByZone(existing, dc, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var corrections []*models.Correction
|
||||
|
||||
if !changes {
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
dnssec := api.Zones[dc.Name].Dnssec
|
||||
|
||||
if api.Zones[dc.Name].Dnssec && dc.AutoDNSSEC == "off" {
|
||||
dnssec = false
|
||||
corrections = append(corrections,
|
||||
&models.Correction{
|
||||
Msg: "Update DNSSEC on -> off",
|
||||
F: func() error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if !api.Zones[dc.Name].Dnssec && dc.AutoDNSSEC == "on" {
|
||||
dnssec = true
|
||||
corrections = append(corrections,
|
||||
&models.Correction{
|
||||
Msg: "Update DNSSEC off -> on",
|
||||
F: func() error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if changes {
|
||||
corrections = append(corrections,
|
||||
&models.Correction{
|
||||
Msg: strings.Join(msgs, "\n"),
|
||||
F: func() error {
|
||||
records := make([]Record, len(dc.Records))
|
||||
for i, r := range dc.Records {
|
||||
records[i] = toRecord(r)
|
||||
}
|
||||
zone := &Zone{Records: records, Dnssec: dnssec}
|
||||
|
||||
err := api.updateZone(dc.Name, zone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) ListZones() ([]string, error) {
|
||||
zones, err := api.getAllZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
nameservers, err := api.getDomainNameservers(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expected := make([]string, len(dc.Nameservers))
|
||||
for i, ns := range dc.Nameservers {
|
||||
expected[i] = removeTrailingDot(ns.Name)
|
||||
}
|
||||
|
||||
sort.Strings(nameservers)
|
||||
sort.Strings(expected)
|
||||
|
||||
if !slices.Equal(nameservers, expected) {
|
||||
return []*models.Correction{
|
||||
{
|
||||
Msg: fmt.Sprintf("Update nameservers %s -> %s",
|
||||
strings.Join(nameservers, ","), strings.Join(expected, ",")),
|
||||
F: func() error { return api.updateNameservers(dc.Name, expected) },
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func toRecordConfig(domain string, record *Record) *models.RecordConfig {
|
||||
|
||||
recordConfig := &models.RecordConfig{
|
||||
Type: record.Type,
|
||||
TTL: uint32(record.TTL),
|
||||
MxPreference: uint16(record.Priority),
|
||||
SrvWeight: uint16(0),
|
||||
SrvPort: uint16(0),
|
||||
Original: record,
|
||||
}
|
||||
|
||||
recordConfig.SetLabelFromFQDN(record.Name, domain)
|
||||
|
||||
switch rtype := record.Type; rtype { // #rtype_variations
|
||||
case "TXT":
|
||||
_ = recordConfig.SetTargetTXT(removeEscapeChars(record.Content))
|
||||
case "NS", "ALIAS", "CNAME":
|
||||
_ = recordConfig.SetTarget(dnsutil.AddOrigin(addTrailingDot(record.Content), domain))
|
||||
case "MX":
|
||||
content := record.Content
|
||||
if content != "." {
|
||||
content = addTrailingDot(content)
|
||||
}
|
||||
_ = recordConfig.SetTarget(dnsutil.AddOrigin(content, domain))
|
||||
case "NAPTR":
|
||||
_ = recordConfig.SetTargetNAPTRString(record.Content)
|
||||
case "SRV":
|
||||
parts := strings.Fields(record.Content)
|
||||
weight, _ := strconv.ParseUint(parts[0], 10, 16)
|
||||
port, _ := strconv.ParseUint(parts[1], 10, 16)
|
||||
content := parts[2]
|
||||
if content != "." {
|
||||
content = addTrailingDot(content)
|
||||
}
|
||||
_ = recordConfig.SetTargetSRV(uint16(record.Priority), uint16(weight), uint16(port), content)
|
||||
case "CAA":
|
||||
_ = recordConfig.SetTargetCAAString(record.Content)
|
||||
case "SSHFP":
|
||||
_ = recordConfig.SetTargetSSHFPString(record.Content)
|
||||
case "TLSA":
|
||||
_ = recordConfig.SetTargetTLSAString(record.Content)
|
||||
case "DS":
|
||||
_ = recordConfig.SetTargetDSString(record.Content)
|
||||
case "LOC":
|
||||
_ = recordConfig.SetTargetLOCString(domain, record.Content)
|
||||
default:
|
||||
_ = recordConfig.SetTarget(record.Content)
|
||||
}
|
||||
return recordConfig
|
||||
}
|
||||
|
||||
func toRecord(recordConfig *models.RecordConfig) Record {
|
||||
record := &Record{
|
||||
Type: recordConfig.Type,
|
||||
Name: recordConfig.NameFQDN,
|
||||
Content: removeTrailingDot(recordConfig.GetTargetField()),
|
||||
TTL: int(recordConfig.TTL),
|
||||
}
|
||||
|
||||
switch rtype := recordConfig.Type; rtype {
|
||||
case "SRV":
|
||||
if record.Content == "" {
|
||||
record.Content = "."
|
||||
}
|
||||
record.Priority = int(recordConfig.SrvPriority)
|
||||
record.Content = fmt.Sprintf("%d %d %s", recordConfig.SrvWeight, recordConfig.SrvPort, record.Content)
|
||||
case "NAPTR", "SSHFP", "TLSA", "CAA":
|
||||
record.Content = recordConfig.GetTargetCombined()
|
||||
case "TXT":
|
||||
record.Content = addEscapeChars(record.Content)
|
||||
case "DS":
|
||||
record.Content = fmt.Sprintf("%d %d %d %s", recordConfig.DsKeyTag, recordConfig.DsAlgorithm,
|
||||
recordConfig.DsDigestType, strings.ToUpper(recordConfig.DsDigest))
|
||||
case "MX":
|
||||
if record.Content == "" {
|
||||
record.Content = "."
|
||||
record.Priority = -1
|
||||
} else {
|
||||
record.Priority = int(recordConfig.MxPreference)
|
||||
}
|
||||
case "LOC":
|
||||
parts := strings.Fields(recordConfig.GetTargetCombined())
|
||||
degrees1, _ := strconv.ParseUint(parts[0], 10, 32)
|
||||
minutes1, _ := strconv.ParseUint(parts[1], 10, 32)
|
||||
degrees2, _ := strconv.ParseUint(parts[4], 10, 32)
|
||||
minutes2, _ := strconv.ParseUint(parts[5], 10, 32)
|
||||
altitude, _ := strconv.ParseFloat(strings.Split(parts[8], "m")[0], 64)
|
||||
size, _ := strconv.ParseFloat(strings.Split(parts[9], "m")[0], 64)
|
||||
hp, _ := strconv.ParseFloat(strings.Split(parts[10], "m")[0], 64)
|
||||
vp, _ := strconv.ParseFloat(strings.Split(parts[11], "m")[0], 64)
|
||||
record.Content = fmt.Sprintf("%d %d %s %s %d %d %s %s %.2fm %.2fm %.2fm %.2fm",
|
||||
degrees1, minutes1, parts[2], parts[3], degrees2, minutes2,
|
||||
parts[6], parts[7], altitude, size, hp, vp,
|
||||
)
|
||||
}
|
||||
|
||||
return *record
|
||||
}
|
||||
|
||||
func (api *realtimeregisterAPI) EnsureZoneExists(domain string) error {
|
||||
exists, err := api.zoneExists(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return api.createZone(domain)
|
||||
}
|
||||
|
||||
func removeTrailingDot(record string) string {
|
||||
return strings.TrimSuffix(record, ".")
|
||||
}
|
||||
|
||||
func addTrailingDot(record string) string {
|
||||
return record + "."
|
||||
}
|
||||
|
||||
func removeEscapeChars(name string) string {
|
||||
return strings.Replace(strings.Replace(name, "\\\"", "\"", -1), "\\\\", "\\", -1)
|
||||
}
|
||||
|
||||
func addEscapeChars(name string) string {
|
||||
return strings.Replace(strings.Replace(name, "\\", "\\\\", -1), "\"", "\\\"", -1)
|
||||
}
|
||||
|
||||
func getEndpoint(sandbox bool) string {
|
||||
if sandbox {
|
||||
return endpointSandbox
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
||||
func getServiceType(premium bool) string {
|
||||
if premium {
|
||||
return "PREMIUM"
|
||||
}
|
||||
return "BASIC"
|
||||
}
|
16
providers/realtimeregister/realtimeregisterProvider_test.go
Normal file
16
providers/realtimeregister/realtimeregisterProvider_test.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package realtimeregister
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoveEscapeChars(t *testing.T) {
|
||||
cleanedString := removeEscapeChars("\\\\\\\"")
|
||||
assert.Equal(t, "\\\"", cleanedString)
|
||||
}
|
||||
|
||||
func TestAddEscapeChars(t *testing.T) {
|
||||
addedString := addEscapeChars("\\\"")
|
||||
assert.Equal(t, "\\\\\\\"", addedString)
|
||||
}
|
Loading…
Reference in a new issue