mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-01-11 01:47:53 +08:00
e9510da434
* Added basic structure for domain name shop * Finished proof of concept for domainnameshop * Fixed handeling of IDNA for CNAME records * Updated documentation notes * Added docs * Ran linter and vet * Removed proxy config used for debugging * Ran go generate * Fixed issue with TTLs being restricted to a multiple of 60 * Ran tests, vet and linting and fixed flaws * Fixed typo in docs * Improved code based on feedback * Fixed issues with TXT records not working properly * Refactored according to new file layout proposed * Updated documentation matrix * Suggestions and corrections * Corrected according to suggestions Co-authored-by: Simen Bai <git@simenbai.no> Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
223 lines
5.3 KiB
Go
223 lines
5.3 KiB
Go
package domainnameshop
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/net/idna"
|
|
)
|
|
|
|
var rootAPIURI = "https://api.domeneshop.no/v0"
|
|
|
|
func (api *domainNameShopProvider) getDomains(domainName string) ([]domainResponse, error) {
|
|
client := &http.Client{}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, rootAPIURI+"/domains?domain="+domainName, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.SetBasicAuth(api.Token, api.Secret)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var domainResp []domainResponse
|
|
err = json.NewDecoder(resp.Body).Decode(&domainResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if domainName != "" && domainName != domainResp[0].Domain {
|
|
return nil, fmt.Errorf("invalid domain name: %q != %q", domainName, domainResp[0].Domain)
|
|
}
|
|
return domainResp, nil
|
|
}
|
|
|
|
func (api *domainNameShopProvider) getDomainID(domainName string) (string, error) {
|
|
domainResp, err := api.getDomains(domainName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strconv.Itoa(domainResp[0].ID), nil
|
|
}
|
|
|
|
func (api *domainNameShopProvider) getNS(domainName string) ([]string, error) {
|
|
domainResp, err := api.getDomains(domainName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return domainResp[0].Nameservers, nil
|
|
}
|
|
|
|
func (api *domainNameShopProvider) getDNS(domainName string) ([]domainNameShopRecord, error) {
|
|
domainID, err := api.getDomainID(domainName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := &http.Client{}
|
|
req, err := http.NewRequest(http.MethodGet, rootAPIURI+"/domains/"+domainID+"/dns", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.SetBasicAuth(api.Token, api.Secret)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var domainResponse []domainNameShopRecord
|
|
err = json.NewDecoder(resp.Body).Decode(&domainResponse)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Post processing of the data received. Converting to correct types and setting default values.
|
|
for i := range domainResponse {
|
|
// Convert priority from string to Uint, defaulting to 0
|
|
record := &domainResponse[i]
|
|
priority, err := strconv.ParseUint(record.Priority, 10, 16)
|
|
if err != nil {
|
|
record.ActualPriority = 0
|
|
}
|
|
record.ActualPriority = uint16(priority)
|
|
|
|
// Convert port from string to Uint, defaulting to 0
|
|
port, err := strconv.ParseUint(record.Port, 10, 16)
|
|
if err != nil {
|
|
record.ActualPort = 0
|
|
}
|
|
record.ActualPort = uint16(port)
|
|
|
|
// Convert weight from string ti Uint, defaulting to 0
|
|
weight, err := strconv.ParseUint(record.Weight, 10, 16)
|
|
if err != nil {
|
|
record.ActualWeight = 0
|
|
}
|
|
record.ActualWeight = uint16(weight)
|
|
|
|
// Converting the CAA flag from string to correct value
|
|
if record.Type == "CAA" {
|
|
CaaFlag, err := strconv.ParseUint(record.ActualCAAFlag, 10, 8)
|
|
if err != nil {
|
|
record.CAAFlag = 0
|
|
}
|
|
record.CAAFlag = CaaFlag
|
|
}
|
|
|
|
// Transform data field to punycode if CNAME
|
|
if record.Type == "CNAME" {
|
|
punycodeData, err := idna.ToASCII(record.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
record.Data = punycodeData
|
|
if !strings.HasSuffix(record.Data, ".") {
|
|
record.Data += "."
|
|
}
|
|
}
|
|
|
|
// Normalize the TTL.
|
|
record.TTL = uint16(fixTTL(uint32(record.TTL)))
|
|
|
|
// Add domain id
|
|
(&domainResponse[i]).DomainID = domainID
|
|
}
|
|
|
|
ns, err := api.getNS(domainName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Adds NS as records
|
|
for _, nameserver := range ns {
|
|
domainResponse = append(domainResponse, domainNameShopRecord{
|
|
ID: 0,
|
|
Host: "@",
|
|
TTL: 300,
|
|
Type: "NS",
|
|
Data: nameserver + ".",
|
|
DomainID: domainID,
|
|
})
|
|
}
|
|
|
|
return domainResponse, nil
|
|
}
|
|
|
|
func (api *domainNameShopProvider) deleteRecord(domainID string, recordID string) error {
|
|
return api.sendChangeRequest(http.MethodDelete, rootAPIURI+"/domains/"+domainID+"/dns/"+recordID, nil)
|
|
}
|
|
|
|
func (api *domainNameShopProvider) CreateRecord(domainName string, dnsR *domainNameShopRecord) error {
|
|
domainID, err := api.getDomainID(domainName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
payloadBuf := new(bytes.Buffer)
|
|
err = json.NewEncoder(payloadBuf).Encode(&dnsR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return api.sendChangeRequest(http.MethodPost, rootAPIURI+"/domains/"+domainID+"/dns", payloadBuf)
|
|
}
|
|
|
|
func (api *domainNameShopProvider) UpdateRecord(dnsR *domainNameShopRecord) error {
|
|
domainID := dnsR.DomainID
|
|
recordID := strconv.Itoa(dnsR.ID)
|
|
|
|
payloadBuf := new(bytes.Buffer)
|
|
json.NewEncoder(payloadBuf).Encode(&dnsR)
|
|
|
|
return api.sendChangeRequest(http.MethodPut, rootAPIURI+"/domains/"+domainID+"/dns/"+recordID, payloadBuf)
|
|
}
|
|
|
|
func (api *domainNameShopProvider) sendChangeRequest(method string, uri string, payload *bytes.Buffer) error {
|
|
client := &http.Client{}
|
|
|
|
var req *http.Request
|
|
var err error
|
|
if payload != nil {
|
|
req, err = http.NewRequest(method, uri, payload)
|
|
} else {
|
|
req, err = http.NewRequest(method, uri, nil)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req.SetBasicAuth(api.Token, api.Secret)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch resp.StatusCode {
|
|
case 201:
|
|
// Record is deleted
|
|
return nil
|
|
case 204:
|
|
//Update successful
|
|
return nil
|
|
case 400:
|
|
return fmt.Errorf("DNS record failed validation")
|
|
case 403:
|
|
return fmt.Errorf("not authorized")
|
|
case 404:
|
|
return fmt.Errorf("does not exist")
|
|
case 409:
|
|
return fmt.Errorf("collision")
|
|
default:
|
|
return fmt.Errorf("unknown statuscode: %v", resp.StatusCode)
|
|
}
|
|
}
|