dnscontrol/providers/hostingde/types.go
Julius Rickert c883c1ac68
New provider and new registrar: hosting.de (#1041)
* Add http.net provider

* Rename httpnetProvider

* Add SSHFP capability

* Add paging for records

* Sort documentation notes alphabetically

* Add custom base URL

* Extend documentation for custom base URL

* - renamed to hosting.de
- Fix EnsureDomainExists
- GetNameservers read from NS Records

* Replaced http.net with hosting.de
Contributor Support from hosting.de

* baseURL for hosting.de in documentation
replaced %v with %w for errors
special handling for txt records using .TxtStrings

* removed last references to rc.Target
fixed Trim of last dot

* Re-engineer TXT records for simplicity and better compliance (#1063)

Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
Co-authored-by: Oliver Dick <o.dick@hosting.de>
Co-authored-by: Oliver Dick <31733320+membero@users.noreply.github.com>
2021-03-08 19:25:55 -05:00

183 lines
4.8 KiB
Go

package hostingde
import (
"encoding/json"
"fmt"
"log"
"net"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/pkg/errors"
)
var (
errZoneNotFound = errors.Errorf("zone not found")
)
type request struct {
AuthToken string `json:"authToken"`
OwnerAccountID string `json:"ownerAccountId,omitempty"`
Filter filter `json:"filter,omitempty"`
Limit uint `json:"limit,omitempty"`
Page uint `json:"page,omitempty"`
// Update Zone
ZoneConfig *zoneConfig `json:"zoneConfig"`
RecordsToAdd []*record `json:"recordsToAdd"`
RecordsToModify []*record `json:"recordsToModify"`
RecordsToDelete []*record `json:"recordsToDelete"`
// Create Zone
Records []*record `json:"records"`
// Domain
Domain *domainConfig `json:"domain"`
}
type filter struct {
Field string `json:"field"`
Value string `json:"value"`
Relation string `json:"relation,omitempty"`
}
type nameserver struct {
Name string `json:"name"`
IPs []net.IP `json:"ips"`
}
type domainConfig struct {
Name string `json:"name"`
Contacts json.RawMessage `json:"contacts"`
Nameservers []nameserver `json:"nameservers"`
TransferLockEnabled bool `json:"transferLockEnabled"`
}
type zoneConfig struct {
ID string `json:"id"`
DNSSECMode string `json:"dnsSecMode"`
EmailAddress string `json:"emailAddress,omitempty"`
MasterIP string `json:"masterIp"`
Name string `json:"name"` // Not required per docs, but required IRL
NameUnicode string `json:"nameUnicode"`
// SOAValues struct {
// Refresh uint32 `json:"refresh"`
// Retry uint32 `json:"retry"`
// Expire uint32 `json:"expire"`
// TTL uint32 `json:"ttl"`
// NegativeTTL uint32 `json:"negativeTtl"`
// } `json:"soaValues,omitempty"`
Type string `json:"type"`
ZoneTransferWhitelist []string `json:"zoneTransferWhitelist"`
}
type record struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Content string `json:"content"`
TTL uint32 `json:"ttl"`
Priority uint16 `json:"priority"`
}
type response struct {
Errors []apiError `json:"errors"`
Response *responseData `json:"response"`
Status string `json:"status"`
}
type apiError struct {
Code int `json:"code"`
ContextObject string `json:"contextObject"`
ContextPath string `json:"contextPath"`
Text string `json:"text"`
Value string `json:"value"`
}
type responseData struct {
Data json.RawMessage `json:"data"`
Type string `json:"type"`
Limit uint `json:"limit"`
Page uint `json:"page"`
TotalPages uint `json:"totalPages"`
}
func (r *record) nativeToRecord(domain string) *models.RecordConfig {
// normalize cname,mx,ns records with dots to be consistent with our config format.
if r.Type == "ALIAS" || r.Type == "CNAME" || r.Type == "MX" || r.Type == "NS" || r.Type == "SRV" {
if r.Content != "." {
r.Content = r.Content + "."
}
}
rc := &models.RecordConfig{
Type: "",
TTL: r.TTL,
MxPreference: r.Priority,
SrvPriority: r.Priority,
Original: r,
}
rc.SetLabelFromFQDN(r.Name, domain)
var err error
switch r.Type {
case "ALIAS":
rc.Type = r.Type
rc.SetTarget(r.Content)
case "NULLMX":
err = rc.PopulateFromString("MX", "0 .", domain)
case "MX":
err = rc.SetTargetMX(uint16(r.Priority), r.Content)
case "SRV":
err = rc.SetTargetSRVPriorityString(uint16(r.Priority), r.Content)
default:
if err := rc.PopulateFromString(r.Type, r.Content, domain); err != nil {
panic(err)
}
}
if err != nil {
panic(err)
}
return rc
}
func recordToNative(rc *models.RecordConfig) *record {
record := &record{
Name: rc.NameFQDN,
Type: rc.Type,
Content: strings.TrimSuffix(rc.GetTargetCombined(), "."),
TTL: rc.TTL,
}
switch rc.Type { // #rtype_variations
case "A", "AAAA", "ALIAS", "CAA", "CNAME", "DNSKEY", "DS", "NS", "NSEC", "NSEC3", "NSEC3PARAM", "PTR", "RRSIG", "SSHFP", "TSLA":
// Nothing special.
case "TXT":
if cap(rc.TxtStrings) == 1 {
record.Content = "\"" + rc.TxtStrings[0] + "\""
} else if cap(rc.TxtStrings) > 1 {
record.Content = ""
for _, str := range rc.TxtStrings {
record.Content = record.Content + " \"" + str + "\""
}
record.Content = record.Content[1:len(record.Content)]
}
case "MX":
record.Priority = rc.MxPreference
record.Content = strings.TrimSuffix(rc.GetTargetField(), ".")
if record.Content == "" {
record.Type = "NULLMX"
record.Priority = 10
}
case "SRV":
record.Priority = rc.SrvPriority
record.Content = fmt.Sprintf("%d %d %s", rc.SrvWeight, rc.SrvPort, strings.TrimSuffix(rc.GetTargetField(), "."))
default:
log.Printf("hosting.de rtype %v unimplemented", rc.Type)
}
return record
}