SPDyn support, fixes #182 (#179)

This commit is contained in:
Quentin McGaw
2021-03-23 11:18:02 -04:00
committed by GitHub
parent 869cf52c7d
commit e3472476e2
5 changed files with 204 additions and 0 deletions

View File

@@ -41,6 +41,7 @@
- OpenDNS
- OVH
- Selfhost.de
- [Spdyn](spdyn.de)
- Strato.de
- **Want more?** [Create an issue for it](https://github.com/qdm12/ddns-updater/issues/new/choose)!
- Web User interface
@@ -152,6 +153,7 @@ Check the documentation for your DNS provider:
- [OpenDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/opendns.md)
- [OVH](https://github.com/qdm12/ddns-updater/blob/master/docs/ovh.md)
- [Selfhost.de](https://github.com/qdm12/ddns-updater/blob/master/docs/selfhost.de.md)
- [Spdyn](https://github.com/qdm12/ddns-updater/blob/master/docs/spdyn.md)
- [Strato.de](https://github.com/qdm12/ddns-updater/blob/master/docs/strato.md)
Note that:

39
docs/spdyn.md Normal file
View File

@@ -0,0 +1,39 @@
# Spdyn.de
## Configuration
### Example
```json
{
"settings": [
{
"provider": "spdyn",
"domain": "domain.com",
"host": "@",
"user": "user",
"password": "password",
"token": "token",
"ip_version": "ipv4"
}
]
}
```
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
#### Using user and password
- `"user"` is the name of a user who can update this host
- `"password"` is the password of a user who can update this host
#### Using update tokens
- `"token"` is your update token
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`

View File

@@ -28,6 +28,7 @@ const (
OPENDNS models.Provider = "opendns"
OVH models.Provider = "ovh"
SELFHOSTDE models.Provider = "selfhost.de"
SPDYN models.Provider = "spdyn"
STRATO models.Provider = "strato"
)
@@ -57,6 +58,7 @@ func ProviderChoices() []models.Provider {
OVH,
OPENDNS,
SELFHOSTDE,
SPDYN,
STRATO,
}
}

View File

@@ -156,6 +156,8 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
settingsConstructor = settings.NewDyn
case constants.SELFHOSTDE:
settingsConstructor = settings.NewSelfhostde
case constants.SPDYN:
settingsConstructor = settings.NewSpdyn
case constants.STRATO:
settingsConstructor = settings.NewStrato
case constants.OVH:

159
internal/settings/spdyn.go Normal file
View File

@@ -0,0 +1,159 @@
package settings
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/regex"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
)
type spdyn struct {
domain string
host string
ipVersion ipversion.IPVersion
user string
password string
token string
// useProviderIP bool
}
func NewSpdyn(data json.RawMessage, domain, host string, ipVersion ipversion.IPVersion,
_ regex.Matcher) (s Settings, err error) {
extraSettings := struct {
User string `json:"user"`
Password string `json:"password"`
Token string `json:"token"`
// UseProviderIP bool `json:"provider_ip"`
}{}
if err := json.Unmarshal(data, &extraSettings); err != nil {
return nil, err
}
spdyn := &spdyn{
domain: domain,
host: host,
ipVersion: ipVersion,
user: extraSettings.User,
password: extraSettings.Password,
token: extraSettings.Token,
// useProviderIP: extraSettings.UseProviderIP,
}
if err := spdyn.isValid(); err != nil {
return nil, err
}
return spdyn, nil
}
func (s *spdyn) isValid() error {
if len(s.token) > 0 {
return nil
}
switch {
case len(s.user) == 0:
return ErrEmptyUsername
case len(s.password) == 0:
return ErrEmptyPassword
case s.host == "*":
return ErrHostWildcard
}
return nil
}
func (s *spdyn) String() string {
return fmt.Sprintf("[domain: %s | host: %s | provider: Spdyn]", s.domain, s.host)
}
func (s *spdyn) Domain() string {
return s.domain
}
func (s *spdyn) Host() string {
return s.host
}
func (s *spdyn) IPVersion() ipversion.IPVersion {
return s.ipVersion
}
func (s *spdyn) Proxied() bool {
return false
}
func (s *spdyn) BuildDomainName() string {
return buildDomainName(s.host, s.domain)
}
func (s *spdyn) 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://spdyn.com/\">Spdyn DNS</a>",
IPVersion: models.HTML(s.ipVersion),
}
}
func (s *spdyn) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
// see https://wiki.securepoint.de/SPDyn/Variablen
u := url.URL{
Scheme: "https",
Host: "update.spdyn.de",
Path: "/nic/update",
}
values := url.Values{}
values.Set("hostname", s.BuildDomainName())
values.Set("myip", ip.String())
if len(s.token) > 0 {
values.Set("user", s.BuildDomainName())
values.Set("passport", s.token)
} else {
values.Set("user", s.user)
values.Set("passport", s.password)
}
u.RawQuery = values.Encode()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
setUserAgent(request)
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
b, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
}
bodyString := string(b)
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%w: %d: %s",
ErrBadHTTPStatus, response.StatusCode, bodyDataToSingleLine(bodyString))
}
switch bodyString {
case abuse, "numhost":
return nil, ErrAbuse
case badauth, "!yours":
return nil, ErrAuth
case "good":
return ip, nil
case notfqdn:
return nil, fmt.Errorf("%w: not fqdn", ErrBadRequest)
case "nochg":
return ip, nil
case "nohost", "fatal":
return nil, ErrHostnameNotExists
default:
return nil, fmt.Errorf("%w: %s", ErrUnknownResponse, s)
}
}