lego/providers/dns/hurricane/internal/client.go

106 lines
2.6 KiB
Go

package internal
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
const defaultBaseURL = "https://dyn.dns.he.net/nic/update"
const (
codeGood = "good"
codeNoChg = "nochg"
codeAbuse = "abuse"
codeBadAgent = "badagent"
codeBadAuth = "badauth"
codeNoHost = "nohost"
codeNotFqdn = "notfqdn"
)
// Client the Hurricane Electric client.
type Client struct {
HTTPClient *http.Client
baseURL string
credentials map[string]string
credMu sync.Mutex
}
// NewClient Creates a new Client.
func NewClient(credentials map[string]string) *Client {
return &Client{
HTTPClient: &http.Client{Timeout: 5 * time.Second},
baseURL: defaultBaseURL,
credentials: credentials,
}
}
// UpdateTxtRecord updates a TXT record.
func (c *Client) UpdateTxtRecord(domain string, txt string) error {
hostname := fmt.Sprintf("_acme-challenge.%s", domain)
c.credMu.Lock()
token, ok := c.credentials[domain]
c.credMu.Unlock()
if !ok {
return fmt.Errorf("hurricane: Domain %s not found in credentials, check your credentials map", domain)
}
data := url.Values{}
data.Set("password", token)
data.Set("hostname", hostname)
data.Set("txt", txt)
resp, err := c.HTTPClient.PostForm(c.baseURL, data)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
body := string(bytes.TrimSpace(bodyBytes))
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%d: attempt to change TXT record %s returned %s", resp.StatusCode, hostname, body)
}
return evaluateBody(body, hostname)
}
func evaluateBody(body string, hostname string) error {
words := strings.SplitN(body, " ", 2)
switch words[0] {
case codeGood:
return nil
case codeNoChg:
log.Printf("%s: unchanged content written to TXT record %s", body, hostname)
return nil
case codeAbuse:
return fmt.Errorf("%s: blocked hostname for abuse: %s", body, hostname)
case codeBadAgent:
return fmt.Errorf("%s: user agent not sent or HTTP method not recognized; open an issue on go-acme/lego on Github", body)
case codeBadAuth:
return fmt.Errorf("%s: wrong authentication token provided for TXT record %s", body, hostname)
case codeNoHost:
return fmt.Errorf("%s: the record provided does not exist in this account: %s", body, hostname)
case codeNotFqdn:
return fmt.Errorf("%s: the record provided isn't an FQDN: %s", body, hostname)
default:
// This is basically only server errors.
return fmt.Errorf("attempt to change TXT record %s returned %s", hostname, body)
}
}