lego/providers/dns/namecheap/client.go
2020-02-27 19:14:45 +01:00

193 lines
4.6 KiB
Go

package namecheap
import (
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// Record describes a DNS record returned by the Namecheap DNS gethosts API.
// Namecheap uses the term "host" to refer to all DNS records that include
// a host field (A, AAAA, CNAME, NS, TXT, URL).
type Record struct {
Type string `xml:",attr"`
Name string `xml:",attr"`
Address string `xml:",attr"`
MXPref string `xml:",attr"`
TTL string `xml:",attr"`
}
// apiError describes an error record in a namecheap API response.
type apiError struct {
Number int `xml:",attr"`
Description string `xml:",innerxml"`
}
type setHostsResponse struct {
XMLName xml.Name `xml:"ApiResponse"`
Status string `xml:"Status,attr"`
Errors []apiError `xml:"Errors>Error"`
Result struct {
IsSuccess string `xml:",attr"`
} `xml:"CommandResponse>DomainDNSSetHostsResult"`
}
type getHostsResponse struct {
XMLName xml.Name `xml:"ApiResponse"`
Status string `xml:"Status,attr"`
Errors []apiError `xml:"Errors>Error"`
Hosts []Record `xml:"CommandResponse>DomainDNSGetHostsResult>host"`
}
// getHosts reads the full list of DNS host records.
// https://www.namecheap.com/support/api/methods/domains-dns/get-hosts.aspx
func (d *DNSProvider) getHosts(sld, tld string) ([]Record, error) {
request, err := d.newRequestGet("namecheap.domains.dns.getHosts",
addParam("SLD", sld),
addParam("TLD", tld),
)
if err != nil {
return nil, err
}
var ghr getHostsResponse
err = d.do(request, &ghr)
if err != nil {
return nil, err
}
if len(ghr.Errors) > 0 {
return nil, fmt.Errorf("%s [%d]", ghr.Errors[0].Description, ghr.Errors[0].Number)
}
return ghr.Hosts, nil
}
// setHosts writes the full list of DNS host records .
// https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
func (d *DNSProvider) setHosts(sld, tld string, hosts []Record) error {
req, err := d.newRequestPost("namecheap.domains.dns.setHosts",
addParam("SLD", sld),
addParam("TLD", tld),
func(values url.Values) {
for i, h := range hosts {
ind := fmt.Sprintf("%d", i+1)
values.Add("HostName"+ind, h.Name)
values.Add("RecordType"+ind, h.Type)
values.Add("Address"+ind, h.Address)
values.Add("MXPref"+ind, h.MXPref)
values.Add("TTL"+ind, h.TTL)
}
},
)
if err != nil {
return err
}
var shr setHostsResponse
err = d.do(req, &shr)
if err != nil {
return err
}
if len(shr.Errors) > 0 {
return fmt.Errorf("%s [%d]", shr.Errors[0].Description, shr.Errors[0].Number)
}
if shr.Result.IsSuccess != "true" {
return errors.New("setHosts failed")
}
return nil
}
func (d *DNSProvider) do(req *http.Request, out interface{}) error {
resp, err := d.config.HTTPClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
var body []byte
body, err = readBody(resp)
if err != nil {
return fmt.Errorf("HTTP error %d [%s]: %w", resp.StatusCode, http.StatusText(resp.StatusCode), err)
}
return fmt.Errorf("HTTP error %d [%s]: %s", resp.StatusCode, http.StatusText(resp.StatusCode), string(body))
}
body, err := readBody(resp)
if err != nil {
return err
}
if err := xml.Unmarshal(body, out); err != nil {
return err
}
return nil
}
func (d *DNSProvider) newRequestGet(cmd string, params ...func(url.Values)) (*http.Request, error) {
query := d.makeQuery(cmd, params...)
reqURL, err := url.Parse(d.config.BaseURL)
if err != nil {
return nil, err
}
reqURL.RawQuery = query.Encode()
return http.NewRequest(http.MethodGet, reqURL.String(), nil)
}
func (d *DNSProvider) newRequestPost(cmd string, params ...func(url.Values)) (*http.Request, error) {
query := d.makeQuery(cmd, params...)
req, err := http.NewRequest(http.MethodPost, d.config.BaseURL, strings.NewReader(query.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return req, nil
}
func (d *DNSProvider) makeQuery(cmd string, params ...func(url.Values)) url.Values {
queryParams := make(url.Values)
queryParams.Set("ApiUser", d.config.APIUser)
queryParams.Set("ApiKey", d.config.APIKey)
queryParams.Set("UserName", d.config.APIUser)
queryParams.Set("Command", cmd)
queryParams.Set("ClientIp", d.config.ClientIP)
for _, param := range params {
param(queryParams)
}
return queryParams
}
func addParam(key, value string) func(url.Values) {
return func(values url.Values) {
values.Set(key, value)
}
}
func readBody(resp *http.Response) ([]byte, error) {
if resp.Body == nil {
return nil, errors.New("response body is nil")
}
defer resp.Body.Close()
rawBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return rawBody, nil
}