From 7571c0bd314855a0bdb61a7bbfd7fd9921826070 Mon Sep 17 00:00:00 2001 From: Joel Strasser Date: Thu, 19 Jun 2025 13:33:38 +0200 Subject: [PATCH] Add DNS provider for DynDnsFree.de (#2540) Co-authored-by: Fernandez Ludovic --- README.md | 56 +++---- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_dyndnsfree.md | 68 +++++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/dyndnsfree/dyndnsfree.go | 118 +++++++++++++++ providers/dns/dyndnsfree/dyndnsfree.toml | 23 +++ providers/dns/dyndnsfree/dyndnsfree_test.go | 143 ++++++++++++++++++ providers/dns/dyndnsfree/internal/client.go | 78 ++++++++++ .../dns/dyndnsfree/internal/client_test.go | 58 +++++++ providers/dns/zz_gen_dns_providers.go | 3 + 10 files changed, 541 insertions(+), 29 deletions(-) create mode 100644 docs/content/dns/zz_gen_dyndnsfree.md create mode 100644 providers/dns/dyndnsfree/dyndnsfree.go create mode 100644 providers/dns/dyndnsfree/dyndnsfree.toml create mode 100644 providers/dns/dyndnsfree/dyndnsfree_test.go create mode 100644 providers/dns/dyndnsfree/internal/client.go create mode 100644 providers/dns/dyndnsfree/internal/client_test.go diff --git a/README.md b/README.md index c4d4fd9c2..90e60436a 100644 --- a/README.md +++ b/README.md @@ -108,145 +108,145 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Duck DNS Dyn + DynDnsFree.de Dynu EasyDNS Efficient IP - Epik + Epik Exoscale External program F5 XC - freemyip.com + freemyip.com G-Core Gandi Gandi Live DNS (v5) - Glesys + Glesys Go Daddy Google Cloud Google Domains - Hetzner + Hetzner Hosting.de Hosttech HTTP request - http.net + http.net Huawei Cloud Hurricane Electric DNS HyperOne - IBM Cloud (SoftLayer) + IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox Infomaniak - Internet Initiative Japan + Internet Initiative Japan Internet.bs INWX Ionos - IPv64 + IPv64 iwantmyname Joker Joohoi's ACME-DNS - Liara + Liara Lima-City Linode (v4) Liquid Web - Loopia + Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNS - Manual + Manual Metaname Metaregistrar mijn.host - Mittwald + Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Technitium + Technitium Tencent Cloud DNS Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr Webnames - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index c160bd826..ee6f726c4 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -55,6 +55,7 @@ func allDNSCodes() string { "dreamhost", "duckdns", "dyn", + "dyndnsfree", "dynu", "easydns", "edgedns", @@ -1096,6 +1097,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dyn`) + case "dyndnsfree": + // generated from: providers/dns/dyndnsfree/dyndnsfree.toml + ew.writeln(`Configuration for DynDnsFree.de.`) + ew.writeln(`Code: 'dyndnsfree'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "DYNDNSFREE_PASSWORD": Password`) + ew.writeln(` - "DYNDNSFREE_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "DYNDNSFREE_HTTP_TIMEOUT": Request timeout in seconds (Default: 30)`) + ew.writeln(` - "DYNDNSFREE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DYNDNSFREE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/dyndnsfree`) + case "dynu": // generated from: providers/dns/dynu/dynu.toml ew.writeln(`Configuration for Dynu.`) diff --git a/docs/content/dns/zz_gen_dyndnsfree.md b/docs/content/dns/zz_gen_dyndnsfree.md new file mode 100644 index 000000000..6f4cf46ff --- /dev/null +++ b/docs/content/dns/zz_gen_dyndnsfree.md @@ -0,0 +1,68 @@ +--- +title: "DynDnsFree.de" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: dyndnsfree +dnsprovider: + since: "v4.23.0" + code: "dyndnsfree" + url: "https://www.dyndnsfree.de" +--- + + + + + + +Configuration for [DynDnsFree.de](https://www.dyndnsfree.de). + + + + +- Code: `dyndnsfree` +- Since: v4.23.0 + + +Here is an example bash command using the DynDnsFree.de provider: + +```bash +DYNDNSFREE_USERNAME="xxx" \ +DYNDNSFREE_PASSWORD="yyy" \ +lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `DYNDNSFREE_PASSWORD` | Password | +| `DYNDNSFREE_USERNAME` | Username | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here]({{% ref "dns#configuration-and-credentials" %}}). + + +## Additional Configuration + +| Environment Variable Name | Description | +|--------------------------------|-------------| +| `DYNDNSFREE_HTTP_TIMEOUT` | Request timeout in seconds (Default: 30) | +| `DYNDNSFREE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DYNDNSFREE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here]({{% ref "dns#configuration-and-credentials" %}}). + + + + +## More information + +- [API documentation](https://www.dyndnsfree.de/user/hilfe.php?hsm=2) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index f06353e4c..8fcb47ba6 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -150,7 +150,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/dyndnsfree/dyndnsfree.go b/providers/dns/dyndnsfree/dyndnsfree.go new file mode 100644 index 000000000..8c1d87aaa --- /dev/null +++ b/providers/dns/dyndnsfree/dyndnsfree.go @@ -0,0 +1,118 @@ +// Package dyndnsfree implements a DNS provider for solving the DNS-01 challenge using DynDnsFree.de API. +package dyndnsfree + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/dyndnsfree/internal" +) + +// Environment variables names. +const ( + envNamespace = "DYNDNSFREE_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Username string + Password string + + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for DynDNSFree. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("dyndnsfree: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for DynDNSFree. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("dyndnsfree: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("dyndnsfree: new client: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dyndnsforfree: could not find zone for domain %q: %w", domain, err) + } + + err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(authZone), dns01.UnFqdn(info.EffectiveFQDN), info.Value) + if err != nil { + return fmt.Errorf("dyndnsfree: add record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + // Records are deleted automatically. + + return nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} diff --git a/providers/dns/dyndnsfree/dyndnsfree.toml b/providers/dns/dyndnsfree/dyndnsfree.toml new file mode 100644 index 000000000..dd354fb33 --- /dev/null +++ b/providers/dns/dyndnsfree/dyndnsfree.toml @@ -0,0 +1,23 @@ +Name = "DynDnsFree.de" +Description = '''''' +URL = "https://www.dyndnsfree.de" +Code = "dyndnsfree" +Since = "v4.23.0" + +Example = ''' +DYNDNSFREE_USERNAME="xxx" \ +DYNDNSFREE_PASSWORD="yyy" \ +lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + DYNDNSFREE_USERNAME = "Username" + DYNDNSFREE_PASSWORD = "Password" + [Configuration.Additional] + DYNDNSFREE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DYNDNSFREE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DYNDNSFREE_HTTP_TIMEOUT = "Request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.dyndnsfree.de/user/hilfe.php?hsm=2" diff --git a/providers/dns/dyndnsfree/dyndnsfree_test.go b/providers/dns/dyndnsfree/dyndnsfree_test.go new file mode 100644 index 000000000..cb063a029 --- /dev/null +++ b/providers/dns/dyndnsfree/dyndnsfree_test.go @@ -0,0 +1,143 @@ +package dyndnsfree + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvUsername, EnvPassword).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "secret", + }, + expected: "dyndnsfree: some credentials information are missing: DYNDNSFREE_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "", + }, + expected: "dyndnsfree: some credentials information are missing: DYNDNSFREE_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "dyndnsfree: some credentials information are missing: DYNDNSFREE_USERNAME,DYNDNSFREE_PASSWORD", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + envTest.Apply(test.envVars) + + p, err := NewDNSProvider() + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + username string + password string + expected string + }{ + { + desc: "success", + username: "user", + password: "secret", + }, + { + desc: "missing username", + username: "", + password: "secret", + expected: "dyndnsfree: new client: credentials missing", + }, + { + desc: "missing password", + username: "user", + password: "", + expected: "dyndnsfree: new client: credentials missing", + }, + { + desc: "missing credentials", + expected: "dyndnsfree: new client: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestLivePresent(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.Present(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} + +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/dyndnsfree/internal/client.go b/providers/dns/dyndnsfree/internal/client.go new file mode 100644 index 000000000..02a1f1607 --- /dev/null +++ b/providers/dns/dyndnsfree/internal/client.go @@ -0,0 +1,78 @@ +package internal + +import ( + "bytes" + "context" + "errors" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://dynup.de/acme.php" + +type Client struct { + username string + password string + + baseURL string + HTTPClient *http.Client +} + +func NewClient(username, password string) (*Client, error) { + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + return &Client{ + username: username, + password: password, + baseURL: defaultBaseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddTXTRecord(ctx context.Context, zone, hostname, value string) error { + baseURL, err := url.Parse(c.baseURL) + if err != nil { + return err + } + + query := baseURL.Query() + query.Set("username", c.username) + query.Set("password", c.password) + query.Set("hostname", zone) + query.Set("add_hostname", hostname) + query.Set("txt", value) + baseURL.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL.String(), http.NoBody) + if err != nil { + return err + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return errutils.NewUnexpectedResponseStatusCodeError(req, resp) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + if !bytes.Equal(raw, []byte("success")) { + return errors.New(string(raw)) + } + + return nil +} diff --git a/providers/dns/dyndnsfree/internal/client_test.go b/providers/dns/dyndnsfree/internal/client_test.go new file mode 100644 index 000000000..17ac89b0f --- /dev/null +++ b/providers/dns/dyndnsfree/internal/client_test.go @@ -0,0 +1,58 @@ +package internal + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, message string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + client, err := NewClient("user", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL = server.URL + + mux.HandleFunc("GET /", func(rw http.ResponseWriter, req *http.Request) { + query := req.URL.Query() + + username := query.Get("username") + if username != "user" { + http.Error(rw, "invalid username: "+username, http.StatusUnauthorized) + return + } + + password := query.Get("password") + if password != "secret" { + http.Error(rw, "invalid password: "+password, http.StatusUnauthorized) + return + } + + _, _ = rw.Write([]byte(message)) + }) + + return client +} + +func TestAddTXTRecord(t *testing.T) { + client := setupTest(t, "success") + + err := client.AddTXTRecord(context.Background(), "example.com", "sub.example.com", "value") + require.NoError(t, err) +} + +func TestAddTXTRecord_error(t *testing.T) { + client := setupTest(t, "error: authentification failed") + + err := client.AddTXTRecord(context.Background(), "example.com", "sub.example.com", "value") + require.EqualError(t, err, "error: authentification failed") +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index eba28d83e..ef9dcba28 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -49,6 +49,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/dreamhost" "github.com/go-acme/lego/v4/providers/dns/duckdns" "github.com/go-acme/lego/v4/providers/dns/dyn" + "github.com/go-acme/lego/v4/providers/dns/dyndnsfree" "github.com/go-acme/lego/v4/providers/dns/dynu" "github.com/go-acme/lego/v4/providers/dns/easydns" "github.com/go-acme/lego/v4/providers/dns/edgedns" @@ -252,6 +253,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return duckdns.NewDNSProvider() case "dyn": return dyn.NewDNSProvider() + case "dyndnsfree": + return dyndnsfree.NewDNSProvider() case "dynu": return dynu.NewDNSProvider() case "easydns":