package inwx import ( "context" "encoding/json" "fmt" "io" "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" ) type Provider struct { domain string owner string ipVersion ipversion.IPVersion ipv6Suffix netip.Prefix username string password string } func New(data json.RawMessage, domain, owner string, ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) ( p *Provider, err error, ) { extraSettings := struct { Username string `json:"username"` Password string `json:"password"` }{} err = json.Unmarshal(data, &extraSettings) if err != nil { return nil, fmt.Errorf("decoding inwx extra settings: %w", err) } err = validateSettings(domain, extraSettings.Username, extraSettings.Password) if err != nil { return nil, fmt.Errorf("validating provider specific settings: %w", err) } return &Provider{ domain: domain, owner: owner, ipVersion: ipVersion, ipv6Suffix: ipv6Suffix, username: extraSettings.Username, password: extraSettings.Password, }, nil } func validateSettings(domain, username, password string) (err error) { err = utils.CheckDomain(domain) if err != nil { return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err) } switch { case username == "": return fmt.Errorf("%w", errors.ErrUsernameNotSet) case password == "": return fmt.Errorf("%w", errors.ErrPasswordNotSet) } return nil } func (p *Provider) String() string { return utils.ToString(p.domain, p.owner, constants.INWX, p.ipVersion) } func (p *Provider) Domain() string { return p.domain } func (p *Provider) Owner() string { return p.owner } func (p *Provider) IPVersion() ipversion.IPVersion { return p.ipVersion } func (p *Provider) IPv6Suffix() netip.Prefix { return p.ipv6Suffix } func (p *Provider) Proxied() bool { return false } func (p *Provider) BuildDomainName() string { return utils.BuildDomainName(p.owner, p.domain) } func (p *Provider) HTML() models.HTMLRow { return models.HTMLRow{ Domain: fmt.Sprintf("%s", p.BuildDomainName(), p.BuildDomainName()), Owner: p.Owner(), Provider: "INWX", IPVersion: p.ipVersion.String(), } } func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) { u := url.URL{ Scheme: "https", User: url.UserPassword(p.username, p.password), Host: "dyndns.inwx.com", Path: "/nic/update", } values := url.Values{} values.Set("hostname", utils.BuildURLQueryHostname(p.owner, p.domain)) if ip.Is4() { values.Set("myip", ip.String()) } else { values.Set("myipv6", ip.String()) } u.RawQuery = values.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) response, err := client.Do(request) if err != nil { return netip.Addr{}, fmt.Errorf("doing http request: %w", err) } defer response.Body.Close() b, err := io.ReadAll(response.Body) if err != nil { return netip.Addr{}, fmt.Errorf("reading response body: %w", err) } s := string(b) if response.StatusCode != http.StatusOK { return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrHTTPStatusNotValid, response.StatusCode, s) } if !strings.HasPrefix(s, "good") && !strings.HasPrefix(s, "nochg") { return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s) } return ip, nil }