diff --git a/docs/dondominio.md b/docs/dondominio.md index 2525358cf..3082a1479 100644 --- a/docs/dondominio.md +++ b/docs/dondominio.md @@ -13,7 +13,7 @@ "host": "@", "name": "something", "username": "username", - "password": "password", + "key": "key", "ip_version": "ipv4" } ] diff --git a/internal/provider/providers/dondominio/api.go b/internal/provider/providers/dondominio/api.go deleted file mode 100644 index 55500e517..000000000 --- a/internal/provider/providers/dondominio/api.go +++ /dev/null @@ -1,66 +0,0 @@ -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: "simple-api.dondominio.net", - 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 -} diff --git a/internal/provider/providers/dondominio/create.go b/internal/provider/providers/dondominio/create.go deleted file mode 100644 index f789789cf..000000000 --- a/internal/provider/providers/dondominio/create.go +++ /dev/null @@ -1,39 +0,0 @@ -package dondominio - -import ( - "context" - "net/http" - "net/netip" - - "github.com/qdm12/ddns-updater/internal/provider/constants" -) - -// See https://dondominio.dev/en/api/docs/api/#dns-zone-create-service-dnscreate -func (p *Provider) create(ctx context.Context, client *http.Client, ip netip.Addr) (err error) { - recordType := constants.A - if ip.Is6() { - recordType = constants.AAAA - } - requestData := struct { - APIUser string `json:"apiuser"` - APIPasswd string `json:"apipasswd"` - ServiceName string `json:"serviceName"` - Name string `json:"name"` // Name for the DNS zone - Type string `json:"type"` - Value string `json:"value"` - }{ - APIUser: p.username, - APIPasswd: p.password, - ServiceName: p.name, - Name: p.BuildDomainName(), - Type: recordType, - Value: ip.String(), - } - - _, err = apiCall(ctx, client, "/service/dnscreate", requestData) - if err != nil { - return err - } - - return nil -} diff --git a/internal/provider/providers/dondominio/errorcodes.go b/internal/provider/providers/dondominio/errorcodes.go deleted file mode 100644 index 35ac14ed5..000000000 --- a/internal/provider/providers/dondominio/errorcodes.go +++ /dev/null @@ -1,100 +0,0 @@ -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 -) diff --git a/internal/provider/providers/dondominio/list.go b/internal/provider/providers/dondominio/list.go deleted file mode 100644 index 80bae8591..000000000 --- a/internal/provider/providers/dondominio/list.go +++ /dev/null @@ -1,54 +0,0 @@ -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 -} diff --git a/internal/provider/providers/dondominio/provider.go b/internal/provider/providers/dondominio/provider.go index cbdc0610d..aad42c7fa 100644 --- a/internal/provider/providers/dondominio/provider.go +++ b/internal/provider/providers/dondominio/provider.go @@ -6,10 +6,13 @@ 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" ) @@ -19,7 +22,7 @@ type Provider struct { host string ipVersion ipversion.IPVersion username string - password string + key string name string } @@ -27,7 +30,8 @@ func New(data json.RawMessage, domain, host string, ipVersion ipversion.IPVersion) (p *Provider, err error) { extraSettings := struct { Username string `json:"username"` - Password string `json:"password"` + Password string `json:"password"` // retro-compatibility + Key string `json:"key"` Name string `json:"name"` }{} err = json.Unmarshal(data, &extraSettings) @@ -37,12 +41,16 @@ func New(data json.RawMessage, domain, host string, if host == "" { host = "@" // default } + if extraSettings.Password != "" { // retro-compatibility + extraSettings.Key = extraSettings.Password + } + p = &Provider{ domain: domain, host: host, ipVersion: ipVersion, username: extraSettings.Username, - password: extraSettings.Password, + key: extraSettings.Key, name: extraSettings.Name, } err = p.isValid() @@ -56,8 +64,8 @@ func (p *Provider) isValid() error { switch { case p.username == "": return fmt.Errorf("%w", errors.ErrUsernameNotSet) - case p.password == "": - return fmt.Errorf("%w", errors.ErrPasswordNotSet) + case p.key == "": + return fmt.Errorf("%w", errors.ErrKeyNotSet) case p.name == "": return fmt.Errorf("%w", errors.ErrNameNotSet) } @@ -98,33 +106,51 @@ func (p *Provider) HTML() models.HTMLRow { } func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) { - aIDs, aaaaIDs, err := p.list(ctx, client) + u := url.URL{ + Scheme: "https", + Host: "dondns.dondominio.com", + Path: "/json/", + RawQuery: url.Values{ + "user": {p.username}, + "apikey": {p.key}, + "host": {p.BuildDomainName()}, + "ip": {ip.String()}, + "lang": {"en"}, + }.Encode(), + } + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return netip.Addr{}, fmt.Errorf("creating http request: %w", err) + } + headers.SetUserAgent(request) + headers.SetAccept(request, "application/json") + + response, err := client.Do(request) if err != nil { - return netip.Addr{}, fmt.Errorf("listing records: %w", err) + return netip.Addr{}, err } - recordType := constants.A - ids := aIDs - if ip.Is6() { - recordType = constants.AAAA - ids = aaaaIDs + var data struct { + Success bool `json:"success"` + Messages []string `json:"messages"` + } + decoder := json.NewDecoder(response.Body) + err = decoder.Decode(&data) + if err != nil { + _ = response.Body.Close() + return netip.Addr{}, fmt.Errorf("decoding response body: %w", err) } - if len(ids) == 0 { - err = p.create(ctx, client, ip) - if err != nil { - return netip.Addr{}, fmt.Errorf("creating %s record for %s: %w", - recordType, p.BuildDomainName(), err) - } - return ip, nil + err = response.Body.Close() + if err != nil { + return netip.Addr{}, fmt.Errorf("closing 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 !data.Success { + _ = response.Body.Close() + return netip.Addr{}, fmt.Errorf("%w: %s", + errors.ErrUnsuccessful, strings.Join(data.Messages, ", ")) } return ip, nil diff --git a/internal/provider/providers/dondominio/update.go b/internal/provider/providers/dondominio/update.go deleted file mode 100644 index 0ee225e95..000000000 --- a/internal/provider/providers/dondominio/update.go +++ /dev/null @@ -1,33 +0,0 @@ -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 -}