Skip to content

Commit

Permalink
fix(dondominio): use endpoint api.dondominio.com
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Jan 28, 2024
1 parent 8888694 commit 7eee3fc
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 67 deletions.
4 changes: 3 additions & 1 deletion docs/dondominio.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
### Compulsory parameters

- `"domain"`
- `"name"` is the name server associated with the domain
- `"name"` is the name of the service/hosting
- `"username"`
- `"password"`

Expand All @@ -31,3 +31,5 @@
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`

## Domain setup

See [dondominio.dev/en/dondns/docs/api/#before-start](https://dondominio.dev/en/dondns/docs/api/#before-start)
66 changes: 66 additions & 0 deletions internal/provider/providers/dondominio/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package dondominio

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/qdm12/ddns-updater/internal/provider/headers"
)

func apiCall(ctx context.Context, client *http.Client,
path string, requestData any) (responseData json.RawMessage, err error) {
u := url.URL{
Scheme: "https",
Host: "api.dondominio.com",
Path: path,
}

body := bytes.NewBuffer(nil)
encoder := json.NewEncoder(body)
err = encoder.Encode(requestData)
if err != nil {
return nil, fmt.Errorf("encoding request data: %w", err)
}

request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), body)
if err != nil {
return nil, fmt.Errorf("creating http request: %w", err)
}
headers.SetUserAgent(request)
headers.SetContentType(request, "application/json")
headers.SetAccept(request, "application/json")

response, err := client.Do(request)
if err != nil {
return nil, err
}

var data struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorCodeMsg string `json:"errorCodeMsg"`
ResponseData json.RawMessage `json:"responseData"`
}
decoder := json.NewDecoder(response.Body)
err = decoder.Decode(&data)
if err != nil {
_ = response.Body.Close()
return nil, fmt.Errorf("decoding response body: %w", err)
}

err = response.Body.Close()
if err != nil {
return nil, fmt.Errorf("closing response body: %w", err)
}

if !data.Success {
_ = response.Body.Close()
return nil, makeError(data.ErrorCode, data.ErrorCodeMsg)
}

return data.ResponseData, nil
}
100 changes: 100 additions & 0 deletions internal/provider/providers/dondominio/errorcodes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package dondominio

import (
"fmt"

"github.com/qdm12/ddns-updater/internal/provider/errors"
)

func makeError(errorCode int, errorMessage string) error {
switch errorCode {
case syntaxError, syntaxErrorParameterFault, invalidObjectOrAction,
notImplementedObjectOrAction, syntaxErrorInvalidParameter, accountDeleted,
accountNotExists, invalidDomainName, tldNotSupported:
return fmt.Errorf("%w: %s (%d)", errors.ErrBadRequest, errorMessage, errorCode)
case notAllowedObjectOrAction, loginRequired, loginInvalid, sessionInvalid,
actionNotAllowed, accountInvalidPass1, accountInvalidPass2, accountInvalidPass3,
domainUpdateNotAllowed:
return fmt.Errorf("%w: %s (%d)", errors.ErrAuth, errorMessage, errorCode)
case accountBlocked1, accountBlocked2, accountBlocked3, accountBlocked4,
accountBlocked5, accountBlocked6, accountFiltered1, accountFiltered2,
accountBanned, domainUpdateBlocked:
return fmt.Errorf("%w: %s (%d)", errors.ErrBannedAbuse, errorMessage, errorCode)
case accountInactive:
return fmt.Errorf("%w: %s (%d)", errors.ErrAccountInactive, errorMessage, errorCode)
case domainCheckError, domainNotFound:
return fmt.Errorf("%w: %s (%d)", errors.ErrDomainNotFound, errorMessage, errorCode)
default:
return fmt.Errorf("%w: %s (%d)", errors.ErrUnknownResponse, errorMessage, errorCode)
}
}

// See section "10.1.1 Error codes" at https://dondominio.dev/en/api/docs/api/#tables
const (
success = 0
undefinedError = 1
syntaxError = 100
syntaxErrorParameterFault = 101
invalidObjectOrAction = 102
notAllowedObjectOrAction = 103
notImplementedObjectOrAction = 104
syntaxErrorInvalidParameter = 105
loginRequired = 200
loginInvalid = 201
sessionInvalid = 210
actionNotAllowed = 300
accountBlocked1 = 1000
accountDeleted = 1001
accountInactive = 1002
accountNotExists = 1003
accountInvalidPass1 = 1004
accountInvalidPass2 = 1005
accountBlocked2 = 1006
accountFiltered1 = 1007
accountInvalidPass3 = 1009
accountBlocked3 = 1010
accountBlocked4 = 1011
accountBlocked5 = 1012
accountBlocked6 = 1013
accountFiltered2 = 1014
accountBanned = 1030
insufficientBalance = 1100
invalidDomainName = 2001
tldNotSupported = 2002
tldInMaintenance = 2003
domainCheckError = 2004
domainTransferNotAllowed = 2005
domainWhoisNotAllowed = 2006
domainWhoisError = 2007
domainNotFound = 2008
domainCreateError = 2009
domainCreateErrorTaken = 2010
domainCreateErrorDomainPremium = 2011
domainTransferError = 2012
domainRenewError = 2100
domainRenewNotAllowed = 2101
domainRenewBlocked = 2102
domainUpdateError = 2200
domainUpdateNotAllowed = 2201
domainUpdateBlocked = 2202
invalidOperationDueToTheOwnerContactDataVerificationStatus = 2210
contactNotExists = 3001
contactDataError = 3002
invalidOperationDueToTheContactDataVerification = 3003
userNotExists = 3500
userCreateError = 3501
userUpdateError = 3502
serviceNotFound = 4001
serviceEntityNotFound = 4002
maximumAmountOfEntitiesError = 4003
failureToCreateTheEntity = 4004
failureToUpdateTheEntity = 4005
failureToDeleteTheEntity = 4006
failureToCreateTheService = 4007
failureToUpgradeTheService = 4008
failureToRenewTheService = 4009
failureToMotifyTheParkingSystem = 4010
sslError = 5000
sslNotFound = 5001
webConstructorError = 10001
)
54 changes: 54 additions & 0 deletions internal/provider/providers/dondominio/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dondominio

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/qdm12/ddns-updater/internal/provider/constants"
)

// See https://dondominio.dev/en/api/docs/api/#dns-zone-list-service-dnslist
func (p *Provider) list(ctx context.Context, client *http.Client) (aIDs, aaaaIDs []string, err error) {
requestData := struct {
APIUser string `json:"apiuser"`
APIPasswd string `json:"apipasswd"`
ServiceName string `json:"serviceName"`
}{
APIUser: p.username,
APIPasswd: p.password,
ServiceName: p.name,
}

data, err := apiCall(ctx, client, "/service/dnslist", requestData)
if err != nil {
return nil, nil, err
}

var responseData struct {
DNS []struct {
EntityID string `json:"entityID"`
Name string `json:"name"`
Type string `json:"type"`
} `json:"dns"`
}
err = json.Unmarshal(data, &responseData)
if err != nil {
return nil, nil, fmt.Errorf("json decoding response data: %w", err)
}

for _, record := range responseData.DNS {
if record.Name != p.BuildDomainName() {
continue
}
switch record.Type {
case constants.A:
aIDs = append(aIDs, record.EntityID)
case constants.AAAA:
aaaaIDs = append(aaaaIDs, record.EntityID)
}
}

return aIDs, aaaaIDs, nil
}
80 changes: 14 additions & 66 deletions internal/provider/providers/dondominio/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ import (
"fmt"
"net/http"
"net/netip"
"net/url"
"strings"

"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/provider/constants"
"github.com/qdm12/ddns-updater/internal/provider/errors"
"github.com/qdm12/ddns-updater/internal/provider/headers"
"github.com/qdm12/ddns-updater/internal/provider/utils"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
)
Expand Down Expand Up @@ -103,74 +100,25 @@ func (p *Provider) HTML() models.HTMLRow {
}

func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
u := url.URL{
Scheme: "https",
Host: "simple-api.dondominio.net",
}
values := url.Values{}
values.Set("apiuser", p.username)
values.Set("apipasswd", p.password)
values.Set("domain", p.domain)
values.Set("name", p.name)
isIPv4 := ip.Is4()
if isIPv4 {
values.Set("ipv4", ip.String())
} else {
values.Set("ipv6", ip.String())
}
encodedValues := values.Encode()
buffer := strings.NewReader(encodedValues)

request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
if err != nil {
return netip.Addr{}, fmt.Errorf("creating http request: %w", err)
}
headers.SetUserAgent(request)
headers.SetContentType(request, "application/x-www-form-urlencoded")
headers.SetAccept(request, "application/json")

response, err := client.Do(request)
aIDs, aaaaIDs, err := p.list(ctx, client)
if err != nil {
return netip.Addr{}, err
return netip.Addr{}, fmt.Errorf("listing records: %w", err)
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
errors.ErrHTTPStatusNotValid, response.StatusCode, utils.BodyToSingleLine(response.Body))
recordType := constants.A
ids := aIDs
if ip.Is6() {
recordType = constants.AAAA
ids = aaaaIDs
}

decoder := json.NewDecoder(response.Body)
var responseData struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorCodeMessage string `json:"errorCodeMsg"`
ResponseData struct {
GlueRecords []struct {
IPv4 string `json:"ipv4"`
IPv6 string `json:"ipv6"`
} `json:"gluerecords"`
} `json:"responseData"`
}
err = decoder.Decode(&responseData)
if err != nil {
return netip.Addr{}, fmt.Errorf("json decoding response body: %w", err)
for _, id := range ids {
err = p.update(ctx, client, id, ip)
if err != nil {
return netip.Addr{}, fmt.Errorf("updating %s record for %s: %w",
recordType, p.BuildDomainName(), err)
}
}

if !responseData.Success {
return netip.Addr{}, fmt.Errorf("%w: %s (error code %d)",
errors.ErrUnsuccessful, responseData.ErrorCodeMessage, responseData.ErrorCode)
}
ipString := responseData.ResponseData.GlueRecords[0].IPv4
if !isIPv4 {
ipString = responseData.ResponseData.GlueRecords[0].IPv6
}
newIP, err = netip.ParseAddr(ipString)
if err != nil {
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
} else if ip.Compare(newIP) != 0 {
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
errors.ErrIPReceivedMismatch, ip, newIP)
}
return newIP, nil
return ip, nil
}
33 changes: 33 additions & 0 deletions internal/provider/providers/dondominio/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dondominio

import (
"context"
"fmt"
"net/http"
"net/netip"
)

// See https://dondominio.dev/en/api/docs/api/#dns-zone-update-service-dnsupdate
func (p *Provider) update(ctx context.Context, client *http.Client, entityID string,
ip netip.Addr) (err error) {
requestData := struct {
APIUser string `json:"apiuser"`
APIPasswd string `json:"apipasswd"`
ServiceName string `json:"serviceName"`
EntityID string `json:"entityID"`
Value string `json:"value"`
}{
APIUser: p.username,
APIPasswd: p.password,
ServiceName: p.name,
EntityID: entityID,
Value: ip.String(),
}

_, err = apiCall(ctx, client, "/service/dnsupdate", requestData)
if err != nil {
return fmt.Errorf("for entity id %s: %w", entityID, err)
}

return nil
}

0 comments on commit 7eee3fc

Please sign in to comment.