Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strato #132

Merged
merged 3 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- Namecheap
- NoIP
- Selfhost.de
- Strato.de
- **Want more?** [Create an issue for it](https://github.com/qdm12/ddns-updater/issues/new/choose)!
- Web User interface

Expand Down Expand Up @@ -136,6 +137,7 @@ Check the documentation for your DNS provider:
- [Namecheap](docs/namecheap.md)
- [NoIP](docs/noip.md)
- [Selfhost.de](docs/selfhost.de.md)
- [Strato.de](docs/strato.md)

Note that:

Expand Down
35 changes: 35 additions & 0 deletions docs/strato.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Strato

## Configuration

### Example

```json
{
"settings": [
{
"provider": "strato",
"domain": "domain.com",
"host": "@",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
}
]
}
```

### Compulsory parameters

- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"password"` is your dyndns password

### Optional parameters

- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.

## Domain setup

See [their article](https://www.strato.com/faq/en_us/domain/this-is-how-easy-it-is-to-set-up-dyndns-for-your-domains/)
2 changes: 2 additions & 0 deletions internal/constants/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
NAMECHEAP models.Provider = "namecheap"
NOIP models.Provider = "noip"
SELFHOSTDE models.Provider = "selfhost.de"
STRATO models.Provider = "strato"
)

func ProviderChoices() []models.Provider {
Expand All @@ -40,5 +41,6 @@ func ProviderChoices() []models.Provider {
NAMECHEAP,
NOIP,
SELFHOSTDE,
STRATO,
}
}
2 changes: 2 additions & 0 deletions internal/params/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
settingsConstructor = settings.NewDyn
case constants.SELFHOSTDE:
settingsConstructor = settings.NewSelfhostde
case constants.STRATO:
settingsConstructor = settings.NewStrato
default:
return nil, warnings, fmt.Errorf("provider %q is not supported", provider)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/settings/dyn.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (d *dyn) HTML() models.HTMLRow {
}
}

func (d *dyn) Update(ctx context.Context, client network.Client, ip net.IP) (newIP net.IP, err error) {
func (d *dyn) Update(ctx context.Context, client network.Client, ip net.IP) (newIP net.IP, err error) { //nolint:dupl
u := url.URL{
Scheme: "https",
User: url.UserPassword(d.username, d.password),
Expand Down
135 changes: 135 additions & 0 deletions internal/settings/strato.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package settings

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

"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/regex"
"github.com/qdm12/golibs/network"
)

//nolint:maligned
type strato struct {
domain string
host string
ipVersion models.IPVersion
dnsLookup bool
password string
useProviderIP bool
}

func NewStrato(data json.RawMessage, domain, host string, ipVersion models.IPVersion,
noDNSLookup bool, matcher regex.Matcher) (s Settings, err error) {
extraSettings := struct {
Password string `json:"password"`
UseProviderIP bool `json:"provider_ip"`
}{}
if err := json.Unmarshal(data, &extraSettings); err != nil {
return nil, err
}
ss := &strato{
domain: domain,
host: host,
ipVersion: ipVersion,
dnsLookup: !noDNSLookup,
password: extraSettings.Password,
useProviderIP: extraSettings.UseProviderIP,
}
if err := ss.isValid(); err != nil {
return nil, err
}
return ss, nil
}

func (s *strato) isValid() error {
switch {
case len(s.password) == 0:
return fmt.Errorf("password cannot be empty")
case s.host == "*":
return fmt.Errorf(`host cannot be "*"`)
}
return nil
}

func (s *strato) String() string {
return fmt.Sprintf("[domain: %s | host: %s | provider: Strato]", s.domain, s.host)
}

func (s *strato) Domain() string {
return s.domain
}

func (s *strato) Host() string {
return s.host
}

func (s *strato) IPVersion() models.IPVersion {
return s.ipVersion
}

func (s *strato) DNSLookup() bool {
return s.dnsLookup
}

func (s *strato) BuildDomainName() string {
return buildDomainName(s.host, s.domain)
}

func (s *strato) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", s.BuildDomainName(), s.BuildDomainName())),
Host: models.HTML(s.Host()),
Provider: "<a href=\"https://strato.com/\">Strato DNS</a>",
IPVersion: models.HTML(s.ipVersion),
}
}

func (s *strato) Update(ctx context.Context, client network.Client, ip net.IP) (newIP net.IP, err error) { //nolint:dupl
u := url.URL{
Scheme: "https",
User: url.UserPassword(s.domain, s.password),
Host: "dyndns.strato.com",
Path: "/nic/update",
}
values := url.Values{}
switch s.host {
case "@":
values.Set("hostname", s.domain)
default:
values.Set("hostname", fmt.Sprintf("%s.%s", s.host, s.domain))
}
if !s.useProviderIP {
values.Set("myip", ip.String())
}
u.RawQuery = values.Encode()
r, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
r.Header.Set("User-Agent", "DDNS-Updater quentin.mcgaw@gmail.com")
r = r.WithContext(ctx)
content, status, err := client.Do(r)
if err != nil {
return nil, err
}
if status != http.StatusOK {
return nil, fmt.Errorf(http.StatusText(status))
}
str := string(content)
switch {
case strings.HasPrefix(str, notfqdn):
return nil, fmt.Errorf("fully qualified domain name is not valid")
case strings.HasPrefix(str, "badrequest"):
return nil, fmt.Errorf("bad request")
case strings.HasPrefix(str, "good"):
return ip, nil
default:
return nil, fmt.Errorf("unknown response: %s", s)
}
}