lego/providers/dns/joker/internal/dmapi/client.go

204 lines
4.7 KiB
Go
Raw Normal View History

2020-10-08 14:52:50 +00:00
// Package dmapi Client for DMAPI joker.com.
// https://joker.com/faq/category/39/22-dmapi.html
package dmapi
2019-04-28 12:33:50 +00:00
import (
2023-05-05 07:49:38 +00:00
"context"
2020-02-27 18:14:46 +00:00
"errors"
2019-04-28 12:33:50 +00:00
"fmt"
2021-08-25 09:44:11 +00:00
"io"
2019-04-28 12:33:50 +00:00
"net/http"
"net/url"
"strconv"
"strings"
2023-05-05 07:49:38 +00:00
"sync"
"time"
2019-04-28 12:33:50 +00:00
2020-09-02 01:20:01 +00:00
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/log"
2023-05-05 07:49:38 +00:00
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
2019-04-28 12:33:50 +00:00
)
const defaultBaseURL = "https://dmapi.joker.com/request/"
2020-10-08 14:52:50 +00:00
// Response Joker DMAPI Response.
type Response struct {
2019-04-28 12:33:50 +00:00
Headers url.Values
Body string
StatusCode int
StatusText string
AuthSid string
}
2020-10-08 14:52:50 +00:00
type AuthInfo struct {
APIKey string
Username string
Password string
}
2019-04-28 12:33:50 +00:00
2020-10-08 14:52:50 +00:00
// Client a DMAPI Client.
type Client struct {
2023-05-05 07:49:38 +00:00
apiKey string
username string
password string
2019-04-28 12:33:50 +00:00
2023-05-05 07:49:38 +00:00
token *Token
muToken sync.Mutex
2019-04-28 12:33:50 +00:00
2023-05-05 07:49:38 +00:00
Debug bool
BaseURL string
HTTPClient *http.Client
2020-10-08 14:52:50 +00:00
}
2019-04-28 12:33:50 +00:00
2020-10-08 14:52:50 +00:00
// NewClient creates a new DMAPI Client.
2023-05-05 07:49:38 +00:00
func NewClient(authInfo AuthInfo) *Client {
2020-10-08 14:52:50 +00:00
return &Client{
2023-05-05 07:49:38 +00:00
apiKey: authInfo.APIKey,
username: authInfo.Username,
password: authInfo.Password,
2020-10-08 14:52:50 +00:00
BaseURL: defaultBaseURL,
2023-05-05 07:49:38 +00:00
HTTPClient: &http.Client{Timeout: 5 * time.Second},
2019-04-28 12:33:50 +00:00
}
}
2020-10-08 14:52:50 +00:00
// GetZone returns content of DNS zone for domain.
2023-05-05 07:49:38 +00:00
func (c *Client) GetZone(ctx context.Context, domain string) (*Response, error) {
if getSessionID(ctx) == "" {
2020-02-27 18:14:46 +00:00
return nil, errors.New("must be logged in to get zone")
2019-04-28 12:33:50 +00:00
}
2023-05-05 07:49:38 +00:00
return c.postRequest(ctx, "dns-zone-get", url.Values{"domain": {dns01.UnFqdn(domain)}})
2019-04-28 12:33:50 +00:00
}
2020-10-08 14:52:50 +00:00
// PutZone uploads DNS zone to Joker DMAPI.
2023-05-05 07:49:38 +00:00
func (c *Client) PutZone(ctx context.Context, domain, zone string) (*Response, error) {
if getSessionID(ctx) == "" {
2020-02-27 18:14:46 +00:00
return nil, errors.New("must be logged in to put zone")
2019-04-28 12:33:50 +00:00
}
2023-05-05 07:49:38 +00:00
return c.postRequest(ctx, "dns-zone-put", url.Values{"domain": {dns01.UnFqdn(domain)}, "zone": {strings.TrimSpace(zone)}})
2019-04-28 12:33:50 +00:00
}
2020-05-08 17:35:25 +00:00
// postRequest performs actual HTTP request.
2023-05-05 07:49:38 +00:00
func (c *Client) postRequest(ctx context.Context, cmd string, data url.Values) (*Response, error) {
2023-02-09 16:19:58 +00:00
endpoint, err := url.JoinPath(c.BaseURL, cmd)
2020-10-08 14:52:50 +00:00
if err != nil {
return nil, err
}
2019-04-28 12:33:50 +00:00
2023-05-05 07:49:38 +00:00
if getSessionID(ctx) != "" {
data.Set("auth-sid", getSessionID(ctx))
2019-04-28 12:33:50 +00:00
}
2020-10-08 14:52:50 +00:00
if c.Debug {
2023-02-09 16:19:58 +00:00
log.Infof("postRequest:\n\tURL: %q\n\tData: %v", endpoint, data)
2019-04-28 12:33:50 +00:00
}
2023-05-05 07:49:38 +00:00
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(data.Encode()))
2019-04-28 12:33:50 +00:00
if err != nil {
2023-05-05 07:49:38 +00:00
return nil, fmt.Errorf("unable to create request: %w", err)
2019-04-28 12:33:50 +00:00
}
2023-05-05 07:49:38 +00:00
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := c.HTTPClient.Do(req)
2019-04-28 12:33:50 +00:00
if err != nil {
2023-05-05 07:49:38 +00:00
return nil, errutils.NewHTTPDoError(req, err)
2019-04-28 12:33:50 +00:00
}
2023-05-05 07:49:38 +00:00
defer func() { _ = resp.Body.Close() }()
2022-09-02 07:05:52 +00:00
if resp.StatusCode != http.StatusOK {
2023-05-05 07:49:38 +00:00
return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
}
raw, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
2019-04-28 12:33:50 +00:00
}
2023-05-05 07:49:38 +00:00
return parseResponse(string(raw)), nil
2019-04-28 12:33:50 +00:00
}
2020-10-08 14:52:50 +00:00
// parseResponse parses HTTP response body.
func parseResponse(message string) *Response {
r := &Response{Headers: url.Values{}, StatusCode: -1}
2022-08-22 15:05:31 +00:00
lines, body, _ := strings.Cut(message, "\n\n")
2020-10-08 14:52:50 +00:00
2022-08-22 15:05:31 +00:00
for _, line := range strings.Split(lines, "\n") {
2020-10-08 14:52:50 +00:00
if strings.TrimSpace(line) == "" {
continue
}
2022-08-22 15:05:31 +00:00
k, v, _ := strings.Cut(line, ":")
2020-10-08 14:52:50 +00:00
2022-08-22 15:05:31 +00:00
val := strings.TrimSpace(v)
2020-10-08 14:52:50 +00:00
2022-08-22 15:05:31 +00:00
r.Headers.Add(k, val)
2020-10-08 14:52:50 +00:00
2022-08-22 15:05:31 +00:00
switch k {
2020-10-08 14:52:50 +00:00
case "Status-Code":
i, err := strconv.Atoi(val)
if err == nil {
r.StatusCode = i
}
case "Status-Text":
r.StatusText = val
case "Auth-Sid":
r.AuthSid = val
}
}
2022-08-22 15:05:31 +00:00
r.Body = body
2020-10-08 14:52:50 +00:00
return r
}
2020-05-08 17:35:25 +00:00
// Temporary workaround, until it get fixed on API side.
2019-04-28 12:33:50 +00:00
func fixTxtLines(line string) string {
fields := strings.Fields(line)
if len(fields) < 6 || fields[1] != "TXT" {
return line
}
if fields[3][0] == '"' && fields[4] == `"` {
fields[3] = strings.TrimSpace(fields[3]) + `"`
fields = append(fields[:4], fields[5:]...)
}
return strings.Join(fields, " ")
}
2020-10-08 14:52:50 +00:00
// RemoveTxtEntryFromZone clean-ups all TXT records with given name.
func RemoveTxtEntryFromZone(zone, relative string) (string, bool) {
2019-04-28 12:33:50 +00:00
prefix := fmt.Sprintf("%s TXT 0 ", relative)
modified := false
var zoneEntries []string
for _, line := range strings.Split(zone, "\n") {
if strings.HasPrefix(line, prefix) {
modified = true
continue
}
zoneEntries = append(zoneEntries, line)
}
return strings.TrimSpace(strings.Join(zoneEntries, "\n")), modified
}
2020-10-08 14:52:50 +00:00
// AddTxtEntryToZone returns DNS zone with added TXT record.
func AddTxtEntryToZone(zone, relative, value string, ttl int) string {
2019-04-28 12:33:50 +00:00
var zoneEntries []string
for _, line := range strings.Split(zone, "\n") {
zoneEntries = append(zoneEntries, fixTxtLines(line))
}
newZoneEntry := fmt.Sprintf("%s TXT 0 %q %d", relative, value, ttl)
zoneEntries = append(zoneEntries, newZoneEntry)
return strings.TrimSpace(strings.Join(zoneEntries, "\n"))
}